DZone
Java Zone
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
  • Refcardz
  • Trend Reports
  • Webinars
  • Zones
  • |
    • Agile
    • AI
    • Big Data
    • Cloud
    • Database
    • DevOps
    • Integration
    • IoT
    • Java
    • Microservices
    • Open Source
    • Performance
    • Security
    • Web Dev
DZone > Java Zone > 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.

Daniel Buza user avatar by
Daniel Buza
·
Mar. 03, 22 · Java Zone · Tutorial
Like (9)
Save
Tweet
6.55K 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.

Popular on DZone

  • Anypoint CLI Commands in MuleSoft
  • Kotlin vs Java: Which One Is the Best?
  • No Code Expectations vs Reality
  • The Developer's Guide to SaaS Compliance

Comments

Java Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

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

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends:

DZone.com is powered by 

AnswerHub logo