Skip to content
This repository has been archived by the owner on Oct 16, 2024. It is now read-only.

Latest commit

 

History

History
1068 lines (809 loc) · 55.3 KB

README.md

File metadata and controls

1068 lines (809 loc) · 55.3 KB

FreeBuilder

Automatic generation of the Builder pattern for Java 1.8+

Maven Central CI (GitHub Workflow)

The Builder pattern is a good choice when designing classes whose constructors or static factories would have more than a handful of parameters. — Effective Java, Second Edition, 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

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. — Effective Java, Second Edition, 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 Effective Java 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 IllegalStateException for unset fields)
    • setters
    • lambda-accepting mapper methods
    • mergeFrom and static from methods to copy data from existing values or builders
    • a build method that verifies all fields have been set
  • An implementation of Person with:
    • toString
    • equals and hashCode
  • A partial implementation of Person for unit tests with:
    • UnsupportedOperationException-throwing getters for unset fields
    • toString
    • equals and hashCode
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 method.
      Person person = super.build();
      checkState(!person.description().contains(person.name()));
      return person;
    }
  }
}

Optional values

If a property is optional—that is, has no reasonable default—then use the Java Optional type (or the Guava Optional type for backwards-compatibility).

  /** Returns an optional human-readable description of the person. */
  Optional<String> description();

This property will now default to Optional.empty(), and the Builder will gain additional convenience setter methods:

Method Description
description(String value) Sets the property to Optional.of(value). Throws a NullPointerException if value is null; this avoids users accidentally clearing an optional value in a way that peer review is unlikely to catch.
clearDescription() Sets the property to Optional.empty().
description(Optional<String> value) Sets the property to value.
nullableDescription(String value) Sets the property to Optional.ofNullable(value).
mapDescription(UnaryOperator<String> mapper If the property value is not empty, this replaces the value with the result of invoking mapper with the existing value, or clears it if mapper returns null. Throws a NullPointerException if mapper is null.

Prefer to use explicit defaults where meaningful, as it avoids the need for edge-case code; but prefer Optional to ad-hoc 'not set' defaults, like -1 or the empty string, as it forces the user to think about those edge cases.

Using @Nullable

As Java currently stands, you should strongly prefer Optional to returning nulls. Using null to represent unset properties is the classic example of Hoare's billion-dollar mistake: a silent source of errors that users won't remember to write test cases for, and which won't be spotted in code reviews. The large "air gap" between the declaration of the getter and the usage is the cause of this problem. Optional uses the compiler to force the call sites to perform explicit null handling, giving reviewers a better chance of seeing mistakes. See also Using and Avoiding Null.

Obviously, greenfield code can trivially adopt Optional, but even existing APIs can be converted to Optional via a simple refactoring sequence; see below. However, if you have compelling legacy reasons that mandate using nulls, you can disable null-checking by marking the getter method @Nullable. (Any annotation type named "Nullable" will do, but you may wish to use javax.annotation.Nullable, as used in Google Guava.)

  /** Returns an optional title to use when addressing the person. */
  @Nullable String title();

This property will now default to null, and the Builder's setter methods will change their null-handling behaviour:

Method Description
title(@Nullable String title) Sets the property to title.
title() Returns the current value of the property. May be null.
mapTitle(UnaryOperator<String> mapper) Takes a UnaryOperator. Replaces the current property value, if it is not null, with the result of invoking mapper on it. Throws a NullPointerException if mapper is null. mapper may return a null.

Converting from @Nullable

This is the O(1), non-tedious, non–error-prone way we recomment converting @Nullable to Optional:

  • Load all your code in Eclipse, or another IDE with support for renaming and inlining.
  • [IDE REFACTOR] Rename all your @Nullable getters to nullableX() (or getNullableX() if you use JavaBean naming conventions).
  • Add an Optional-returning x() (or getX())
  • Implement your nullableX methods as: return x().orElse(null)
    (Guava: return x().orNull())
  • [IDE REFACTOR] Inline your nullableX() methods

At this point, you have effectively performed an automatic translation of a @Nullable method to an Optional-returning one. Of course, your code is not optimal yet (e.g. if (foo.x().orElse(null) != null) instead of if (foo.x().isPresent()) ). Search-and-replace should get most of these issues.

  • [IDE REFACTOR] Rename all your @Nullable setters to nullableX (or setNullableX).

Your API is now FreeBuilder-compatible :)

