portrait

End of Line blog

Thoughts on software development, by Adam Ruka

Jilt 1.9 (and others) released!

Jilt, my Java library for generating Builder pattern classes, had not one, not two, but four releases since I last wrote about it in May of this year.

The changes included in those four releases are:

Copy JavaDocs to setter methods

Before version 1.9, Jilt never added JavaDocs to any of the classes (or interfaces) it generated.

However, that’s not ideal if the property in the class being built already had some JavaDocs, since the information contained in those JavaDocs will be missing from the generated Builder code.

For that reason, version 1.9 changes that behavior, and copies any JavaDoc present on the property to the setter method of the generated Builder for that property.

The logic of the copying is as follows:

Thanks to markvr for requesting this feature.

Allow placing @Builder on abstract methods

In Jilt version 1.5, we added support for placing @Builder on private constructors or static methods, which results in generating an abstract Builder that you need to “manually” extend in the built class (so that the private constructor or static method can be accessed).

However, in some cases, it’s useful to trigger this behavior on a purely abstract method, without a method body. The main usecase I’ve seen for placing @Builder on a method without a body is a generic method that returns a subclass of a parent class that defines the set of properties for their subclasses, something like:

import org.jilt.Builder;

abstract class JiltContext {
    @Builder(className = "SomeBaseClassBuilder", packageName = "my.package")
    abstract <T extends SomeBaseClass> T produceInstance(
            String prop1, int prop2, boolean prop3, ...);
}

class Subclass1 extends SomeBaseClass {
    static class Subclass1Builder extends SomeBaseClassBuilder<Subclass1> {
        @Override
        public Subclass1 build() {
            return new Subclass1(this.prop1, this.prop2, this.prop3, ...);
        }
    }

    // ...
}

class Subclass2 extends SomeBaseClass {
    static class Subclass2Builder extends SomeBaseClassBuilder<Subclass2> {
        @Override
        public Subclass2 build() {
            return new Subclass2(this.prop1, this.prop2, this.prop3, ...);
        }
    }

    // ...
}

Thanks to Diego Pedregal for requesting this feature.

Handling checked exceptions

When placing @Builder on constructors or static methods, it might happen that that constructor or method declares it throws some checked exception(s). In those cases, generating a build() method in the naïve way would result in non-compiling code, since the checked exceptions need either to be handled, or re-declared as being thrown.

Jilt version 1.8.4 fixes this issue, and uses the second solution mentioned above: so now, when @Builder is placed on a constructor or static method that declares throwing checked exceptions, the generated build() method will re-declare it throws the same checked exceptions as that constructor or static method does.

For example:

import java.io.IOException;
import org.jilt.Builder;

public class A {
    @Builder
    public A() throws IOException {
        // ...
    }
}

Will generate:

import java.io.IOException;

public class ABuilder {
    // ...

    public A build() throws IOException {
        return new A();
    }
}

Thanks to Oliver Kopp for reporting this issue.

Lombok getters and toBuilder

When you set the toBuilder attribute of the @Builder annotation, the Builder instance returned from that method must have each property initialized from the provided instance of the target class. However, it’s sometimes tricky to determine how to extract the value of a given property from the target class, since properties can be accessed in multiple ways: through classic getters, through record-style getters, or through a field directly if it’s public (or possibly package-private, when the Builder is in the same package as the target class).

Jilt tries to be clever, and determine which way of access should be used for each property. However, there is a special case that is particularly tricky to figure out, and that is combining Jilt with Lombok’s getter generation feature. Since both Lombok and Jilt are annotation processors, and the order of execution of annotation processors is unspecified, it can happen that Jilt executes before Lombok, and so, the getter for a given property might not have been generated yet at the time Jilt executes. This is made even more complicated by the fact that when using the @Value annotation, it’s typical to leave the fields of the class package-private, and then Lombok changes them to private (and final) after it runs.

Jilt version 1.8.3 fixes this edge case, so that using @Getter (and @Value) in combination with toBuilder correctly uses the property’s getter to initialize the returned Builder instance, regardless of the order in which Jilt and Lombok run in.

Thanks to singloon for reporting this issue.

Summary

So, these are all the changes in Jilt versions 1.8.2, 1.8.3, 1.8.4, and 1.9.

Let me know if you have feedback on any of these changes. For instance, regarding the JavaDoc copying, should Jilt add even more JavaDocs to the generated code? For example, to the top-most level of the generated Builder classes (and interfaces)?

Leave a comment below!