DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workkloads.

Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • How to Convert XLS to XLSX in Java
  • Recurrent Workflows With Cloud Native Dapr Jobs
  • Java Virtual Threads and Scaling
  • Java’s Next Act: Native Speed for a Cloud-Native World

Trending

  • Internal Developer Portals: Modern DevOps's Missing Piece
  • Beyond Linguistics: Real-Time Domain Event Mapping with WebSocket and Spring Boot
  • Streamlining Event Data in Event-Driven Ansible
  • How to Practice TDD With Kotlin
  1. DZone
  2. Coding
  3. Java
  4. Method Builder With Lombok @Builder

Method Builder With Lombok @Builder

In this tutorial, we are going to explore the various possibilities of generating method builders with Lombok's @Builder annotation to improve usability.

By 
Daniel Buza user avatar
Daniel Buza
·
Mar. 03, 22 · Tutorial
Likes (10)
Comment
Save
Tweet
Share
12.8K Views

Join the DZone community and get the full member experience.

Join For Free

Overview

In this tutorial, we are going to explore the possibilities of generating method builders with Lombok's @Builder annotation. The aim is to improve usability by providing a flexible way of calling a given method even if it has a lot of parameters.

@Builder on Simple Methods

How to provide a flexible usage for methods is a general topic that might take multiple inputs. Take a look at the following example:

Java
 
void method(@NotNull String firstParam, @NotNull String secondParam, 
            String thirdParam, String fourthParam, 
            Long fifthParam, @NotNull Object sixthParam) {
    ...            
}

If the parameters that are not marked as not null are optional, the method might accept all the following calls: 

Java
 
method("A", "B", null, null, null, new Object());
method("A", "B", "C", null, 2L, "D");
method("A", "B", null, null, 3L, this);
...

This example already shows some problematic points such as:

  • The caller should know which parameter is which (e.g. in order to change the first call to provide a Long too, the caller must know Long is expected to be the fifth parameter).
  • Inputs must be set in a given order.
  • Names of the input parameters are not transparent.

In the meantime, from the provider's perspective, providing methods with fewer parameters would mean massive overloading of the method name, such as:

Java
 
void method(@NotNull String firstParam, @NotNull String secondParam, @NotNull Object sixthParam);
void method(@NotNull String firstParam, @NotNull String secondParam, String thirdParam, @NotNull Object sixthParam);
void method(@NotNull String firstParam, @NotNull String secondParam, String thirdParam, String fourthParam, @NotNull Object sixthParam);
void method(@NotNull String firstParam, @NotNull String secondParam, String thirdParam, String fourthParam, Long fifthParam, @NotNull Object sixthParam);
...

To achieve better usability and avoid boilerplate code, method builders could be introduced. Project Lombok already provides an annotation in order to make the use of builders simple. The example method above could be annotated in the following way: 

Java
 
@Builder(builderMethodName = "methodBuilder", buildMethodName = "call")
void method(@NotNull String firstParam, @NotNull String secondParam, 
            String thirdParam, String fourthParam, 
            Long fifthParam, @NotNull Object sixthParam) {
    ...            
}

Thus, calling the method would look like: 

Java
 
methodBuilder()
        .firstParam("A")
        .secondParam("B")
        .sixthParam(new Object())
        .call();

methodBuilder()
        .firstParam("A")
        .secondParam("B")
        .thirdParam("C")
        .fifthParam(2L)
        .sixthParam("D")
        .call();

methodBuilder()
        .firstParam("A")
        .secondParam("B")
        .fifthParam(3L)
        .sixthParam(this)
        .call();

In this way, the method call is much easier to understand and change later. Some remarks:

  • By default, a builder method (method to obtain a builder instance) on a static method is going to be itself a static method.
  • By default, the call() method will have the same throw signature as the original method.

Default Values

In many cases, it can be really helpful to define default values for the input parameters. Unlike some other languages, Java does not have a language element to support this need. Therefore, in most cases, this is reached via method overloading, having structures like: 

Java
 
method() { method("Hello"); }
method(String a) { method(a, "builder"); }
method(String a, String b) { method(a, b, "world!"); }
method(String a, String b, String c) { ... acutal logic here ... }

While using Lombok builders, a builder class is going to be generated within the target class. This builder class:

  • has the same number of properties and arguments as the method.
  • has setters for its arguments.