Collections and Maps

FreeBuilder has special support for collection and map properties, removing the foo accessor method and generating new ones appropriate to the type. Collection and map properties default to an empty collection/map and cannot hold nulls.

  /** Returns a list of descendents for this person. **/
  List<String> descendants();

A List, Set, SortedSet or Multiset property called 'descendants' would generate:

Method Description
addDescendants(String element) Appends element to the collection of descendants. If descendants is a set and the element is already present, it is ignored. Throws a NullPointerException if element is null.
addDescendants(String... elements) Appends all elements to the collection of descendants. If descendants is a set, any elements already present are ignored. Throws a NullPointerException if elements, or any of the values it holds, is null.
addAllDescendants(​Iterable<String> elements)
addAllDescendants(​Stream<String> elements)
addAllDescendants(​Spliterator<String> elements)
Appends all elements to the collection of descendants. If descendants is a set, any elements already present are ignored. Throws a NullPointerException if elements, or any of the values it holds, is null.
mutateDescendants(​Consumer<‌.‌.‌.‌<String>> mutator) Invokes the Consumer mutator with the collection of descendants. (The mutator takes a list, set or map as appropriate.) Throws a NullPointerException if mutator is null. As mutator is a void consumer, any value returned from a lambda will be ignored, so be careful not to call pure functions like stream() expecting the returned collection to replace the existing collection.
clearDescendants() Removes all elements from the collection of descendants, leaving it empty.
descendants() Returns an unmodifiable view of the collection of descendants. Changes to the collection held by the builder will be reflected in the view.
setComparatorForDescendants(​Comparator<? super String> comparator) SortedSet only A protected method that sets the comparator to keep the set elements ordered by. Must be called before any other accessor method for this property. Defaults to the natural ordering of the set's elements.
  /** Returns a map of favourite albums by year. **/
  Map<Integer, String> albums();

A Map property called 'albums' would generate:

Method Description
putAlbums(int key, String value) Associates key with value in albums. Throws a NullPointerException if either parameter is null. Replaces any existing entry.
putAllAlbums(Map<? extends Integer, ? extends String> map) Associates all of map's keys and values in albums. Throws a NullPointerException if the map is null or contains a null key or value. Replaces any existing mapping for all keys in map.
removeAlbums(int key) Removes the mapping for key from albums. Throws a NullPointerException if the parameter is null. Does nothing if the key is not present.
mutateAlbums(​Consumer<Map<Integer, String>> mutator) Invokes the Consumer mutator with the map of albums. Throws a NullPointerException if mutator is null. As mutator is a void consumer, any value returned from a lambda will be ignored, so be careful not to call pure functions like stream() expecting the returned map to replace the existing map.
clearAlbums() Removes all mappings from albums, leaving it empty.
albums() Returns an unmodifiable view of the map of albums. Changes to the map held by the builder will be reflected in this view.
  /** Returns a bimap of favourite albums by year. **/
  BiMap<Integer, String> albums();

A BiMap property called 'albums' would generate:

