Simple-builders supports fine-grained configuration through the @SimpleBuilder.Options annotation and compiler options.
- Overview
- Annotation Configuration
- Template Annotations
- Compiler Options
- Configuration Options
- Examples
- Priority Rules
- AccessModifier Enum
- Troubleshooting
- Best Practices
- Reference
Configuration follows a priority system:
- Annotation values - Highest priority
- Compiler options - Medium priority
- Default values - Lowest priority
This allows you to set project-wide defaults while still being able to override them per-class when needed.
Configure individual builders using @SimpleBuilder with @SimpleBuilder.Options:
@SimpleBuilder
@SimpleBuilder.Options(
generateFieldSupplier = OptionState.ENABLED,
generateFieldConsumer = OptionState.ENABLED,
generateBuilderConsumer = OptionState.ENABLED,
generateConditionalHelper = OptionState.ENABLED,
builderAccess = AccessModifier.PUBLIC,
methodAccess = AccessModifier.PUBLIC,
generateVarArgsHelpers = OptionState.ENABLED,
usingArrayListBuilder = OptionState.ENABLED,
usingHashMapBuilder = OptionState.ENABLED,
generateWithInterface = OptionState.ENABLED
)
public class PersonDto {
private String name;
private int age;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
}Create reusable configuration presets with custom template annotations:
@SimpleBuilder.Template(options = @SimpleBuilder.Options(
generateFieldSupplier = OptionState.DISABLED,
generateFieldConsumer = OptionState.DISABLED,
generateBuilderConsumer = OptionState.DISABLED,
generateConditionalHelper = OptionState.DISABLED,
generateVarArgsHelpers = OptionState.DISABLED,
generateStringFormatHelpers = OptionState.DISABLED,
generateAddToCollectionHelpers = OptionState.DISABLED,
generateUnboxedOptional = OptionState.DISABLED,
copyTypeAnnotations = OptionState.DISABLED,
usingArrayListBuilder = OptionState.DISABLED,
usingArrayListBuilderWithElementBuilders = OptionState.DISABLED,
usingHashSetBuilder = OptionState.DISABLED,
usingHashSetBuilderWithElementBuilders = OptionState.DISABLED,
usingHashMapBuilder = OptionState.DISABLED,
generateWithInterface = OptionState.DISABLED,
usingGeneratedAnnotation = OptionState.DISABLED,
usingBuilderImplementationAnnotation = OptionState.DISABLED,
implementsBuilderBase = OptionState.DISABLED,
usingJacksonDeserializerAnnotation = OptionState.DISABLED
))
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface MinimalBuilder {}Then use your template:
@MinimalBuilder // No need for @SimpleBuilder - template includes it!
public class PersonDto {
private String name;
}Set project-wide defaults via compiler options. These apply to all builders unless overridden by annotations.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>17</source>
<target>17</target>
<annotationProcessorPaths>
<path>
<groupId>io.github.java-helpers</groupId>
<artifactId>simple-builders-processor</artifactId>
<version>${simple-builders.version}</version>
</path>
</annotationProcessorPaths>
<compilerArgs>
<arg>-Asimplebuilder.generateFieldSupplier=ENABLED</arg>
<arg>-Asimplebuilder.generateFieldConsumer=ENABLED</arg>
<arg>-Asimplebuilder.builderAccess=PUBLIC</arg>
<arg>-Asimplebuilder.usingArrayListBuilder=ENABLED</arg>
</compilerArgs>
</configuration>
</plugin>dependencies {
annotationProcessor "io.github.java-helpers:simple-builders-processor:${simpleBuildersVersion}"
}
compileJava {
options.compilerArgs += [
"-Asimplebuilder.generateFieldSupplier=ENABLED",
"-Asimplebuilder.generateFieldConsumer=ENABLED",
"-Asimplebuilder.builderAccess=PUBLIC"
]
}- Go to Settings → Build, Execution, Deployment → Compiler → Java Compiler
- Add to Additional command line parameters:
-Asimplebuilder.generateFieldSupplier=true -Asimplebuilder.builderAccess=PUBLIC
All options use OptionState enum with values: ENABLED, DISABLED, or UNSET (uses default/compiler arg).
Default: ENABLED | Compiler Option: -Asimplebuilder.generateFieldSupplier=ENABLED|DISABLED
Generates setter methods that accept Supplier<T> for lazy field value initialization.
When ENABLED:
// Generated method
public PersonDtoBuilder name(Supplier<String> nameSupplier) {
this.name = changedValue(nameSupplier.get());
return this;
}
// Usage
PersonDto person = PersonDtoBuilder.create()
.name(() -> expensiveNameComputation())
.build();When DISABLED: No Supplier<> setter methods are generated.
Default: ENABLED | Compiler Option: -Asimplebuilder.generateFieldConsumer=ENABLED|DISABLED
Generates setter methods that accept Consumer<StringBuilder> for String fields, allowing fluent string building.
When ENABLED:
// Generated method for String fields
public PersonDtoBuilder name(Consumer<StringBuilder> nameConsumer) {
StringBuilder builder = new StringBuilder();
nameConsumer.accept(builder);
this.name = changedValue(builder.toString());
return this;
}
// Usage
PersonDto person = PersonDtoBuilder.create()
.name(sb -> sb.append("Dr. ").append(firstName).append(" ").append(lastName))
.build();When DISABLED: No Consumer<StringBuilder> methods are generated.
Default: ENABLED | Compiler Option: -Asimplebuilder.generateBuilderConsumer=ENABLED|DISABLED
Generates setter methods that accept Consumer<Builder> for complex nested objects and collections.
When ENABLED:
// Generated method for collection fields
public PersonDtoBuilder tags(Consumer<ArrayListBuilder<String>> tagsConsumer) {
ArrayListBuilder<String> builder = new ArrayListBuilder<>();
tagsConsumer.accept(builder);
this.tags = changedValue(builder.build());
return this;
}
// Usage
PersonDto person = PersonDtoBuilder.create()
.tags(list -> list.add("java").add("kotlin").add("scala"))
.build();When DISABLED: No builder consumer methods are generated.
Default: ENABLED | Compiler Option: -Asimplebuilder.generateConditionalHelper=ENABLED|DISABLED
Generates conditional helper methods for fluent conditional logic in builder chains.
When ENABLED:
// Generated methods
public PersonDtoBuilder conditional(BooleanSupplier condition,
Consumer<PersonDtoBuilder> trueCase,
Consumer<PersonDtoBuilder> falseCase) { ... }
public PersonDtoBuilder conditional(BooleanSupplier condition,
Consumer<PersonDtoBuilder> yesCondition) { ... }
// Usage
PersonDto person = PersonDtoBuilder.create()
.name("John")
.conditional(() -> isPremiumUser,
builder -> builder.premiumFeatures(true),
builder -> builder.premiumFeatures(false))
.build();When DISABLED: No conditional helper methods are generated.
Default: PUBLIC | Compiler Option: -Asimplebuilder.builderAccess=PUBLIC|PACKAGE_PRIVATE
Controls the visibility of the generated builder class.
Supported Values:
PUBLIC- Builder accessible from anywhere (default, recommended for public APIs)PACKAGE_PRIVATE- Builder only accessible within the same package (good for internal APIs)
PRIVATE is not allowed for builderAccess. If you try to use it, builder generation will fail with an error message explaining that Java does not allow private top-level classes. The builder will not be generated, but other DTOs in your project will continue processing normally.
Example with PACKAGE_PRIVATE:
// Generated builder
class PersonDtoBuilder implements IBuilderBase<PersonDto> { // No 'public' keyword
// ... only accessible within the same package
}Use case: Use PACKAGE_PRIVATE for DTOs that are internal to your package and shouldn't have their builders exposed publicly.
Default: PUBLIC | Compiler Option: -Asimplebuilder.builderConstructorAccess=PUBLIC|PACKAGE_PRIVATE|PRIVATE
Controls the visibility of the builder's constructors.
Supported Values:
PUBLIC- Constructors accessible from anywhere (default)PACKAGE_PRIVATE- Constructors only accessible within the same packagePRIVATE- Constructors only accessible via static factory methods ✅ Recommended pattern
Example with PRIVATE (recommended for API design):
// Generated constructors
private PersonDtoBuilder() { }
private PersonDtoBuilder(PersonDto instance) { ... }
// Usage - forced to use static factory methods
PersonDtoBuilder builder = PersonDtoBuilder.create(); // ✅ OK
new PersonDtoBuilder() // ❌ Compilation error - constructor is privateUse case: Use PRIVATE constructors to enforce using the static create() factory method, preventing direct instantiation and ensuring consistent builder creation patterns.
Default: PUBLIC | Compiler Option: -Asimplebuilder.methodAccess=PUBLIC|PACKAGE_PRIVATE
Controls the visibility of all generated setter methods.
Supported Values:
PUBLIC- Methods accessible from anywhere (default, recommended)PACKAGE_PRIVATE- Methods only accessible within the same package
PRIVATE is not allowed for methodAccess. If you try to use it, builder generation will fail with an error message explaining that all setter methods would be inaccessible. The builder will not be generated, but other DTOs in your project will continue processing normally.
Example with PACKAGE_PRIVATE:
// Generated methods without 'public' modifier
PersonDtoBuilder name(String name) { // Package-private
this.name = changedValue(name);
return this;
}Use case: Rarely needed. Consider using PACKAGE_PRIVATE only when the entire builder API should be internal to the package.
Default: ENABLED | Compiler Option: -Asimplebuilder.generateVarArgsHelpers=ENABLED|DISABLED
Generates varargs methods for List and Set fields for convenient multi-value initialization.
When ENABLED:
// Generated method
public PersonDtoBuilder tags(String... tags) {
this.tags = changedValue(Arrays.asList(tags));
return this;
}
// Usage
PersonDto person = PersonDtoBuilder.create()
.tags("java", "kotlin", "scala") // Varargs syntax
.build();When DISABLED: No varargs methods are generated; must use collection directly.
Default: ENABLED | Compiler Option: -Asimplebuilder.generateStringFormatHelpers=ENABLED|DISABLED
Generates String.format() helper methods for String and Optional fields.
When ENABLED:
// For String title field:
public BookDtoBuilder title(String format, Object... args) {
this.title = changedValue(String.format(format, args));
return this;
}
// For Optional<String> subtitle field:
public BookDtoBuilder subtitle(String format, Object... args) {
this.subtitle = changedValue(Optional.of(String.format(format, args)));
return this;
}
// Usage
BookDto book = BookDtoBuilder.create()
.title("The %s Guide", "Complete")
.subtitle("A comprehensive %s tutorial for %s", "Java", "beginners")
.build();When DISABLED: No format helper methods are generated.
Default: ENABLED | Compiler Option: -Asimplebuilder.generateAddToCollectionHelpers=ENABLED|DISABLED
Generates add2FieldName() helper methods for List and Set fields to add single elements.
When ENABLED:
// Generated methods
public PersonDtoBuilder add2Nicknames(String element) {
List<String> newCollection;
if (this.nicknames.isSet()) {
newCollection = new ArrayList<>(this.nicknames.value());
} else {
newCollection = new ArrayList<>();
}
newCollection.add(element);
this.nicknames = changedValue(newCollection);
return this;
}
// Usage
PersonDto person = PersonDtoBuilder.create()
.name("John")
.add2Nicknames("Johnny")
.add2Nicknames("JD")
.add2Tags("developer")
.build();When DISABLED: No add2 helper methods are generated; must use collection setters or consumer methods.
Default: ENABLED | Compiler Option: -Asimplebuilder.generateUnboxedOptional=ENABLED|DISABLED
Generates methods that accept Optional<T> and automatically unwrap them.
When ENABLED:
// Generated method
public PersonDtoBuilder name(Optional<String> nameOptional) {
nameOptional.ifPresent(value -> this.name = changedValue(value));
return this;
}
// Usage
Optional<String> maybeName = findName();
PersonDto person = PersonDtoBuilder.create()
.name(maybeName) // Automatically unwrapped
.build();When DISABLED: Must unwrap Optional manually before passing to builder.
Default: ENABLED | Compiler Option: -Asimplebuilder.copyTypeAnnotations=ENABLED|DISABLED
Copies type annotations (TYPE_USE) from the DTO fields to the builder fields and methods. This is useful for validation annotations (e.g. @NotNull, @Size) or other metadata that should be preserved.
When ENABLED:
// DTO
private List<@NotNull String> items;
// Generated Builder
private TrackedValue<List<@NotNull String>> items;
public Builder items(List<@NotNull String> items) { ... }When DISABLED: Type annotations are stripped from the builder.
Default: ENABLED | Compiler Option: -Asimplebuilder.usingArrayListBuilder=ENABLED|DISABLED
Generates methods using ArrayListBuilder for fluent List construction.
When ENABLED:
// Generated method
public PersonDtoBuilder tags(Consumer<ArrayListBuilder<String>> consumer) {
ArrayListBuilder<String> builder = new ArrayListBuilder<>();
consumer.accept(builder);
this.tags = changedValue(builder.build());
return this;
}
// Usage
.tags(list -> list.add("tag1").add("tag2").addAll(otherTags))When DISABLED: Basic List setter only; no fluent list building.
Default: ENABLED | Compiler Option: -Asimplebuilder.usingArrayListBuilderWithElementBuilders=ENABLED|DISABLED
Generates methods using ArrayListBuilderWithElementBuilders for fluent construction of Lists containing complex objects that have their own builders.
When ENABLED:
// For List<PersonDto> where PersonDto has a builder
public TeamDtoBuilder members(Consumer<ArrayListBuilderWithElementBuilders<PersonDto, PersonDtoBuilder>> consumer) {
ArrayListBuilderWithElementBuilders<PersonDto, PersonDtoBuilder> builder = ...;
consumer.accept(builder);
this.members = changedValue(builder.build());
return this;
}
// Usage - build complex nested objects inline
.members(list -> list
.add(person -> person.name("Alice").age(30))
.add(person -> person.name("Bob").age(25)))When DISABLED: No builder consumer methods for complex list elements; must construct objects separately.
Default: ENABLED | Compiler Option: -Asimplebuilder.usingHashSetBuilder=ENABLED|DISABLED
Generates methods using HashSetBuilder for fluent Set construction.
When ENABLED:
// Generated method
public PersonDtoBuilder tags(Consumer<HashSetBuilder<String>> consumer) {
HashSetBuilder<String> builder = new HashSetBuilder<>();
consumer.accept(builder);
this.tags = changedValue(builder.build());
return this;
}
// Usage
.tags(set -> set.add("tag1").add("tag2").addAll(otherTags))When DISABLED: Basic Set setter only; no fluent set building.
Default: ENABLED | Compiler Option: -Asimplebuilder.usingHashSetBuilderWithElementBuilders=ENABLED|DISABLED
Generates methods using HashSetBuilderWithElementBuilders for fluent construction of Sets containing complex objects that have their own builders.
When ENABLED:
// For Set<PersonDto> where PersonDto has a builder
public TeamDtoBuilder uniqueMembers(Consumer<HashSetBuilderWithElementBuilders<PersonDto, PersonDtoBuilder>> consumer) {
HashSetBuilderWithElementBuilders<PersonDto, PersonDtoBuilder> builder = ...;
consumer.accept(builder);
this.uniqueMembers = changedValue(builder.build());
return this;
}
// Usage - build complex nested objects inline
.uniqueMembers(set -> set
.add(person -> person.name("Alice").email("alice@example.com"))
.add(person -> person.name("Bob").email("bob@example.com")))When DISABLED: No builder consumer methods for complex set elements; must construct objects separately.
Default: ENABLED | Compiler Option: -Asimplebuilder.usingHashMapBuilder=ENABLED|DISABLED
Generates methods using HashMapBuilder for fluent Map construction.
Default: "" (empty) | Compiler Option: -Asimplebuilder.deactivateGenerationComponents=pattern1,pattern2,...
Deactivates specific method generators and builder enhancers by class name pattern. This allows you to override default generators/enhancers with your own custom implementations.
Primary Use Case: Override Default Components
When you want to replace a built-in generator or enhancer with your own custom implementation, you first deactivate the default component, then register your custom one via ServiceLoader.
For detailed instructions on creating custom generators and enhancers, see CUSTOMIZING.md.
Pattern Matching:
- Exact match:
ConditionalEnhancer- deactivates exactly this class - Wildcard suffix:
*HelperGenerator- deactivates all classes ending with HelperGenerator - Wildcard prefix:
String*- deactivates all classes starting with String - Wildcard anywhere:
*Consumer*- deactivates all classes containing Consumer
Feature Toggling vs Component Override:
- Feature toggling: Use
@SimpleBuilder.Options(generateConditionalHelper = DISABLED) - Component override: Use
deactivateGenerationComponents+ custom ServiceLoader implementation
See CUSTOMIZING.md for complete implementation examples and best practices.
Default: ENABLED | Compiler Option: -Asimplebuilder.generateWithInterface=ENABLED|DISABLED
Generates a With interface that can be implemented by your DTO to enable fluent modification methods.
When ENABLED:
// Generated interface inside the builder
public interface With {
default PersonDto with(Consumer<PersonDtoBuilder> modifications) {
PersonDtoBuilder builder = new PersonDtoBuilder((PersonDto) this);
modifications.accept(builder);
return builder.build();
}
default PersonDtoBuilder with() {
return new PersonDtoBuilder((PersonDto) this);
}
}
// Your DTO can implement it
public class PersonDto implements PersonDtoBuilder.With {
// ...
}
// Usage - create modified copies
PersonDto original = new PersonDto();
PersonDto modified = original.with(p -> p.name("New Name").age(30));When DISABLED: No With interface is generated. DTOs cannot use the fluent modification pattern.
Default: ENABLED | Compiler Option: -Asimplebuilder.implementsBuilderBase=ENABLED|DISABLED
Makes the generated builder implement IBuilderBase<T> interface for framework integration.
When ENABLED:
public class PersonDtoBuilder implements IBuilderBase<PersonDto> {
// Can be used with generic builder frameworks
}When DISABLED: Builder is a standalone class without interface.
Default: ENABLED | Compiler Option: -Asimplebuilder.usingGeneratedAnnotation=ENABLED|DISABLED
Adds @Generated annotation to the builder class for tooling and code coverage exclusion.
When ENABLED:
@Generated("Generated by org.javahelpers.simple.builders.processor.BuilderProcessor")
public class PersonDtoBuilder implements IBuilderBase<PersonDto> {
// ...
}When DISABLED: No @Generated annotation. Useful if you want the builder counted in code coverage.
Default: ENABLED | Compiler Option: -Asimplebuilder.usingBuilderImplementationAnnotation=ENABLED|DISABLED
Adds @BuilderImplementation annotation linking the builder to its target class.
When ENABLED:
@BuilderImplementation(forClass = PersonDto.class)
public class PersonDtoBuilder implements IBuilderBase<PersonDto> {
// ...
}When DISABLED: No @BuilderImplementation annotation.
Default: DISABLED | Compiler Option: -Asimplebuilder.usingJacksonDeserializerAnnotation=ENABLED|DISABLED
Adds @JsonPOJOBuilder annotation to the builder class for Jackson deserialization support.
When ENABLED:
@JsonPOJOBuilder(withPrefix = "")
public class PersonDtoBuilder {
// ...
}When DISABLED: No @JsonPOJOBuilder annotation.
Note: This requires com.fasterxml.jackson.core:jackson-databind on the classpath during compilation. If missing, the annotation is skipped with a warning.
Default: DISABLED | Compiler Option: -Asimplebuilder.generateJacksonModule=ENABLED|DISABLED
Generates a Jackson SimpleModule (named SimpleBuildersJacksonModule) that registers all generated builders via MixIns. This allows deserialization without annotating your DTOs with @JsonDeserialize.
Requirement: You MUST also enable usingJacksonDeserializerAnnotation. If generateJacksonModule is enabled but usingJacksonDeserializerAnnotation is disabled, the processor will issue a warning and skip module generation.
When ENABLED:
- The builder is generated as usual.
- A
SimpleBuildersJacksonModuleclass is generated. - The module registers a MixIn for the DTO that points to the Builder.
Package Name:
By default, a SimpleBuildersJacksonModule is generated in each package that contains DTOs configured for Jackson module generation. This ensures deterministic behavior.
To specify a single fixed package name for all generated modules (grouping them into one), use the jacksonModulePackage option.
Generated Module Example:
package com.example.project.dto; // Generated in the same package as DTOs (by default)
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
public class SimpleBuildersJacksonModule extends SimpleModule {
public SimpleBuildersJacksonModule() {
setMixInAnnotation(PersonDto.class, PersonDtoMixin.class);
}
@JsonDeserialize(builder = PersonDtoBuilder.class)
private interface PersonDtoMixin {}
}Usage:
ObjectMapper mapper = new ObjectMapper();
// Register the module for your package
// Note: If you have DTOs in multiple packages and use the default strategy,
// you need to register the generated module for each package.
mapper.registerModule(new com.example.project.dto.SimpleBuildersJacksonModule());
PersonDto dto = mapper.readValue(json, PersonDto.class);Tip: Use the jacksonModulePackage option to generate a single module for your entire project, making registration easier:
mapper.registerModule(new com.example.project.config.SimpleBuildersJacksonModule());
Note: This requires com.fasterxml.jackson.core:jackson-databind on the classpath.
Default: null (uses package of each processed DTO) | Compiler Option: -Asimplebuilder.jacksonModulePackage=com.your.package
Specifies the package name where the SimpleBuildersJacksonModule class will be generated.
This is highly recommended to ensure deterministic output location and avoid split-package issues.
Note: If not specified, a separate SimpleBuildersJacksonModule will be generated in each package containing processed DTOs.
Example:
-Asimplebuilder.jacksonModulePackage=com.example.project.config
Default: "Builder" | Compiler Option: -Asimplebuilder.builderSuffix=CustomSuffix
Customizes the suffix appended to the DTO class name to create the builder class name.
Example:
@SimpleBuilder.Options(builderSuffix = "Factory")
public class PersonDto { }
// Generated class name: PersonDtoFactory (instead of PersonDtoBuilder)Default: "" (empty) | Compiler Option: -Asimplebuilder.setterSuffix=customPrefix
Adds a prefix to all setter method names.
Example:
@SimpleBuilder.Options(setterSuffix = "with")
public class PersonDto {
private String name;
}
// Generated method: withName(String name) instead of name(String name)Default: DISABLED | Compiler Option: -Asimplebuilder.strict=ENABLED|DISABLED
When enabled, builder and Jackson-module generation failures are promoted from compiler warnings to errors that fail the build. By default, strict mode is disabled and generation failures are reported as warnings so compilation can continue.
Generate only essential builder methods:
@SimpleBuilder(
options = @SimpleBuilder.Options(
generateFieldSupplier = OptionState.DISABLED,
generateFieldConsumer = OptionState.DISABLED,
generateBuilderConsumer = OptionState.DISABLED,
generateConditionalHelper = OptionState.DISABLED,
generateVarArgsHelpers = OptionState.DISABLED,
generateStringFormatHelpers = OptionState.DISABLED,
generateAddToCollectionHelpers = OptionState.DISABLED,
generateUnboxedOptional = OptionState.DISABLED,
copyTypeAnnotations = OptionState.DISABLED,
usingArrayListBuilder = OptionState.DISABLED,
usingArrayListBuilderWithElementBuilders = OptionState.DISABLED,
usingHashSetBuilder = OptionState.DISABLED,
usingHashSetBuilderWithElementBuilders = OptionState.DISABLED,
usingHashMapBuilder = OptionState.DISABLED,
generateWithInterface = OptionState.DISABLED,
usingGeneratedAnnotation = OptionState.DISABLED,
usingBuilderImplementationAnnotation = OptionState.DISABLED,
implementsBuilderBase = OptionState.DISABLED,
usingJacksonDeserializerAnnotation = OptionState.DISABLED
)
public class MinimalDto {
private String name;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}Generated: Only basic builder methods (create(), field setters, build())
Create builders for internal use only:
@SimpleBuilder
@SimpleBuilder.Options(
builderAccess = AccessModifier.PACKAGE_PRIVATE,
methodAccess = AccessModifier.PACKAGE_PRIVATE
)
public class InternalConfig {
private String secretKey;
public String getSecretKey() { return secretKey; }
public void setSecretKey(String secretKey) { this.secretKey = secretKey; }
}Generated: Package-private builder and methods, only accessible within the same package
Optimize for collection manipulation:
@SimpleBuilder
@SimpleBuilder.Options(
generateVarArgsHelpers = OptionState.ENABLED,
usingArrayListBuilder = OptionState.ENABLED,
usingArrayListBuilderWithElementBuilders = OptionState.ENABLED,
usingHashSetBuilder = OptionState.ENABLED,
usingHashMapBuilder = OptionState.ENABLED
)
public class TeamDto {
private List<String> memberNames;
private Set<PersonDto> members;
private Map<String, PersonDto> memberMap;
}Generated: Chained collection builders for fluent collection manipulation
Example usage:
TeamDto team = TeamDtoBuilder.create()
.memberNames(list -> list.add("Alice").add("Bob"))
.members(set -> set
.add(person -> person.name("Alice").age(30))
.add(person -> person.name("Bob").age(25)))
.memberMap(map -> map.put("Alice", alice).put("Bob", bob))
.build();Create a reusable template for lightweight builders:
@SimpleBuilder.Template(options = @SimpleBuilder.Options(
generateFieldSupplier = OptionState.DISABLED,
generateFieldConsumer = OptionState.DISABLED,
generateBuilderConsumer = OptionState.DISABLED,
generateConditionalHelper = OptionState.DISABLED,
generateVarArgsHelpers = OptionState.DISABLED,
generateStringFormatHelpers = OptionState.DISABLED,
generateAddToCollectionHelpers = OptionState.DISABLED,
generateUnboxedOptional = OptionState.DISABLED,
copyTypeAnnotations = OptionState.DISABLED,
usingArrayListBuilder = OptionState.DISABLED,
usingArrayListBuilderWithElementBuilders = OptionState.DISABLED,
usingHashSetBuilder = OptionState.DISABLED,
usingHashSetBuilderWithElementBuilders = OptionState.DISABLED,
usingHashMapBuilder = OptionState.DISABLED,
generateWithInterface = OptionState.DISABLED,
usingGeneratedAnnotation = OptionState.DISABLED,
usingBuilderImplementationAnnotation = OptionState.DISABLED,
implementsBuilderBase = OptionState.DISABLED,
usingJacksonDeserializerAnnotation = OptionState.DISABLED
))
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface MinimalBuilder {}Use everywhere:
@MinimalBuilder
public class CustomerDto {
private String name;
private List<OrderDto> orders;
}Set sensible defaults for your entire project:
<!-- Maven compiler plugin configuration -->
<compilerArgs>
<!-- Enable all field setter variants -->
<arg>-Asimplebuilder.generateFieldSupplier=ENABLED</arg>
<arg>-Asimplebuilder.generateFieldConsumer=ENABLED</arg>
<arg>-Asimplebuilder.generateBuilderConsumer=ENABLED</arg>
<!-- Make builders package-private for internal APIs -->
<arg>-Asimplebuilder.builderAccess=PACKAGE_PRIVATE</arg>
<!-- Enable collection helpers -->
<arg>-Asimplebuilder.usingArrayListBuilder=ENABLED</arg>
<arg>-Asimplebuilder.usingHashMapBuilder=ENABLED</arg>
</compilerArgs>Override per-class when needed:
@SimpleBuilder
@SimpleBuilder.Options(builderAccess = AccessModifier.PUBLIC) // Override: make this one public
public class PublicApiDto {
private String data;
}
@SimpleBuilder // Uses project defaults: package-private
public class InternalDto {
private String data;
}Configuration resolution follows these priority rules:
- Annotation values (highest priority)
- Values in
@SimpleBuilder.Options(...)always win
- Values in
- Compiler options (medium priority)
- Used when no annotation value is specified
- Default values (lowest priority)
- Used when neither annotation nor compiler option is specified
// Compiler option: -Asimple.builders.generateFieldSupplier=false
// Global default: true
@SimpleBuilder
@SimpleBuilder.Options(generateFieldSupplier = OptionState.ENABLED) // Annotation wins!
public class Person {
private String name;
}
@SimpleBuilder // Uses compiler option (false)
public class Company {
private String name;
}
// No compiler option set
@SimpleBuilder // Uses global default (true)
public class Product {
private String name;
}The AccessModifier enum provides type-safe access control:
public enum AccessModifier {
PUBLIC, // Accessible from anywhere
PROTECTED, // Accessible within same package and subclasses
PACKAGE_PRIVATE, // Accessible only within same package (default Java visibility)
PRIVATE // Accessible only within same class
}Use in annotations:
@SimpleBuilder.Options(
builderAccess = AccessModifier.PACKAGE_PRIVATE,
methodAccess = AccessModifier.PUBLIC
)Or in compiler options:
-Asimplebuilder.builderAccess=PACKAGE_PRIVATE
- Check option names: Ensure you're using the full option name (e.g.,
-Asimplebuilder.generateFieldSupplier) - Verify processor is running: Ensure annotation processor is configured correctly
- Check IDE configuration: Some IDEs need special configuration for compiler options
- Clean and rebuild: Run
mvn clean compileto ensure fresh build
- Verify annotation import: Import
org.javahelpers.simple.builders.core.annotations.SimpleBuilder - Check annotation placement: Use
@SimpleBuilderon the class,@SimpleBuilder.Optionson the same class - Verify compilation: Recompile after changing annotations
- Check for syntax errors: Ensure AccessModifier enum values are correct
- Package-private builders: Ensure DTO and builder are in the same package
- Private builders: May cause issues with reflection-based frameworks
- Protected builders: Only accessible to subclasses
- AccessModifier import: Import
org.javahelpers.simple.builders.core.enums.AccessModifier
- Check @SimpleBuilder.Template: Ensure template annotation has
@SimpleBuilder.Template - Verify options parameter: Template must specify
options = @SimpleBuilder.Options(...) - Retention and Target: Add
@Retention(RetentionPolicy.CLASS)and@Target(ElementType.TYPE) - Don't combine: Don't use
@SimpleBuilderwhen using a template annotation
If you see warnings like "Failed to generate builder" with access modifier messages:
Problem: Used PRIVATE for builderAccess or methodAccess
@SimpleBuilder.Options(builderAccess = AccessModifier.PRIVATE) // ❌ ERRORSolution: Use PUBLIC or PACKAGE_PRIVATE instead
@SimpleBuilder.Options(builderAccess = AccessModifier.PACKAGE_PRIVATE) // ✅ OKNote: Only builderConstructorAccess = PRIVATE is valid - this enforces using the create() factory method.
Error Messages:
builderAccess=PRIVATE→ "Java does not allow private top-level classes"methodAccess=PRIVATE→ "Makes all setter methods inaccessible"
- Use templates for common patterns: Define reusable templates for your project
- Set project-wide defaults: Configure sensible defaults via compiler options
- Override sparingly: Only override when truly necessary
- Document templates: Add JavaDoc to custom template annotations
- Use type-safe enums: Prefer
AccessModifierenum over string values - Test configurations: Verify generated code meets expectations
- Consider team preferences: Choose configurations that work for everyone
Recommended Combinations:
✅ Public API Builder (most common):
@SimpleBuilder.Options(
builderAccess = AccessModifier.PUBLIC, // ✅ Accessible everywhere
builderConstructorAccess = AccessModifier.PRIVATE, // ✅ Forces use of create()
methodAccess = AccessModifier.PUBLIC // ✅ Accessible everywhere
)✅ Internal/Package-Private Builder:
@SimpleBuilder.Options(
builderAccess = AccessModifier.PACKAGE_PRIVATE, // ✅ Internal to package
builderConstructorAccess = AccessModifier.PRIVATE, // ✅ Forces use of create()
methodAccess = AccessModifier.PACKAGE_PRIVATE // ✅ Internal to package
)❌ Invalid Combinations (Will Cause Builder Generation to Fail):
// ❌ ERROR: Private builder class causes generation failure
builderAccess = AccessModifier.PRIVATE
// Error: "Java does not allow private top-level classes"
// Result: Builder NOT generated, other DTOs continue processing
// ❌ ERROR: Private methods cause generation failure
methodAccess = AccessModifier.PRIVATE
// Error: "Makes all setter methods inaccessible"
// Result: Builder NOT generated, other DTOs continue processing
// ❌ ERROR: Both invalid configurations
builderAccess = AccessModifier.PRIVATE,
methodAccess = AccessModifier.PRIVATE
// Result: Builder NOT generated, other DTOs continue processingWhy PRIVATE constructors are different:
- ✅
builderConstructorAccess = PRIVATEIS ALLOWED - Enforces usingcreate()factory method (recommended pattern) - ❌
builderAccess = PRIVATECAUSES ERROR - Java doesn't allow private top-level classes - ❌
methodAccess = PRIVATECAUSES ERROR - Makes all methods inaccessible and builder unusable
What happens when validation fails:
- Builder generation for that DTO is skipped
- A clear warning message is logged explaining the problem
- Compilation continues and succeeds
- Other DTOs in your project still get their builders generated
- No invalid Java code is produced
# Field Setter Generation
-Asimplebuilder.generateFieldSupplier=ENABLED|DISABLED
-Asimplebuilder.generateFieldConsumer=ENABLED|DISABLED
-Asimplebuilder.generateBuilderConsumer=ENABLED|DISABLED
# Conditional Logic
-Asimplebuilder.generateConditionalHelper=ENABLED|DISABLED
# Access Control
-Asimplebuilder.builderAccess=PUBLIC|PACKAGE_PRIVATE # PRIVATE not recommended (unusable builder)
-Asimplebuilder.builderConstructorAccess=PUBLIC|PACKAGE_PRIVATE|PRIVATE # PRIVATE recommended for factory pattern
-Asimplebuilder.methodAccess=PUBLIC|PACKAGE_PRIVATE # PRIVATE not recommended (unusable methods)
# Helper Methods
-Asimplebuilder.generateVarArgsHelpers=ENABLED|DISABLED
-Asimplebuilder.generateStringFormatHelpers=ENABLED|DISABLED
-Asimplebuilder.generateAddToCollectionHelpers=ENABLED|DISABLED
-Asimplebuilder.generateUnboxedOptional=ENABLED|DISABLED
# Collection Helpers
-Asimplebuilder.usingArrayListBuilder=ENABLED|DISABLED
-Asimplebuilder.usingArrayListBuilderWithElementBuilders=ENABLED|DISABLED
-Asimplebuilder.usingHashSetBuilder=ENABLED|DISABLED
-Asimplebuilder.usingHashSetBuilderWithElementBuilders=ENABLED|DISABLED
-Asimplebuilder.usingHashMapBuilder=ENABLED|DISABLED
# Component Filtering
-Asimplebuilder.deactivateGenerationComponents=pattern1,pattern2,...
# Integration & Annotations
-Asimplebuilder.generateWithInterface=ENABLED|DISABLED
-Asimplebuilder.implementsBuilderBase=ENABLED|DISABLED
-Asimplebuilder.usingGeneratedAnnotation=ENABLED|DISABLED
-Asimplebuilder.usingBuilderImplementationAnnotation=ENABLED|DISABLED
# Naming
-Asimplebuilder.builderSuffix=CustomSuffix
-Asimplebuilder.setterSuffix=customPrefix
# Reliability
-Asimplebuilder.strict=ENABLED|DISABLED
@SimpleBuilder
@SimpleBuilder.Options(
// Field Setter Generation
generateFieldSupplier = OptionState.ENABLED,
generateFieldConsumer = OptionState.ENABLED,
generateBuilderConsumer = OptionState.ENABLED,
// Conditional Logic
generateConditionalHelper = OptionState.ENABLED,
// Access Control
builderAccess = AccessModifier.PUBLIC,
builderConstructorAccess = AccessModifier.PUBLIC,
methodAccess = AccessModifier.PUBLIC,
// Helper Methods
generateVarArgsHelpers = OptionState.ENABLED,
generateStringFormatHelpers = OptionState.ENABLED,
generateUnboxedOptional = OptionState.ENABLED,
// Collection Helpers
usingArrayListBuilder = OptionState.ENABLED,
usingArrayListBuilderWithElementBuilders = OptionState.ENABLED,
usingHashSetBuilder = OptionState.ENABLED,
usingHashSetBuilderWithElementBuilders = OptionState.ENABLED,
usingHashMapBuilder = OptionState.ENABLED,
// Integration & Annotations
generateWithInterface = OptionState.ENABLED,
implementsBuilderBase = OptionState.ENABLED,
usingGeneratedAnnotation = OptionState.ENABLED,
usingBuilderImplementationAnnotation = OptionState.ENABLED,
usingJacksonDeserializerAnnotation = OptionState.ENABLED,
// Naming
builderSuffix = "Builder",
setterSuffix = ""
)
public class ExampleDto {
private String name;
}Related: README.md