Fluent setter: breaking the convention

Sergey Chernov
Miro Engineering
Published in
6 min readOct 18, 2021

What if we implement a fluent Interface, whereby the setter method returns this instead of void?

Now, let’s rewrite a standard piece of code using a fluent interface:

Here’s the same code, without a fluent interface:

By implementing a fluent interface, we use less code, we don’t declare or refer to local variables, we require lower cognitive load, and we don’t change the meaning. The hierarchy in the code reflects the nesting of the objects, of the fields to fill out. You can also notice the nesting visually. For example, let’s consider a JSON-serializable object, a standard data exchange format for REST APIs:

Why not use Lombok Builder?

Programmers familiar with Lombok may prefer to use Builder, instead. This option achieves the same result, and the code looks pretty much the same, but with more builder() and build() calls:

In general, Builder works well with immutable objects, as an alternative to passing a large set of parameters to the constructor. However, it’s not an ideal option for mutable POJOs. Moreover, Builder limits compatibility with a number of frameworks and libraries.

What frameworks support a fluent setter?

Several modern frameworks support such access methods. These are the ones I know of and work with.

Lombok

It is very easy to convert the @Data class to use this approach.

For a class, add the @Accessors(chain = true) annotation:

For package / module / project, add the following parameter to lombok.config:

lombok.accessors.chain=true

Fluent vs chain in Lombok

It’s important to distinguish between fluent and chain in Lombok. For example:

lombok.accessors.fluent=true

produces almost the same result as @Accessors (fluent = true). The difference is that the generated setter method isn’t prefixed with set:

IDEA (Lombok plugin)

From IntelliJ version 2020.3 the Lombok plugin is shipped with IntelliJ IDEA by default. The plugin perfectly recognizes such configurations, both through annotations and configs, and code analysis works correctly:

IDEA (code generation)

IntelliJ IDEA can generate access methods for class fields, including fluent. To do this, set a new field, and then from the context menu select Code > Generate > Setter:

From Template select Builder:

This generates an access method:

Note that this sets the Builder pattern as the default template. To revert to the classic option, from Template select IntelliJ Default.

Jackson

Jackson, a library for serializing JSON, XML and more, doesn’t require additional configuration to work with beans whose setters return this. Serialization and deserialization work without issues.

jOOQ

jOOQ, a framework that generates model classes from a database schema, can generate classes with fluent setters. Since it is generation, not analysis, it requires an explicit setting of the configuration parameter.

Hibernate

Hibernate, a database access framework, allows using classes with the proposed notation like Entity. No additional configuration required.

Spring and Spring Boot

Spring and Spring Boot (as well as derived projects like Spring Data and Spring Data REST) contain lots of logic based on “reflection” and analysis of class structures. Here are the configuration classes, the parsing of ResultSets through BeanPropertyRowMapper, and so on. BeanUtils is the main class responsible for parsing the properties of beans in spring, and it supports both setter methods — returning either `this` or `void`. Which means everything works out of the box.

Mapstruct

Mapstruct, a library for auto-generation of conversion classes from one class to another, makes analysis of the corresponding class structures. It requires no additional configuration, and it recognizes setter methods that return this.

Kotlin

The developers of the Kotlin language have solved all the problems that the proposed approach is trying to solve in Java. There are data classes and named passing of many parameters to constructors and methods. Moreover, Kotlin has syntactic sugar to access the properties of objects as calls to the corresponding getter and setter methods. Kotlin honors setter methods that return this instead of void:

In Kotlin we can use setter calls in Java models as property assignments. Long live Kotlin!

What does the spec say?

There is a JavaBeans spec that doesn’t explicitly say anywhere that setter methods should return exactly void, although all examples are written that way.

Not everything is so rosy. The main JDK class responsible for parsing the structure of Java Beans is java.beans.Introspector, and it doesn’t recognize setter methods, if they return something other than void.

What can we break?

java.beans.Introspector is heavily used in Swing and Java EE elements. For example, if a JSP tag class tries to return this in its methods, it throws an error at runtime. This is perhaps the biggest problem with this approach: compilation and unit tests can’t prevent this bug, unless it’s a good integration or an e2e test.

Silent bugs can also be problematic if you use Apache Commons-BeanUtils, a library to work with beans (for example, copying properties from one bean to another). By default, copying bean properties ignores properties with setters returning this. The FluentPropertyBeanIntrospector extension class adds this analysis, but it has a bug. There are two solutions to address the bug: 1, 2. However, even after trying to discuss this topic on the mailing lists, this issue remains open. It looks like the project was abandoned, despite its popularity.

When to use

I’ve been using this approach for the past few years. I apply it especially when working with REST-APIs, because they have many classes of data models and DTOs. If a project is still in the development stage, there are fewer chances of breaking what works. Frameworks like Spring Boot are great, but if you are running on a traditional Java EE / Swing stack, it probably won’t work for you.

How to migrate

If there are no good integration tests, then migrate very carefully. Lombok allows flexibility in customization; for example, you can start with one class, package, or a new module. Or you can do it the other way around: enable chained setters at the level of the entire project, and disable them for a specific package; for example, JSP tags don’t work particularly well with fluent setters:

In an existing project, it’s better to implement these changes one at a time, iteratively.

Inheritance problem

In case we inherit setters from the base class, a problem arises.

Now we can’t write code like this:

There are 4 possible solutions:

  1. Often we need to get an object of the superclass at the output, not the class; we can write in reverse order, and return the supertype:

2. In the superclass, declare a constructor that accepts base fields as an exception. It’s advisable to limit the number of fields. Remember that we may also need a parameterless constructor for deserialization.

public IdPojo() {
}
public IdPojo(long id) {
this.id = id;
}
...
public SubPojo() {
}
public SubPojo(long id) {
super(id);
}

3. Declare a generic type for self, and return T instead of this:

public class IdPojo<T extends IdPojo<T>> {
...
public T setId(long id) {
this.id = id;
return self();
}
private T self() {
return (T) this;
}
// equals, hashCode, toString
}
public class SubPojo extends IdPojo<SubPojo> {
...
}

4. To override methods in the inheritor:

public class SubPojo extends IdPojo {
...
@Override
public SubPojo setId(long id) {
return (SubPojo) super.setId(id);
}

Conclusions

  • ✅ Reduced cognitive load
  • ✅ Fewer local variables (coming up with names for local variables is one of the really difficult programming problems)
  • ✅ Hierarchy of code, compact form
  • ❌ Incompatibility with conservative stack
  • ❌ Silent bugs.

It might be a bold statement to define the use of fluent setters as a new trend. But a number of emerging technologies are going against traditional conventions. I made my choice. And I’ve never seen that this approach was reverted in projects that I participated.

Code examples.

Join our team!

Would you like to be an Engineer at Miro? Check out opportunities to join the Engineering team.

--

--