Method Description
putAlbums(int key, String value) Associates key with value in albums. Throws a NullPointerException if either parameter is null, or an IllegalArgumentException if value is already bound to a different key. Replaces any existing entry for key.
forcePutAlbums(int key, String value) Associates key with value in albums. Throws a NullPointerException if either parameter is null. Replaces any existing entry for both key and value. Override this method to implement constraint checks.
putAllAlbums(Map<? extends Integer, ? extends String> map) Associates all of map's keys and values in albums. Throws a NullPointerException if the map is null or contains a null key or value. Replaces any existing mapping for all keys in map. Throws an IllegalArgumentException if an attempt to put any entry fails.
removeKeyFromAlbums(int key) Removes the mapping for key from albums. Throws a NullPointerException if the parameter is null. Does nothing if the key is not present.
removeValueFromAlbums(String value) Removes the mapping for value from albums. Throws a NullPointerException if the parameter is null. Does nothing if the value is not present.
mutateAlbums(​Consumer<BiMap<Integer, String>> mutator) Invokes the Consumer mutator with the bimap of albums. Throws a NullPointerException if mutator is null. As mutator is a void consumer, any value returned from a lambda will be ignored, so be careful not to call pure functions like stream() expecting the returned map to replace the existing map.
clearAlbums() Removes all mappings from albums, leaving it empty.
albums() Returns an unmodifiable view of the bimap of albums. Changes to the bimap held by the builder will be reflected in this view.
  /** Returns a multimap of all awards by year. **/
  SetMultimap<Integer, String> awards();

A Multimap property called 'awards' would generate:

Method Description
putAwards(int key, String value) Associates key with value in awards. Throws a NullPointerException if either parameter is null.
putAllAwards(int key, Iterable<? extends String> values) Associates key with every element of values in awards. Throws a NullPointerException if either parameter, or any value, is null.
putAllAwards(Map<? extends Integer, ? extends String> map) Associates all of map's keys and values in awards. Throws a NullPointerException if the map is null or contains a null key or value. If awards is a map, an IllegalArgumentException will be thrown if any key is already present.
removeAwards(int key, String value) Removes the single pair key-value from awards. If multiple pairs match, which is removed is unspecified. Throws a NullPointerException if either parameter is null.
removeAllAwards(int key) Removes all values associated with key from awards. Throws a NullPointerException if the key is null.
mutateAwards(​Consumer<Map<Integer, String>> mutator) Invokes the Consumer mutator with the multimap of awards. Throws a NullPointerException if mutator is null. As mutator is a void consumer, any value returned from a lambda will be ignored, so be careful not to call pure functions like stream() expecting the returned multimap to replace the existing multimap.
clearAwards() Removes all mappings from awards, leaving it empty.
awards() Returns an unmodifiable view of the multimap of awards. Changes to the multimap held by the builder will be reflected in this view.

In all cases, the value type will return immutable objects from its getter.

The mutator methods are useful for invoking methods not directly exposed on the builder, like subList, or methods that take a mutable collection, like sort:

personBuilder
    // Delete the fourth and fifth descendants in the list
    .mutateDescendants(d -> d.subList(3,5).clear())
    // Sort the remaining descendants
    .mutateDescendants(Collections::sort);

Nested buildable types

  /** Returns the person responsible for this project. */
  Person owner();

FreeBuilder has special support for buildable types like protos and other FreeBuilder types. A buildable property called 'owner' would generate:

Method Description
owner(Person owner) Sets the owner. Throws a NullPointerException if provided a null.
owner(Person.Builder builder) Calls build() on builder and sets the owner to the result. Throws a NullPointerException if builder or the result of calling build() is null.
ownerBuilder() Returns a builder for the owner property. Unlike other getter methods in FreeBuilder-generated API, this object is mutable, and modifying it will modify the underlying property.
mutateOwner(Consumer<Person.Builder> mutator) Invokes the Consumer mutator with the builder for the property. Throws a NullPointerException if mutator is null. As mutator is a void consumer, any value returned from a lambda will be ignored.

The mutate method allows the buildable property to be set up or modified succinctly and readably:

Project project = new Project.Builder()
    .mutateOwner($ -> $
        .name("Phil")
        .department("HR"))
    .build();

Lists of buildable types

FreeBuilder has special support for lists of buildable types, too. It maintains a list of builders, to allow elements of the list to be built incrementally. (For better performance, if given a built instance for the list, it will lazily convert it to a Builder on demand. This may cause problems if your buildable types continue to be mutable after construction; to avoid unpredictable aliasing, we recommend disabling buildable list support, as described below.)

