DEV Community

Guillaume Laforge
Guillaume Laforge

Posted on • Originally published at glaforge.dev on

Functional builders in Java with Jilt

A few months ago, I shared an article about what I called Javafunctional builders, inspired by an equivalent pattern found in Go. The main idea was to have builders that looked like this example:

LanguageModel languageModel = new LanguageModel( name("cool-model"), project("my-project"), temperature(0.5), description("This is a generative model") ); 
Enter fullscreen mode Exit fullscreen mode

Compared to the more tranditional builder approach:

  • You’re using the new keyword again to construct instances.
  • There’s no more build() method, which felt a bit verbose.

Compared to using constructors with tons of parameters:

  • You have methods like in traditional builders, that say what each parameter is about (name(), temperature()…) a bit similar to named parameters in some programming languages.

The approach I followed was to take advantage of lambda functions under the hood:

public static ModelOption temperature(Float temperature) { return model -> model.temperature = temperature; } 
Enter fullscreen mode Exit fullscreen mode

However, there were a few downsides:

  • Of course, it’s not very conventional! So it can be a bit disturbing for people used to classical builders.
  • I didn’t make the distinction between required and optional parameters (they were all optional!)
  • The internal fields were not final, and I felt they should be.

Discovering Jilt

When searching on this topic, I found Adam Ruka’s great annotation processor library:Jilt.

One of the really cool features of Jilt is its staged builder concept, which makes builders very type-safe, and forces you to call all the required property methods by chaining them. I found this approach very elegant.

Adam heard about my functional builder approach, and decided to implement this new style of builder in Jilt. There are a few differences with my implementation, but it palliates some of the downsides I mentioned.

Let’s have a look at what functional builders looks like from a usage standpoint:

LanguageModel languageModel = languageModel( name("cool-model"), project("my-project"), temperature(0.5), description("This is a generative model") ); 
Enter fullscreen mode Exit fullscreen mode

Compared to my approach, you’re not using constructors (as annotation processors can’t change existing classes), so you have to use a static method instead. But otherwise, inside that method call, you have the named-parameter-like methods you’re used to use in builders.

Here, name(), project() and temperature() are mandatory, and you’d get a compilation error if you forgot one of them. But description() is optional and can be ommitted.

Let’s now look at the implementation:

import org.jilt.Builder; import org.jilt.BuilderStyle; import org.jilt.Opt; import static jilt.testing.LanguageModelBuilder.*; import static jilt.testing.LanguageModelBuilder.Optional.description; //... LanguageModel languageModel = languageModel( name("cool-model"), project("my-project"), temperature(0.5), description("This is a generative model") ); //... @Builder(style = BuilderStyle.FUNCTIONAL) public record LanguageModel( String name, String project, Double temperature, @Opt String description ) {} 
Enter fullscreen mode Exit fullscreen mode

I used a Java record but it could be a good old POJO. You must annotate that class with the @Builder annotation. The style parameter specifies that you want to use a functional builder. Notice the use of the @Opt annotation to say that a parameter is not required.

Derived instance creation

Let me close this article with another neat trick offered by Jilt, which is how to build other instances from existing ones:

@Builder(style = BuilderStyle.FUNCTIONAL, toBuilder = "derive") public record LanguageModel(...) {} //... LanguageModel derivedModel = derive(languageModel, name("new-name")); 
Enter fullscreen mode Exit fullscreen mode

By adding the toBuilder = "derive" parameter to the annotation, you get the ability to create new instances similar to the original one, but you can change both required and optional parameters, to derive a new instance.

Time to try Jilt!

You can try functional builders in Jilt 1.6 which was just released a few days ago!

Top comments (0)