It is also possible to define the class manually, which gives the possibility to define default values for the parameters. In this way, the method above would look like this:

Java
 
@Builder(builderMethodName = "methodBuilder", buildMethodName = "call", builderClassName = "MethodBuilder")
method(String a, String b, String c) {
  ... acutal logic here ...
}

private class MethodBuilder {
    private String a = "Hello";
    private String b = "builder";
    private String c = "world!";
}

With this addition, if the caller does not specify a parameter, the default value defined in the builder class is going to be used.

Note: In this case, we do not have to declare all the input parameters of the method in the class. If an input parameter of the method is not present in the class, Lombok will generate an additional property accordingly.

Typed Methods

It is a common need to define the return type of a given method through one of the inputs, such as: 

Java
 
public <T> T read(byte[] content, Class<T> type) {...}

In this case, the builder class will also be a typed class, but the builder method will create an instance without a bounded type. Take a look at the following example:

Java
 
@Builder(builderMethodName = "methodBuilder", buildMethodName = "call", builderClassName = "MethodBuilder")
public <T> T read(byte[] content, Class<T> type) {...}

In this case, the methodBuilder method is going to create an instance of MethodBuilder without bounded type parameters. This leads to the fact that the following code will not compile (as would be required by Class<T>, and is provided by Class<String>):

Java
 
methodBuilder()
    .content(new byte[]{})
    .type(String.class)
    .call();

This can be resolved by casting the input of type and use it as: 

Java
 
methodBuilder()
    .content(new byte[]{})
    .type((Class)String.class)
    .call();

It will compile, but there is another aspect to mention: the return type of call method is not going to be String in this case, but still unbound T. Therefore, the client has to cast the return type like this: 

Java
 
String result = (String)methodBuilder()
    .content(new byte[]{})
    .type((Class)String.class)
    .call();

This solution works, but it also requires the caller to cast both the input and the result. As the original motivation is to provide a caller-friendly way to invoke the methods, it is recommended to consider one of the two following options. 

Override the Builder Method

As stated above, the root of the problem is that the builder method creates an instance of the builder class without a specific type parameter. It is still possible to define the builder method in the class and create an instance of the builder class with the desired type:

Java
 
@Builder(builderMethodName = "methodBuilder", buildMethodName = "call", builderClassName = "MethodBuilder")
public <T extends Collection> T read(final byte[] content, final Class<T> type) {...}

public <T extends Collection> MethodBuilder<T> methodBuilder(final Class<T> type) {
    return new MethodBuilder<T>().type(type);
}

public class MethodBuilder<T extends Collection> {
    private Class<T> type;
    public MethodBuilder<T> type(Class<T> type) { this.type = type; return this; }
    public T call() { return read(content, type); }
}

In this case, the caller does not have to cast anytime and the call looks like this: 

Java
 
List result = methodBuilder(List.class)
    .content(new byte[]{})
    .call();

Casting in the Setter

It is also possible to cast the builder instance within the setter of the type parameter: 

Java
 
@Builder(builderMethodName = "methodBuilder", buildMethodName = "call", builderClassName = "MethodBuilder")
public <T extends Collection> T read(final byte[] content, final Class<T> type) {...}

public class MethodBuilder<T extends Collection> {
    private Class<T> type;
    public <L extends Collection> MethodBuilder<L> type(final Class<L> type) { 
        this.type = (Class)type; 
        return (MethodBuilder<L>) this;
    }
    public T call() { return read(content, type); }
}

Using this way, there is no need to define the builder method manually, and from the caller's perspective, the type parameter is handed over just as any other parameter.

Conclusion

Using @Builder on methods can bring the following advantages:

  • More flexibility on the caller's side
  • Default input values without method overloads
  • Improved readability of the method calls
  • Allowing similar calls via the same builder instance

In the meantime, is also worth mentioning that in some cases, using method builders can bring unnecessary complexity on the provider's side. Various examples for method builders are available on GitHub.

Java (programming language)

Opinions expressed by DZone contributors are their own.

Related

  • How to Convert XLS to XLSX in Java
  • Recurrent Workflows With Cloud Native Dapr Jobs
  • Java Virtual Threads and Scaling
  • Java’s Next Act: Native Speed for a Cloud-Native World

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!