A list of buildable properties called 'owners' would generate:

Method Description
addOwners(Person element) Appends element to the collection of owners. Throws a NullPointerException if element is null. The element may be lazily converted to/from a Builder.
addOwners(Person.Builder builder) Appends the value built by builder to the collection of owners. Throws a NullPointerException if builder is null. Only a copy of the builder will be stored; changes made to it after this method returns will have no effect on the list. The copied builder's build() method will not be called immediately, so if this builder's state is not legal, you will not get failures until you build the final immutable object.
addOwners(Person... elements) Appends all elements to the collection of owners. Throws a NullPointerException if elements, or any of the values it holds, is null. Each element may be lazily converted to/from a Builder.
addOwners(Person.Builder... builders) Appends the values built by builders to the collection of owners. Throws a NullPointerException if builders, or any of the values it holds, is null. Only copies of the builders will be stored, and build() methods will not be called immediately.
addAllOwners(​Iterable<Person> elements)
addAllOwners(​Stream<Person> elements)
addAllOwners(​Spliterator<Person> elements)
Appends all elements to the collection of owners. Throws a NullPointerException if elements, or any of the values it holds, is null. Each element may be lazily converted to/from a Builder.
addAllBuildersOfOwners(​Iterable<Person.Builder> builders)
addAllBuildersOfOwners(​Stream<Person.Builder> builders)
addAllBuildersOfOwners(​Spliterator<Person.Builder> builders)
Appends the values built by builders to the collection of owners. Throws a NullPointerException if builders, or any of the values it holds, is null. Only copies of the builders will be stored, and build() methods will not be called immediately.
mutateOwners(​Consumer<? super List<Person.Builder>> mutator) Invokes the Consumer mutator with the list of owner builders. Throws a NullPointerException if mutator is null. As mutator is a void consumer, any value returned from a lambda will be ignored, so be careful not to call pure functions like stream() expecting the returned collection to replace the existing collection.
clearOwners() Removes all elements from the collection of owners, leaving it empty.
buildersOfOwners() Returns an unmodifiable view of the list of owner builders. Changes to the list held by the builder will be reflected in the view, and changes made to any of the returned builders will be reflected in the final list of owners.

Note that mutateOwners and buildersOfOwners are the only methods which can cause lazy convertion of an inserted value to a Builder, and then only upon accessing the element, so avoid these actions if possible to avoid unexpected performance hits.

Disabling buildable lists

You can force FreeBuilder to use vanilla list support, rather than converting elements to/from Builders under the hood, by declaring a vanilla getter in the Builder. For instance, to force owners to drop Builder support:

  class Builder extends Foo_Builder {
    @Override
    public List<Person> owners() {
      // Disable FreeBuilder's lazy conversion to/from Person.Builder by declaring
      // a non-Builder-compatible getter.
      return super.owners();
    }
  }

FreeBuilder will now generate the methods described in Collections and Maps.

Custom toString method

FreeBuilder will only generate toString, hashCode and equals methods if they are left abstract, so to customise them, just implement them. (Due to language constraints, you will need to first convert your type to an abstract class if it was previously an interface.)

abstract class Person {
  public abstract String name();
  public abstract int age();

  @Override public String toString() {
    return name() + " (" + age() + " years old)";
  }

  public static class Builder extends Person_Builder {}
}

Note that it is a compile error to leave hashCode abstract if equals is implemented, and vice versa, as FreeBuilder has no reasonable way to ensure the consistency of any implementation it might generate.

If you have a small set of properties you wish to exclude from equals or toString without losing the generated code entirely, you can annotate them @IgnoredByEquals and/or @NotInToString.

Warning: It is rarely a good idea to redefine equality on a value type, as it makes testing very hard. For instance, assertEquals in JUnit relies on equality; it will not know to check individual fields, and as a result, tests may be failing to catch bugs that, on the face of it, they looks like they should be. If you are only testing a subset of your fields for equality, consider separating your class in two, as you may have accidentally combined the key and the value of a map into a single object, and you may find your code becomes healthier after the separation. Alternatively, creating a custom Comparator will make it explicit that you are not using the natural definition of equality.

