Jilt
Java annotation processor library for auto-generating Builder (including Staged Builder) pattern classes
Install / Use
/learn @skinny85/JiltREADME
Jilt

Jilt is a Java library that automatically generates classes implementing the Builder design pattern. It leverages the Java annotation processing API to perform the code generation at compile time, instead of runtime, which means using it doesn't incur any performance penalty.
Jilt's "killer features" compared to other tools in this same space are:
- Support for the Staged (sometimes also called Type-Safe, or Step, or Telescopic) variant of the Builder pattern. For more information on the Staged Builder pattern, check out my blog article on the subject.
- The capability to generate Builders for any class, and without requiring any modifications to the target classes' source code.
- Seamless interoperability with other annotation processors, most noticeably Lombok.
Jilt is purely a code generator - it does not add any overhead, nor any runtime dependencies, to your code.
Example
Given this class:
import org.jilt.Builder;
@Builder
public final class Person {
public final String name;
public final boolean isAdult;
public Person(String name, boolean isAdult) {
this.name = name;
this.isAdult = isAdult;
}
}
...Jilt will generate the following Builder code:
public class PersonBuilder {
public static PersonBuilder person() {
return new PersonBuilder();
}
private String name;
private boolean isAdult;
public PersonBuilder name(String name) {
this.name = name;
return this;
}
public PersonBuilder isAdult(boolean isAdult) {
this.isAdult = isAdult;
return this;
}
public Person build() {
return new Person(name, isAdult);
}
}
Jilt also works with Java 14+ Records:
import org.jilt.Builder;
@Builder
public record Person(String name, boolean isAdult) {
}
Check out the documentation below for ways to customize what Jilt generates.
Getting Jilt
Jilt is available from the Maven Central repository.
Example Maven settings:
<dependencies>
<dependency>
<groupId>cc.jilt</groupId>
<artifactId>jilt</artifactId>
<version>1.9.1</version>
<scope>provided</scope> <!-- Jilt is not needed at runtime -->
</dependency>
</dependencies>
Example Gradle settings:
repositories {
mavenCentral()
}
dependencies {
// ...
compileOnly("cc.jilt:jilt:1.9.1") // Jilt is not needed at runtime
annotationProcessor("cc.jilt:jilt:1.9.1") // you might also need this dependency in newer Gradle versions
}
If you're not using dependency managers, you can download the JAR directly (it's distributed as a self-contained JAR, you don't need any additional dependencies for it) and add it to your classpath.
Customizing the generated code
@Builder on classes
When you place the @Builder annotation on the class itself,
the resulting Builder will have as properties all instance fields of that class
(you can mark a field with the @Builder.Ignore annotation to exclude it from being added to the Builder),
and will build the instance of that class assuming it has a constructor taking
all of those properties as arguments, in the same order they were declared in the class.
This allows you to easily use Jilt with Lombok;
for instance, the above example could have been rewritten as:
import org.jilt.Builder;
import lombok.Data;
@Builder
@Data
public final class Person {
private final String name;
private final boolean isAdult;
}
@Builder on constructors
You can also place the annotation on a constructor; in that case, the Builder properties will be the constructor parameters, and the instance will be created by calling the constructor. So, this code will produce the same Builder as the above example:
import org.jilt.Builder;
public final class Person {
public final String name;
public final boolean isAdult;
private int thisFieldWillBeIgnoredByTheBuilder;
@Builder
public Person(String name, boolean isAdult) {
this.name = name;
this.isAdult = isAdult;
}
}
@Builder on static methods
Finally, you can also place the @Builder annotation on a (static) method.
In that case, the built class will be the return type of the method,
and the Builder properties will be the method parameters,
in the same order as they were declared in the method.
The instance will be created by making a call to the annotated method.
This is the most flexible way of generating Builders in Jilt - you have full control of the code constructing the final instance, which allows you to do things like:
- Generate Builders for classes without modifying their source code, or for classes that you don't control (from libraries, for example).
- Generate Builders for classes with non-standard ways to construct them (for example, those that use setters instead of constructor parameters).
- Customize the construction behavior - for example, add validations, or default property values.
Here is an example illustrating the possibilities:
import org.jilt.Builder;
import java.util.Date;
public abstract class DateFactory {
@Builder(packageName = "com.example")
public static Date make(int month, int day, int year) {
// validation
if (month < 1 || month > 12) {
throw new IllegalArgumentException("month must be between 1 and 12");
}
// default value
if (day == 0) {
day = 1;
}
// non-standard construction
return new Date(year + 1900, month - 1, day);
}
}
And you can use the generated Builder like so:
import com.example.DateBuilder;
import org.junit.Assert;
import org.junit.Test;
import java.util.Date;
public class DateFactoryTest {
@Test
public void use_date_builder() {
Date date = DateBuilder.date()
.month(12)
.year(23)
.build();
Assert.assertEquals(11, date.getMonth());
Assert.assertEquals(1, date.getDay());
Assert.assertEquals(1923, date.getYear());
}
}
Staged Builders
All Builders shown so far were "regular" Builders.
Using the @Builder's style attribute, you can instead generate a
Staged (also called Type-Safe, or Step, or Telescopic) Builder by setting
style to BuilderStyle.STAGED.
A Staged Builder generates interfaces for each property of the Builder, and enforces that they have to be initialized before constructing the final instance. The order of construction will be exactly as the order of the properties in the Builder.
For a longer and more in-depth introduction to the Staged Builder pattern variant, check out my blog article on the subject.
So, this slightly modified code from above:
import org.jilt.Builder;
import org.jilt.BuilderStyle;
@Builder(style = BuilderStyle.STAGED)
public final class Person {
public final String name;
public final boolean isAdult;
public Person(String name, boolean isAdult) {
this.name = name;
this.isAdult = isAdult;
}
}
...generates a Builder that can be only used as follows:
Person person = PersonBuilder.person()
.name("John Doe") // this has to be 'name' for the code to compile
.isAdult(true) // this has to be 'isAdult' for the code to compile
.build(); // this has to be 'build' for the code to compile
Optional properties
When using Staged Builders, there are often properties that don't have to be provided in order to construct a valid instance of the target class - the property could be optional, it could have some default, etc.
When using the STAGED Builder style, you can mark a field or constructor/static method parameter
(depending on where you placed the @Builder annotation) optional by annotating it with the
@Opt annotation. All optional Builder properties will be grouped into a single interface
(the same containing the build method), which means the client can (but doesn't have to)
provide them, after all the required properties have been set.
If a value for an optional property is not set, Jilt will construct the instance with
the zero-value for that property's type (0 for int and other numeric types,null for reference types, etc.)
as the value of the property.
For example, a Builder for this class:
import org.jilt.Builder;
import org.jilt.BuilderStyle;
import org.jilt.Opt;
public final class User {
public final String email, username, firstName, lastName, displayName;
@Builder(style = BuilderStyle.STAGED)
public User(String email, @Opt String username, String firstName,
String lastName, @Opt String displayName) {
this.email = email;
this.username = username == null ? email : username;
this.firstName = firstName;
this.lastName = lastName;
this.displayName = displayName == null
? firstName +
Related Skills
node-connect
338.7kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
83.6kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
338.7kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
commit-push-pr
83.6kCommit, push, and open a PR
