FreeBuilder
Automatic generation of the Builder pattern for Java
Install / Use
/learn @inferred/FreeBuilderREADME
FreeBuilder
Automatic generation of the Builder pattern for Java 1.8+
The Builder pattern is a good choice when designing classes whose constructors or static factories would have more than a handful of parameters. — <em>Effective Java, Second Edition</em>, page 39
Project Archival
FreeBuilder was released back in 2015! It was a pet project of my own that unfortunately generated a weird amount of controversy at the company I worked at at the time, and thus ended up being open-sourced after the Immutables project had already been on the scene for most of a year. While I hoped the different design that allowed using partials for robust testing would still be a winner, a community hasn't picked up, and as I haven't used Java at my work for over 5 years now, and it's clearly not keeping up with modern Java, I think it's time to archive it.
If someone feels strongly about picking up where I've left off, please do reach out and let me know! I'd ask that you first fork the project and modernize the dev and CI setup, which really needs doing.
Otherwise, it's been a fun ride, and thanks to everyone who's been building free with me along the way 😁
— Alice
Table of Contents
<!-- START doctoc generated TOC please keep comment here to allow auto update --> <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->- Project Archival
- Background
- How to use FreeBuilder
- Build tools and IDEs
- Release notes
- Troubleshooting
- Alternatives
- Wait, why "free"?
- License
Background
Implementing the Builder pattern in Java is tedious, error-prone and repetitive. Who hasn't seen a ten-argument constructor, thought cross thoughts about the previous maintainers of the class, then added "just one more"? Even a simple four-field class requires 39 lines of code for the most basic builder API, or 72 lines if you don't use a utility like [AutoValue][] to generate the value boilerplate.
FreeBuilder produces all the boilerplate for you, as well as free extras like JavaDoc, getter methods, mapper methods, collections support, nested builders, and partial values (used in testing), which are highly useful, but would very rarely justify their creation and maintenance burden in hand-crafted code. (We also reserve the right to add more awesome methods in future!)
[The Builder pattern] is more verbose…so should only be used if there are enough parameters, say, four or more. But keep in mind that you may want to add parameters in the future. If you start out with constructors or static factories, and add a builder when the class evolves to the point where the number of parameters starts to get out of hand, the obsolete constructors or static factories will stick out like a sore thumb. Therefore, it's often better to start with a builder in the first place. — <em>Effective Java, Second Edition</em>, page 39
How to use FreeBuilder
Quick start
See Build tools and IDEs for how to add FreeBuilder to your project's build and/or IDE.
Create your value type (e.g. Person) as an interface or abstract class,
containing an abstract accessor method for each desired field. Add the
@FreeBuilder annotation to your class, and it will automatically generate an
implementing class and a package-visible builder API (Person_Builder), which
you must subclass. For instance:
import org.inferred.freebuilder.FreeBuilder;
@FreeBuilder
public interface Person {
/** Returns this person's full (English) name. */
String name();
/** Returns this person's age in years, rounded down. */
int age();
/** Returns a new {@link Builder} with the same property values as this person. */
Builder toBuilder();
/** Builder of {@link Person} instances. */
class Builder extends Person_Builder { }
}
The toBuilder() method here is optional but highly recommended.
You may also wish to make the builder's constructor package-protected and manually
provide instead a static builder() method on the value type (though
<em>Effective Java</em> does not do this).
What you get
If you write the Person interface shown above, you get:
- A builder class with:
- a no-args constructor
- JavaDoc
- getters (throwing
IllegalStateExceptionfor unset fields) - setters
- lambda-accepting mapper methods
mergeFromand staticfrommethods to copy data from existing values or builders- a
buildmethod that verifies all fields have been set
- An implementation of
Personwith:toStringequalsandhashCode
- A partial implementation of
Personfor unit tests with:UnsupportedOperationException-throwing getters for unset fieldstoStringequalsandhashCode
Person person = new Person.Builder()
.name("Phil")
.age(31)
.build();
System.out.println(person); // Person{name=Phil, age=31}
JavaBean convention
If you prefer your value types to follow the JavaBean naming convention, just prefix your accessor methods with 'get' (or, optionally, 'is' for boolean accessors). FreeBuilder will follow suit, and additionally add 'set' prefixes on setter methods, as well as dropping the prefix from its toString output.
@FreeBuilder
public interface Person {
/** Returns the person's full (English) name. */
String getName();
/** Returns the person's age in years, rounded down. */
int getAge();
/** Builder of {@link Person} instances. */
class Builder extends Person_Builder { }
}
Person person = new Person.Builder()
.setName("Phil")
.setAge(31)
.build();
System.out.println(person); // Person{name=Phil, age=31}
Accessor methods
For each property foo, the builder gets:
| Method | Description |
|:------:| ----------- |
| A setter method, foo | Throws a NullPointerException if provided a null. (See the sections on Optional and Nullable for ways to store properties that can be missing.) |
| A getter method, foo | Throws an IllegalStateException if the property value has not yet been set. |
| A mapper method, mapFoo | Takes a UnaryOperator. Replaces the current property value with the result of invoking the unary operator on it. Throws a NullPointerException if the operator, or the value it returns, is null. Throws an IllegalStateException if the property value has not yet been set. |
The mapper methods are very useful when modifying existing values, e.g.
Person olderPerson = person.toBuilder().mapAge(age -> age + 1).build();
Defaults and constraints
We use method overrides to add customization like default values and constraint checks. For instance:
@FreeBuilder
public interface Person {
/** Returns the person's full (English) name. */
String name();
/** Returns the person's age in years, rounded down. */
int age();
/** Returns a human-readable description of the person. */
String description();
/** Builder class for {@link Person}. */
class Builder extends Person_Builder {
public Builder() {
// Set defaults in the builder constructor.
description("Indescribable");
}
@Override Builder age(int age) {
// Check single-field (argument) constraints in the setter method.
checkArgument(age >= 0);
return super.age(age);
}
@Override public Person build() {
// Check cross-field (state) constraints in the build me