Custom conventional method names

If for any reason your types cannot use the conventional method names (build, buildPartial, clear and mergeFrom), you can force FreeBuilder to generate package protected implementations, and even select alternative fallback names if necessary, by declaring an alternative visibility and/or incompatible signature. If the default name is not available, FreeBuilder will prepend an underscore and append "Impl" (and, if necessary, a number), e.g. build becomes _buildImpl.

public interface MyType {
  class Builder extends MyType_Builder {
    public OtherDataType build() {
      // This signature is not compatible with the default build method.
      // FreeBuilder will instead declare a package-scoped _buildImpl.
      ...
    }
    public DataType buildMyType() {
      return _buildImpl();
    }
  }
}

Note that this will, unfortunately, disable FreeBuilder's enhanced support for nested builders for this type, as it needs to be able to call these methods.

Custom functional interfaces

FreeBuilder's generated map and mutate methods take UnaryOperator or Consumer functional interfaces. If you need to use a different functional interface, you can override the generated methods in your Builder and change the parameter type. FreeBuilder will spot the incompatible override and change the code it generates to match:

public interface MyType {
  String property();

  class Builder extends MyType_Builder {
    @Override public Builder mapProperty(
        com.google.common.base.Function<Integer, Integer> mapper) {
      return super.mapProperty(mapper);
    }
  }
}

Builder construction

Effective Java recommends passing required parameters in to the Builder constructor. While we follow most of the recommendations therein, we explicitly do not follow this one: while you gain compile-time verification that all parameters are set, you lose flexibility in client code, as well as opening yourself back up to the exact same subtle usage bugs as traditional constructors and factory methods. For the default FreeBuilder case, where all parameters are required, this does not scale.

If you want to follow Effective Java more faithfully in your own types, however, just create the appropriate constructor in your builder subclass:

    public Builder(String name, int age) {
      // Set all initial values in the builder constructor
      name(name);
      age(age);
    }

Implementation note: in javac, we spot these fields being set in the constructor, and do not check again at runtime.

Partials

A partial value is an implementation of the value type which does not conform to the type's state constraints. It may be missing required fields, or it may violate a cross-field constraint.

Person person = new Person.Builder()
    .name("Phil")
    .buildPartial();  // build() would throw an IllegalStateException here
System.out.println(person);  // prints: partial Person{name=Phil}
person.age();  // throws UnsupportedOperationException

As partials violate the (legitimate) expectations of your program, they must not be created in production code. (They may also affect the performance of your program, as the JVM cannot make as many optimizations.) However, when testing a component which does not rely on the full state restrictions of the value type, partials can reduce the fragility of your test suite, allowing you to add new required fields or other constraints to an existing value type without breaking swathes of test code.

To allow robust tests of modify-rebuild code, Builders created from partials (either via the static Builder.from method or the optional toBuilder() method) will override build() to instead call buildPartial().

Person anotherPerson = person.toBuilder().name("Bob").build();
System.out.println(anotherPerson);  // prints: partial Person{name=Bob}

This "infectious" behavior of partials is another reason to confine them to test code.

(Note the mergeFrom method does not behave this way; instead, it will throw an UnsupportedOperationException if given a partial.)

Jackson

To create types compatible with the Jackson JSON serialization library, use the builder property of @JsonDeserialize to point Jackson at your Builder class. For instance:

// This type can be freely converted to and from JSON with Jackson
@JsonDeserialize(builder = Address.Builder.class)
interface Address {
    String city();
    String state();

    class Builder extends Address_Builder {}
}

FreeBuilder will generate appropriate @JsonProperty annotations on the builder. (If you use Java 8 or Guava types, you may need to include the relevant Jackson extension modules, jackson-datatype-jdk8 and jackson-datatype-guava.)

GWT

To enable GWT serialization of the generated Value subclass, just add @GwtCompatible(serializable = true) to your @FreeBuilder-annotated type, and extend/implement Serializable. This will generate a CustomFieldSerializer, and ensure all necessary types are whitelisted.

