How to Build a Java Code Generator with Freemarker Templates
This article explains the scenarios, workflow, and implementation details of creating a Java code generator using Freemarker templates, including template design, data binding, and sample code that produces POJO classes automatically.
1. Introduction
A few days ago I wrote an article about code generators; many readers asked how the generator works and why template engines are needed, so this article explains the code generator process.
2. When to Use a Code Generator
When a project contains a large amount of repetitive, fixed‑format boilerplate code whose structure remains stable across iterations, a code generator can significantly improve efficiency; other situations are not suitable for a generator.
3. Code Generator Creation Process
First, create a template by extracting the fixed parts of the boilerplate code. Then bind dynamic attributes to the template, similar to filling in blanks, which makes a template engine ideal. Using the template engine syntax, data is parsed into the static template and exported as the corresponding source file.
Template engines provide rich directives for conditional data binding. For example, with Freemarker you can use a ternary expression: ${true ? 'checked' : ''} and iterate over a list:
<#list fields as field>
private ${field.fieldType} ${field.fieldName};
</#list>Common Java template engines include Freemarker , Velocity , and Thymeleaf . Although front‑end/back‑end separation reduces some use cases, they remain useful technologies.
4. Code Generator Demonstration
Using Freemarker , we create a simple generator to produce a POJO class. First, add the Freemarker dependency:
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.28</version>
</dependency>4.1 Template Creation
The POJO structure consists of several parts:
java.lang package does not need to be imported.
These rules are encapsulated in a configuration class:
public class JavaProperties {
// package name
private final String pkg;
// class name
private final String entityName;
// field collection, need to override equals/hash to ensure unique names
private final Set<Field> fields = new LinkedHashSet<>();
// import set to avoid duplicates
private final Set<String> imports = new LinkedHashSet<>();
public JavaProperties(String entityName, String pkg) {
this.entityName = entityName;
this.pkg = pkg;
}
public void addField(Class<?> type, String fieldName) {
final String pattern = "java.lang";
String fieldType = type.getName();
if (!fieldType.startsWith(pattern)) {
imports.add(fieldType);
}
Field field = new Field();
int i = fieldType.lastIndexOf(".");
field.setFieldType(fieldType.substring(i + 1));
field.setFieldName(fieldName);
fields.add(field);
}
// getters omitted for brevity
public static class Field {
private String fieldType;
private String fieldName;
public String getFieldType() { return fieldType; }
public void setFieldType(String fieldType) { this.fieldType = fieldType; }
public String getFieldName() { return fieldName; }
public void setFieldName(String fieldName) { this.fieldName = fieldName; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Field field = (Field) o;
return Objects.equals(fieldName, field.fieldName);
}
@Override
public int hashCode() { return Objects.hash(fieldType, fieldName); }
}
}The static template file entity.ftl looks like:
package ${pkg};
<#list imports as impt>
import ${impt};
</#list>
/**
* the ${entityName} type
*/
public class ${entityName} {
<#list fields as field>
private ${field.fieldType} ${field.fieldName};
</#list>
}Freemarker binds data using directives such as #list for iteration.
4.2 Generator Implementation
Freemarker configuration and template processing are performed as follows:
/**
* Simple code generator.
*/
public static void autoCodingJavaEntity(String rootPath,
String templatePath,
String templateName,
JavaProperties javaProperties) throws IOException, TemplateException {
Configuration configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
configuration.setDefaultEncoding("UTF-8");
configuration.setDirectoryForTemplateLoading(new File(templatePath));
Template template = configuration.getTemplate(templateName);
String ext = ".java";
String javaName = javaProperties.getEntityName().concat(ext);
String packageName = javaProperties.getPkg();
String out = rootPath.concat(Stream.of(packageName.split("\\.")).collect(Collectors.joining("/", "/", "/" + javaName)));
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream(out));
template.process(javaProperties, outputStreamWriter);
}Running the following code generates a UserEntity POJO:
// Adjust paths to your project
String rootPath = "C:\\Users\\felord\\IdeaProjects\\codegenerator\\src\\main\\java";
String packageName = "cn.felord.code";
String templatePath = "C:\\Users\\felord\\IdeaProjects\\codegenerator\\src\\main\\resources\\templates";
String templateName = "entity.ftl";
JavaProperties userEntity = new JavaProperties("UserEntity", packageName);
userEntity.addField(String.class, "username");
userEntity.addField(LocalDate.class, "birthday");
userEntity.addField(LocalDateTime.class, "addTime");
userEntity.addField(Integer.class, "gender");
userEntity.addField(Integer.class, "age");
autoCodingJavaEntity(rootPath, templatePath, templateName, userEntity);The generated Java POJO looks like:
5. Summary
This covers the core mechanisms of most code generators and should answer common questions about their operation.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
