Efficient Java Development: Exploring Builders and Helidon 4

Jeff Trent
Helidon
Published in
5 min readFeb 16, 2023

--

This article is applicable for Helidon version 4.0.0–Alpha2 or less. There are significant changes in newer versions. Expect new articles covering this material to be published.

Anyone following Helidon is already aware that the headline news for Helidon 4 is the general availability of Nima based on Project Loom. While Nima deserves the main spotlight, developers will want to take notice of other goodies in the 4.0 box. One of the additional features is the Helidon Builder. This article will provide an overview of this new feature/tool.

Why should you care…

If you have ever written a fluent builder in Java, then you should read on — especially if you are using Project Lombok for your builder solution today. Fundamentally, Helidon Builder is an annotation processor that code-generates fluent builder support just by adding @Builder to your declared Java types. While Helidon’s Builder was heavily influenced by Lombok, the implementation and capabilities for builders are quite different between Helidon and Lombok. In both cases, the declarative style of using annotations helps you by reducing the amount of boilerplate code you need to write.

From the casual user point of view both use a @Builder annotation, but Helidon’s implementation adds the following:

  1. Allows for @Builder to be placed on an interface, abstract class, or even on annotation types.
  2. Generates source code instead of bytecode. This provides enhanced readability to your application (e.g., debugging, javadoc, etc).
  3. Simplified usage patterns with intelligent defaults. All generated sources will anticipate hierarchical builder targets (i.e., what Lombok calls SuperBuilder). And it will automatically implement toString(), equals(), and hashCode() unless the user defines these methods directly. Copy and cloning are supported as well.

Beyond the basics…

Going beyond the basics, Helidon Builder offers a rich set of capabilities, including:

  1. Extensibility. Helidon Builder is designed from the onset to be extensible on a few different levels. A future article will be written to showcase how Helidon Builder is extensible in order to integrate directly to the Helidon Configuration subsystem. An advanced user can customize and extend Helidon’s Builder Generator to do “their own thing”.
  2. Visitor Pattern. Helidon Builder allows every attribute in the @Builder-annotated target type to be visited for your general use.
  3. Validation. Having the visitor pattern available allows some convenient mix-in behaviors, one of which is checking for required attributes that must be set as part of the build() method of your target. By default, all non-Optional attributes are considered to be required attributes. Helidon Builder provides validation out of the box in the generated implementation classes.
  4. Interception. You can provide other custom semantics during the build() method (similar to validation) for other types of interception or decoration on your target builders. This is beyond the scope of this article, but examples can be found in the git repository mentioned below.
  5. Packaging and usage choice. Helidon Builder can be used as a standalone module in your build today as long as you are using Java 11 or later. When used in a standalone manner in your build, you can use the requireLibraryDependencies() attribute to toggle between supporting libraries (like validation) to be either generated directly into your application, or otherwise have it supplied by Helidon libraries. This gives you an opportunity to use Helidon’s tooling in isolation in your project build, even if you don’t plan to use Helidon in any other part of your application runtime!

Another point that we should mention are the Lombok risks you will otherwise avoid by using Helidon Builder. We encourage you to research Lombok for yourself and choose the best fluent builder for your team.

Getting Started

Pre-requisites:

  • An IDE
  • Java 11+
  • Maven 3.8.3+

One-time project setup:

  1. Create a new Java Project w/ Maven support in your favorite IDE. I used JDK 19 for this example.
  2. I called my project TestBuilder.
  3. In the pom.xml, add this dependency and annotation processor path configuration:
<dependency>
<groupId>io.helidon.builder</groupId>
<artifactId>helidon-builder</artifactId>
<version>4.0.0-ALPHA4</version>
</dependency>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.helidon.builder</groupId>
<artifactId>helidon-builder-processor</artifactId>
<version>4.0.0-ALPHA4</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>

Coding and Testing:

Create your test subject classes. Here you’ll create a contrived example with two interface types: NameAndDescription and NameAndDescriptionAndComments that extends from the first, each having the Builder annotation:

@Builder(requireLibraryDependencies = false)
public interface NameAndDescription {
String name();
Optional<String> description();
}
@Builder(requireLibraryDependencies = false)
public interface NameAndDescriptionAndComments extends NameAndDescription {
@Singular
List<String> comments();
}

Notice how these two classes above are using the “requireLibraryDependencies=false” — this allows you to use Maven’s provided dependency for the Helidon Builder libraries, making it possible to code generate the supporting validation directly into your builder- generated types, thereby not requiring any Helidon library support in your runtime.

Build your project.

> maven clean compile

Write your main():

public class Main {
public static void main(String[] args) {
NameAndDescriptionAndComments target =
DefaultNameAndDescriptionAndComments.builder()
.name("world")
.addComment("hello")
.build();
System.out.println("Hello " + target);
}
}

Output:

Hello NameAndDescriptionAndComments(name=world, description=Optional.empty,
comments=[hello])

Observations:

  1. The Helidon Builder’s annotation processor generates source code whenever it processes an annotation that it recognizes — in this case the @Builder annotation. You must apply the annotation processors in your build in order to generate the supporting sources (see next point) before they are consumed in the main() from this example. This is understandably a bit of an inconvenience when compared to Lombok having direct IDE integrations like IntelliJ — the Helidon team will be researching ways to improve the end-to-end experience for IDE developers in time.
  2. See the generated sources in the target directory — no magic.

Note that the generated type names are configurable — see the javadoc for details.

What’s next?

There are many more examples of usage you can review in the git repository. A good next step is to review more of the technical notes in the top-level readme.

Builder is just the first example of new compile-time tooling that Helidon will be offering in 4.x. As mentioned above, this tooling has been extended into the Helidon Configuration subsystem. Helidon 4 will be offering even more powerful tooling around compile-time processing for your application in the near future. Visit helidon.io and stay tuned for other articles on new features that will be coming out soon.

--

--