Build tools and IDEs

javac

Download the latest FreeBuilder JAR and add it to the classpath (or processorpath, if you supply one) on the command line. If Guava is available, FreeBuilder will use it to generate cleaner, more interoperable implementation code (e.g returning immutable collections).

Maven

Add the FreeBuilder artifact as an optional dependency to your Maven POM:

<dependencies>
  <dependency>
    <groupId>org.inferred</groupId>
    <artifactId>freebuilder</artifactId>
    <version>[current version]</version>
    <optional>true</optional>
  </dependency>
</dependencies>

If Guava is available, FreeBuilder will use it to generate cleaner, more interoperable implementation code (e.g returning immutable collections).

Gradle

4.6+

Add the following lines to your project's build.gradle file:

dependencies {
  annotationProcessor 'org.inferred:freebuilder:<current version>'
  compileOnly 'org.inferred:freebuilder:<current version>'
}

If Guava is available, FreeBuilder will use it to generate cleaner, more interoperable implementation code (e.g returning immutable collections). You may also wish to use the org.inferred.processors plugin to correctly configure code generation in your IDE.

Pre-4.6

Add the following lines to your project's build.gradle file:

plugins {
  id 'org.inferred.processors' version '1.2.10'
}

dependencies {
  processor 'org.inferred:freebuilder:<current version>'
}

This uses the org.inferred.processors plugin to correctly configure code generation in your IDE. Alternatively, you can drop the plugin and replace processor with compileOnly, or compile if you are on Gradle 2.11 or earlier—you will lose IDE integration—or use your own favourite Gradle annotation processor plugin.

If Guava is available, FreeBuilder will use it to generate cleaner, more interoperable implementation code (e.g returning immutable collections).

Eclipse

Condensed from Eclipse Indigo's documentation.

Download the latest FreeBuilder JAR and add it to your project. Select it, right-click and choose Build path > Add to Build path.

In your projects properties dialog, go to Java Compiler > Annotation Processing and ensure Enable annotation processing is checked. Next, go to Java Compiler > Annotation Processing > Factory Path, select Add JARs, and select the FreeBuilder JAR.

IntelliJ

Condensed from the IntelliJ 14.0.3 documentation and Auto Issue #106.

Download the latest FreeBuilder JAR, add it to your project, right-click it and select Use as Project Library.

In your Settings, go to Build, Execution, Deployment > Compiler > Annotation Processors and ensure Enable annotation processing is selected, and Store generated sources relative to is set to Module content root. (If you have specified a processor path, ensure you add the new JAR to it. Similarly, if you choose to specify processors explicitly, add org.inferred.freebuilder.processor.Processor to the list.)

Run Build > Rebuild Project, then right-click the new generated folder (this may have a different name if you have changed the Production sources directory setting) and select Mark Directory As > Generated Sources Root.

If you are using Maven to build your project within IntelliJ, you can alternatively configure the POM in a way that enables IntelliJ to automatically detect annotation processors when the POM is reloaded:

<plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
        <annotationProcessors>
            <annotationProcessor>org.inferred.freebuilder.processor.Processor</annotationProcessor>
        </annotationProcessors>
        <annotationProcessorPaths>
            <path>
                <groupId>org.inferred</groupId>
                <artifactId>freebuilder</artifactId>
                <version>2.6.2</version>
            </path>
        </annotationProcessorPaths>
    </configuration>
</plugin>

Click Reload All Maven Projects in the Maven tool window to force a reload if you have not configured IntelliJ for automatic reloading. The Annotation processor should appear in Build, Execution, Deployment > Compiler > Annotation Processors automatically, with all needed settings applied.

In case you need to use more than one annotation processor, you'll find an example at #435 .

Release notes

2.3—From method testability

