Java API for generating .java source files programmatically with fluent builder interfaces.
—
Template-based code generation system and utilities for generating method bodies, initializers, and managing identifier names. This system provides safe string-based code generation with proper escaping and formatting.
Template-based code generation using format strings with type-safe placeholders. Handles proper Java syntax, escaping, and formatting automatically.
/**
* Template-based code generation with type-safe placeholders
*/
public final class CodeBlock {
// Static factory methods
public static CodeBlock of(String format, Object... args);
public static CodeBlock join(Iterable<CodeBlock> codeBlocks, String separator);
public static Collector<CodeBlock, ?, CodeBlock> joining(String separator);
public static Collector<CodeBlock, ?, CodeBlock> joining(String separator, String prefix, String suffix);
public static Builder builder();
// Instance methods
public boolean isEmpty();
public Builder toBuilder();
public boolean equals(Object o);
public int hashCode();
public String toString();
}Template Placeholders:
$L - Literals - Direct substitution without escaping$S - Strings - Adds quotes and escapes special characters$T - Types - Type references with automatic import management$N - Names - References to other generated elementsUsage Examples:
// Literal substitution ($L)
CodeBlock literals = CodeBlock.of("int count = $L", 42);
// Result: int count = 42
CodeBlock booleanLiteral = CodeBlock.of("boolean flag = $L", true);
// Result: boolean flag = true
// String substitution ($S)
CodeBlock strings = CodeBlock.of("String message = $S", "Hello, World!");
// Result: String message = "Hello, World!"
CodeBlock escapedString = CodeBlock.of("String path = $S", "C:\\Users\\Name");
// Result: String path = "C:\\Users\\Name"
// Type substitution ($T)
CodeBlock types = CodeBlock.of("$T list = new $T<>()", List.class, ArrayList.class);
// Result: List list = new ArrayList<>();
// Automatically adds imports for List and ArrayList
// Name substitution ($N)
MethodSpec helper = MethodSpec.methodBuilder("helperMethod").build();
CodeBlock names = CodeBlock.of("$N()", helper);
// Result: helperMethod()
// Combined usage
CodeBlock combined = CodeBlock.of(
"$T.out.println($S + $N() + $L)",
System.class, "Result: ", helper, 42
);
// Result: System.out.println("Result: " + helperMethod() + 42)Builder for constructing complex code blocks with multiple statements and control flow.
/**
* Builder for constructing complex code blocks
*/
public static final class Builder {
// Adding code
public Builder add(String format, Object... args);
public Builder addNamed(String format, Map<String, ?> arguments);
public Builder add(CodeBlock codeBlock);
// Statements and control flow
public Builder addStatement(String format, Object... args);
public Builder beginControlFlow(String controlFlow, Object... args);
public Builder nextControlFlow(String controlFlow, Object... args);
public Builder endControlFlow();
public Builder endControlFlow(String controlFlow, Object... args);
// Indentation
public Builder indent();
public Builder unindent();
// Building
public CodeBlock build();
}Usage Examples:
// Simple statements
CodeBlock statements = CodeBlock.builder()
.addStatement("int x = $L", 10)
.addStatement("int y = x * $L", 2)
.addStatement("$T.out.println($S + y)", System.class, "Result: ")
.build();
// Control flow - if/else
CodeBlock ifElse = CodeBlock.builder()
.beginControlFlow("if ($N != null)", someVariable)
.addStatement("return $N.toString()", someVariable)
.nextControlFlow("else")
.addStatement("return $S", "null")
.endControlFlow()
.build();
// Loops
CodeBlock forLoop = CodeBlock.builder()
.addStatement("$T<$T> result = new $T<>()", List.class, String.class, ArrayList.class)
.beginControlFlow("for (int i = 0; i < $N.size(); i++)", items)
.addStatement("$T item = $N.get(i)", String.class, items)
.beginControlFlow("if (item != null)")
.addStatement("result.add(item.toUpperCase())")
.endControlFlow()
.endControlFlow()
.addStatement("return result")
.build();
// Try-catch blocks
CodeBlock tryCatch = CodeBlock.builder()
.beginControlFlow("try")
.addStatement("return processData(input)")
.nextControlFlow("catch ($T e)", IOException.class)
.addStatement("$T.error($S, e)", Logger.class, "Failed to process data")
.addStatement("throw new $T($S, e)", RuntimeException.class, "Processing failed")
.endControlFlow()
.build();
// Switch statements
CodeBlock switchBlock = CodeBlock.builder()
.beginControlFlow("switch (type)")
.add("case $S:\n", "STRING")
.indent()
.addStatement("return processString(value)")
.unindent()
.add("case $S:\n", "INTEGER")
.indent()
.addStatement("return processInteger(value)")
.unindent()
.add("default:\n")
.indent()
.addStatement("throw new $T($S + type)", IllegalArgumentException.class, "Unknown type: ")
.unindent()
.endControlFlow()
.build();For complex templates with many parameters:
Map<String, Object> args = new HashMap<>();
args.put("className", "UserService");
args.put("fieldName", "userRepository");
args.put("methodName", "findUser");
args.put("paramType", Long.class);
CodeBlock namedTemplate = CodeBlock.builder()
.addNamed("public class $className:T {\n", args)
.addNamed(" private final $fieldType:T $fieldName:L;\n",
Map.of("fieldType", UserRepository.class, "fieldName", "userRepository"))
.addNamed(" \n")
.addNamed(" public $returnType:T $methodName:L($paramType:T id) {\n",
Map.of("returnType", User.class, "methodName", "findUser", "paramType", Long.class))
.addNamed(" return $fieldName:L.findById(id);\n", args)
.addNamed(" }\n")
.addNamed("}\n")
.build();For reusing arguments in different positions:
CodeBlock positional = CodeBlock.builder()
.add("$2T $1L = new $2T($3S)", "message", String.class, "Hello")
.build();
// Result: String message = new String("Hello")List<CodeBlock> statements = Arrays.asList(
CodeBlock.of("first()"),
CodeBlock.of("second()"),
CodeBlock.of("third()")
);
// Join with comma separator
CodeBlock joined = CodeBlock.join(statements, ", ");
// Result: first(), second(), third()
// Using stream collectors
CodeBlock streamJoined = statements.stream()
.collect(CodeBlock.joining(", ", "Arrays.asList(", ")"));
// Result: Arrays.asList(first(), second(), third())Utility for generating unique, valid Java identifiers and managing name conflicts.
/**
* Utility for allocating unique Java identifier names
*/
public final class NameAllocator implements Cloneable {
// Constructor
public NameAllocator();
// Name allocation
public String newName(String suggestion);
public String newName(String suggestion, Object tag);
public String get(Object tag);
// Static utility
public static String toJavaIdentifier(String suggestion);
// Cloning
public NameAllocator clone();
}Usage Examples:
// Basic name allocation
NameAllocator nameAllocator = new NameAllocator();
String name1 = nameAllocator.newName("count"); // "count"
String name2 = nameAllocator.newName("count"); // "count_"
String name3 = nameAllocator.newName("count"); // "count__"
// Tagged name allocation
nameAllocator.newName("value", "field");
nameAllocator.newName("value", "parameter"); // Different tag, same name allowed
String fieldName = nameAllocator.get("field"); // "value"
String paramName = nameAllocator.get("parameter"); // "value_"
// Java identifier conversion
String validName1 = NameAllocator.toJavaIdentifier("class"); // "class_"
String validName2 = NameAllocator.toJavaIdentifier("2invalid"); // "_2invalid"
String validName3 = NameAllocator.toJavaIdentifier("with-dash"); // "with_dash"
String validName4 = NameAllocator.toJavaIdentifier("with space"); // "with_space"
// Practical usage in code generation
NameAllocator allocator = new NameAllocator();
// Reserve Java keywords
allocator.newName("class", "reserved");
allocator.newName("interface", "reserved");
// Generate method parameters
String userParam = allocator.newName("user", "param");
String contextParam = allocator.newName("context", "param");
// Generate local variables
String resultVar = allocator.newName("result", "local");
String tempVar = allocator.newName("temp", "local");
MethodSpec method = MethodSpec.methodBuilder("processUser")
.addParameter(User.class, userParam)
.addParameter(Context.class, contextParam)
.addStatement("$T $N = new $T()", ProcessResult.class, resultVar, ProcessResult.class)
.addStatement("$T $N", Object.class, tempVar)
.addStatement("// Use allocated names: $N, $N, $N, $N",
userParam, contextParam, resultVar, tempVar)
.build();// Avoid direct string concatenation
// BAD: "return " + expression + ";"
// GOOD: Use CodeBlock templates
CodeBlock safeReturn = CodeBlock.of("return $L", expression);
// Proper escaping for strings
CodeBlock properString = CodeBlock.of("String message = $S", userInput);
// Automatically handles quotes and escaping// Type references ensure proper imports
CodeBlock typeSafe = CodeBlock.of(
"$T<$T> list = $T.asList($L, $L, $L)",
List.class, String.class, Arrays.class,
"first", "second", "third"
);
// Generates proper imports and code:
// import java.util.List;
// import java.util.Arrays;
// List<String> list = Arrays.asList("first", "second", "third");// Building a complete method with complex logic
public MethodSpec generateProcessorMethod(List<String> operations) {
NameAllocator names = new NameAllocator();
String inputParam = names.newName("input", "param");
String resultVar = names.newName("result", "local");
CodeBlock.Builder body = CodeBlock.builder()
.addStatement("$T $N = new $T()", ProcessResult.class, resultVar, ProcessResult.class);
for (String operation : operations) {
String operationVar = names.newName(operation.toLowerCase(), "operation");
body.addStatement("$T $N = perform$L($N)",
Object.class, operationVar,
operation.substring(0, 1).toUpperCase() + operation.substring(1),
inputParam);
body.addStatement("$N.add($N)", resultVar, operationVar);
}
body.addStatement("return $N", resultVar);
return MethodSpec.methodBuilder("process")
.addModifiers(Modifier.PUBLIC)
.addParameter(Object.class, inputParam)
.returns(ProcessResult.class)
.addCode(body.build())
.build();
}// Generate proper exception handling
CodeBlock errorHandling = CodeBlock.builder()
.beginControlFlow("try")
.addStatement("return riskyOperation()")
.nextControlFlow("catch ($T e)", IOException.class)
.addStatement("$T.error($S, e)", Logger.class, "Operation failed")
.addStatement("throw new $T(e.getMessage(), e)", ProcessingException.class)
.nextControlFlow("finally")
.addStatement("cleanup()")
.endControlFlow()
.build();Install with Tessl CLI
npx tessl i tessl/maven-com-squareup--javapoet