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
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
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
Partner Zones AWS Cloud
by AWS Developer Relations
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
Partner Zones
AWS Cloud
by AWS Developer Relations
  1. DZone
  2. Data Engineering
  3. Databases
  4. Making the Java Fluent API More Flexible

Making the Java Fluent API More Flexible

Want to learn more about improving the flexibility of the Java fluent API? Check out this tutorial to learn how in Java, Groovy, and Kotlin.

Konstantin Plotnikov user avatar by
Konstantin Plotnikov
CORE ·
Oct. 15, 18 · Tutorial
Like (6)
Save
Tweet
Share
24.62K Views

Join the DZone community and get the full member experience.

Join For Free

With the appearance of Java 8 lambdas, and even before that, a lot of APIs appeared that used the fluent API pattern (that started as a pattern explained in this post from Martin Fowler). To name a few of these API instances in Java includes:

  • Java Stream API
  • Java Time API
  • JPA Query Builder
  • Lombok builders

There are a lot of articles about it on DZone and other sources:

  • Design a Fluent API in Java
  • API Design with Java 8
  • Nested Fluent Builders with Java 8
  • The Java Fluent API Designer Crash Course

While this pattern is quite fine, there are two cases when it becomes less fluent: conditional stages and stage reuse.

Java Case

Let's investigate the problem with using fluent builders. To stay on the bleeding edge, let’s use the experimental Lombok’s SuperBuilder annotation processor, which appeared in the version 1.18.2, as this builder fits the scenario described here. Note that this code generator is not supported by IntelliJ IDEA yet, so you will likely see a lot of red code if you open the project there, but the code works and could be compiled on Maven.

To start, let’s define a domain model.

@Data
public class Session {
    private final String sessionId;
    private final String user;
}

@SuperBuilder
public abstract class Base {
    @Getter
    @Builder.Default
    private long created = System.currentTimeMillis();
    @Getter
    private String createdBy;
    @Getter
    private String createdSessionId;
}

@SuperBuilder
public class Person extends Base {
    @Getter
    private String name;
    @Getter
    private int age;
    @Getter
    @Singular
    private Set<String> occupations;
}


Now, let’s build the object in the normal case:

    public Person get(Session session) {
        return Person.builder()
                .name("test")
                .createdBy(session.getUser())
                .createdSessionId(session.getSessionId())
                .age(42)
                .occupation("test")
                .build();
    }


In the example above, there might be a case when the session object is null in case of anonymous users. The naïve handling of this case might look like the following:

    public Person getDefaultValue(Session session) {
        return Person.builder()
                .name("test")
                .createdBy(session == null ? null : session.getUser())
                .createdSessionId(session == null ? null : session.getSessionId())
                .age(42)
                .occupation("test")
                .build();
    }


However, in this example, we create additional coupling between objects. The code now depends on knowledge of what is the default value for security fields. Currently, it is the null, but in the future, the default might change to the string “anonymous”  and our code will break. Thus, it would be better to skip this phase completely, as shown below:

    public Person getOptional(Session session) {
        Person.PersonBuilder<?, ?> b = Person.builder()
                .name("test");
        if (session != null) {
            b = b
                    .createdBy(session.getUser())
                    .createdSessionId(session.getSessionId());
        }
        return b
                .age(42)
                .occupation("test")
                .build();
    }


Suddenly, our code is much less beautiful and fluent. Also, as we work with fields in the base class and we need to use the same check elsewhere, we could abstract it to the utility method:

public class SecurityUtil {
    @SuppressWarnings("unchecked")
    public static <B extends Base.BaseBuilder<?, ?>> B supplySecurityInfo(B builder, Session session) {
        return session == null ? builder : (B) builder
                .createdBy(session.getUser())
                .createdSessionId(session.getSessionId());
    }
}


And, to reuse it:

    public Person getReuseWrap(Session session) {
        return SecurityUtil.supplySecurityInfo(Person.builder()
                        .name("test")
                , session)
                .age(42)
                .occupation("test")
                .build();
    }


Or, like the following:

    public Person getReuseVariable(Session session) {
        Person.PersonBuilder<?, ?> b = Person.builder()
                .name("test");
        b = SecurityUtil.supplySecurityInfo(b, session);
        return b.age(42)
                .occupation("test")
                .build();
    }


The code is a bit better, but the rhythm of invocations is still broken in both cases, and it does not feel fluent. We have only one reused stage, but consider the case when there are several of them. The code could grow quite hairy.

Introducing the Process Method

To solve these problems, we could have a simple <T> T process(Function<Builder,T>) method, that just invokes a function passed as an argument with builder reference. In this example, we extend the Lombok-generated builder, as shown below:

public class PersonBuilderExt extends Person.PersonBuilder<Person, PersonBuilderExt> {
    public <T> T process(Function<PersonBuilderExt, T> function) {
        return function.apply(this);
    }

    public PersonBuilderExt() {
    }

    protected PersonBuilderExt self() {
        return this;
    }

    public Person build() {
        return new Person(this);
    }
}


Then, we could implement the two scenarios above much more fluently for optional stages.

    public static PersonBuilderExt personBuilderExt() {return new PersonBuilderExt();}    
    public Person getOptionalProcess(Session session) {
        return personBuilderExt()
                .name("test")
                .process(b -> session == null ? b : b
                        .createdBy(session.getUser())
                        .createdSessionId(session.getSessionId())
                )
                .age(42)
                .occupation("test")
                .build();
    }