FreeBuilder 2.3 now allows partials to be passed to the static Builder.from method. Previously this would have thrown an UnsupportedOperationException if any field was unset; now, as with the optional toBuilder method, a Builder subclass will be returned that redirects build to buildPartial. This allows unit tests to be written that won't break if new constraints or required fields are later added to the datatype. You can restore the old behaviour by overriding the from method to delegate to mergeFrom.

Note that use of partials outside of tests is considered undefined behaviour by FreeBuilder, as documented here and on the buildPartial method. Incomplete values should always be represented by Builder instances, not partials.

2.2—Primitive optional types

FreeBuilder 2.2 extends its optional value API customization to OptionalInt, OptionalLong and OptionalDouble. This is, for mutate methods, a non-binary-backwards-compatible change. If you have existing properties that you do not want this to affect, you can force FreeBuilder to adhere to your existing API with custom functional interfaces. To do this, use your IDE to override all mapper methods taking an OptionalInt, OptionalLong or OptionalDouble (the implementations can just delegate to super). One of these will have been generated for each primitive optional property. Once all such methods are overridden, upgrading to 2.2 should now leave your APIs unaltered.

This change also alters the default behavior of the Optional-accepting setter method to delegate to a new primitive-accepting setter method, to allow constraints to be added through overriding. If you have previously added such a primitive-accepting setter method that delegates to the Optional-accepting setter method, this will of course now result in a stack overflow at runtime. FreeBuilder will attempt to flag this as a compiler error, but please double-check your builders when upgrading.

2.1—Lists of buildable types

FreeBuilder 2.1 adds more extensive API customization for lists of buildable types, storing Builder instances internally until build is called, cascading buildPartial automatically, and adding overloads accepting Builder instances.

This is a behavioural and, for the get and mutate methods, a non-binary-backwards-compatible change. If you have existing properties that you do not want this to affect, see disabling buildable lists for instructions on restoring the 2.0 behaviour on a case-by-case basis.

Upgrading from v1

There are three API-breaking changes between v1 and v2 of FreeBuilder:

  • Mapper methods use primitive, not boxed, functional interfaces for int, long and double properties.

    This will likely break binary backwards compatibility for any library using FreeBuilder to generate its builders. We apologise profusely for the hassle this causes. If you simply cannot break your clients, but want to upgrade to v2, you can force FreeBuilder to adhere to your existing API with custom functional interfaces. To do this, use your IDE to override all mapper methods taking a UnaryOperator<Integer>, UnaryOperator<Long> or UnaryOperator<Double> (the implementations can just delegate to super). One of these will have been generated for each int, long and double property. Once all such methods are overridden, upgrading to v2 should now leave your APIs unaltered.

  • No more support for Java 6/7

    If you are still on Java 6/7, please continue to use the v1 releases of FreeBuilder.

  • No longer ships with javax.annotation.Nullable

    FreeBuilder treats any annotation named Nullable the same way, so you can either explicitly compile against the old annotation by including the JSR-305 jar, or use a more modern alternative, like org.jetbrains.annotations. (Or use optionals! See Converting from @Nullable for advice.)

Troubleshooting

Troubleshooting javac

If you make a mistake in your code (e.g. giving your value type a private constructor), FreeBuilder is designed to output a Builder superclass anyway, with as much of the interface intact as possible, so the only errors you see are the ones output by the annotation processor.

Unfortunately, javac has a broken design: if any annotation processor outputs any error whatsoever, all generated code is discarded, and all references to that generated code are flagged as broken references. Since your Builder API is generated, that means every usage of a FreeBuilder builder becomes an error. This deluge of false errors swamps the actual error you need to find and fix. (See also the related issue.)

If you find yourself in this situation, search the output of javac for the string "@FreeBuilder type"; nearly all errors include this in their text.

Troubleshooting Eclipse

Eclipse manages, somehow, to be worse than javac here. It will never output annotation processor errors unless there is another error of some kind; and, even then, only after an incremental, not clean, compile. In practice, most mistakes are made while editing the FreeBuilder type, which means the incremental compiler will flag the errors. If you find a generated superclass appears to be missing after a clean compile, however, try touching the relevant file to trigger the incremental compiler. (Or run javac.)

Online resouces

Alternatives

Immutables vs FreeBuilder

Where is Immutables better than FreeBuilder?

Immutables provides many of the same features as FreeBuilder, plus a whole host more. Some are optional ways to potentially enhance performance, like derived and lazy attributes, singleton and interned instances, and hash code precomputation. Some are API-changing: strict builders provide a compile-time guarantee that all fields are set (but limit the use of builders as a consequence), while copy methods provide a concise way to clone a value with a single field changed (but require you to reference the generated ImmutableFoo type, not Foo). It provides advanced binary serialization options and GSON support. It lets you easily customize the conventional method prefixes. As Immutables is an active project, this list is likely incomplete.

Where is FreeBuilder better than Immutables?

FreeBuilder provides some features that are missing from, or not the default in, Immutables:

The first three points are increasingly useful as your interfaces grow in complexity and usage. Partials greatly reduce the fragility of your tests by only setting the values the code being tested actually uses. Your interfaces are liable accumulate more constraints, like new required fields, or cross-field constraints, and while these will be vitally important across the application as a whole, they create a big maintenance burden when they break unit tests for existing code that does not rely on those constraints. Builder getter, mapper and mutation methods and nested builders empower the modify-rebuild pattern, where code changes a small part of an object without affecting the remainder.

The last two points arise because Immutables is type-generating: you write your value type as a prototype, but you always use the generated class. This type is final, meaning you can't proxy it, which unfortunately breaks tools like Mockito and its wonderful smart nulls. The generated builder is hard to customize as you cannot override methods, explaining why Immutables has so many annotations: for instance, the @Value.Check annotation is unnecessary in FreeBuilder, as you can simply override the setters or build method to perform field or cross-field validation.

1: But note that you can write a Builder subclass in your Foo type, enabling FreeBuilder-style override-based customization, though that is not the default approach recommended in the guide, and you will break all your callers if you change between the two.

Immutables is a very active project, frequently adding new features, and future releases may address some or all of these deficiencies.

AutoValue vs FreeBuilder

Why is FreeBuilder better than AutoValue?

It’s not! AutoValue provides an implementing class with a package-visible constructor, so you can easily implement the Factory pattern. If you’re writing an immutable type that needs a small number of values to create (Effective Java suggests at most three), and is not likely to require more in future, use the Factory pattern.

How about if you want a builder? AutoValue.Builder lets you explicitly specify a minimal Builder interface that will then be implemented by generated code, while FreeBuilder provides a generated builder API. AutoValue.Builder is better if you must have a minimal API—for instance, in an Android project, where every method is expensive—or strongly prefer a visible-in-source API at the expense of many useful methods. Otherwise, consider using FreeBuilder to implement the Builder pattern.

I used AutoValue, but now have more than three properties! How do I migrate to FreeBuilder?

  1. Change your annotation to @FreeBuilder.
  2. Rewrite your factory method(s) to use the builder API.
  3. Inline your factory method(s) with a refactoring tool (e.g. Eclipse).

You can always skip step 3 and have both factory and builder methods, if that seems cleaner!

Can I use both AutoValue and FreeBuilder?

Not really. You can certainly use both annotations, but you will end up with two different implementing classes that never compare equal, even if they have the same values.

Proto vs FreeBuilder

Protocol buffers have provided builders for ages. Why should I use FreeBuilder?

Protocol buffers are cross-platform, backwards- and forwards-compatible, and have a very efficient wire format. Unfortunately, they do not support custom validation logic; nor can you use appropriate Java domain types, such as Instant or Range. Generally, it will be clear which one is appropriate for your use-case.

Wait, why "free"?

  • Free as in beer: you don't pay the cost of writing or maintaining the builder code.
  • Free as in flexible: you should always be able to customize the builder where the defaults don't work for you.
  • Free as in liberty: you can always drop FreeBuilder and walk away with the code it generated for you.

License

Copyright 2014-7 Google Inc., 2018 Inferred.Org. All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.