And, stage resue:

    public Person getReuseProcess(Session session) {
        return personBuilderExt()
                .name("test")
                .process(b -> SecurityUtil.supplySecurityInfo(b, session))
                .age(42)
                .occupation("test")
                .build();
    }


The rhythm still breaks a bit, but it happens on a local level, rather than on a global level. After we invoke our special actions, the processing continues as normal. And, there are no user-visible assignments there. The need for such a custom stage processing happens quite often when working with complex APIs, like the JPA Query Builder. The lack of such a simple method sometimes leads to very complicated and hairy code.

Kotlin Case

If you use some other JVM language that has extension methods, like Groovy or Kotlin, then you could add such method to the API yourself. For Kotlin, we just need to define the method as an extension:

fun <B : BaseBuilder<*, *>?, T> B.process(ext: (b: B) -> T): T = ext(this)


And then, we could use it for optional and reuse cases:

    fun getOptional(session: Session?): Person {
        return Person.builder()
                .name("test")
                .process { b ->
                    if (session == null) b
                    else b.createdBy(session.user)
                            .createdSessionId(session.sessionId)
                }
                .age(42)
                .occupation("test")
                .build()
    }

    fun getReuse(session: Session?): Person {
        return Person.builder()
                .name("test")
                .process { SecurityUtil.supplySecurityInfo(it, session) }
                .age(42)
                .occupation("test")
                .build()
    }


Note, if your process  method just invoke function like in this example, you do not have to write own method, it is possible to directly use the standard Kotlin's let  method like the following:

    fun getReuseLet(session: Session?): Person {
        return Person.builder()
                .name("test")
                .let { b -> SecurityUtil.supplySecurityInfo(b, session) }
                .age(42)
                .occupation("test")
                .build()
    }


We could even go a step further, if our method deserves it, and declare the method as a custom processing stage:

    fun <B : BaseBuilder<*, *>?> B.supplySecurityInfo(session: Session?): B =
            SecurityUtil.supplySecurityInfo(this, session)


And, we could use it directly in a bit nicer code:

    fun getCustomStage(session: Session?): Person {
        return Person.builder()
                .name("test")
                .supplySecurityInfo(session)
                .age(42)
                .occupation("test")
                .build()
    }


Note: Kotlin resolves these extension methods at compile time to normal static method calls.

Groovy Case

The Groovy case is more complicated, as there are currently some problems with type inference and compiling the code from IDEA. The supplied code is working fine with Maven, but it fails in IntelliJ IDEA if we use the @CompileStatic  annotation. First, we define an extension method (I'm using Java, as Groovy complains on generic bounds here):

public class BaseBuilderExtensions {
    public static <B extends Base.BaseBuilder<?, ?>, T> T process(B builder, Function<B, T> body) {
        return body.apply(builder);
    }
}


Then, we need to register extensions in the file META-INF/groovy/org.codehaus.groovy.runtime.ExtensionModule , like the following. The older versions before Groovy version 2.5.0 used  META-INF/services/org.codehaus.groovy.runtime.ExtensionModule, but such usage is not completely compatible with Java 11, so the path to the file has changed, while the old path is still supported for compatibility:

moduleName = builder-extension
moduleVersion = 1.0
extensionClasses = samples.BaseBuilderExtensions, samples.SecurityUtil


Note: We register our Java class SecurityUtil as an extension as well, as it follows the pattern of Groovy extension methods. Then, we could use our new methods for optional processing:

    Person getOptionalProcess(Session session) {
        return Person.builder()
                .name("test")
                .process { b -> session == null ? b : b
                            .createdBy(session.getUser())
                            .createdSessionId(session.getSessionId())
                }
                .age(42)
                .occupation("test")
                .build()
    }


For reusing code:

    Person getReuseProcess(Session session) {
        return Person.builder()
                .name("test")
                .process { SecurityUtil.supplySecurityInfo(it, session) }
                .age(42)
                .occupation("test")
                .build()
    }


And, even for custom stage, the method comes from the SecurityUtil class:

    Person getCustomStage(Session session) {
        return Person.builder()
                .name("test")
                .supplySecurityInfo(session)
                .age(42)
                .occupation("test")
                .build()
    }


The code looks as nice as the Kotlin code, but some additional efforts are required due to the dynamic nature of Groovy.

Conclusion

This pattern is very simple to implement, but it could add a great deal of usability in case of complex scenarios. If you are designing a fluent API, I would suggest adding such a method to your API. It would not harm anything, but it could make the user code look a bit nicer.

If you are using Kotlin, Groovy, or some other JVM language with extension methods, you do not need to wait until the API designer will add such a method to their fluent API (.NET had extension methods from the start, and the custom stage pattern is used there quite often). In languages with extension methods, it is possible to start using this pattern right now with custom extension methods when it is needed.

For Java projects, we still have to wait for API designers to support this pattern, as it is not always easy to plug own method into API. If you are fluent API designer yourself, please consider adding a single simple method like <T> T process(Funtion<Builder,T>)  to the API. This could help users with more flexible usage of your API.

The code for this article is available on GitHub.

API Java (programming language) intellij

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • HTTP vs Messaging for Microservices Communications
  • How To Choose the Right Streaming Database
  • Custom Validators in Quarkus
  • DevOps vs Agile: Which Approach Will Win the Battle for Efficiency?

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • 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: