What is a fluent interface in Java? Why is it important, and how can it make your programming easier?
A fluent interface simplifies the way we use the interface of a class. It makes the class easier to use. It’s a way of designing an object’s API to allow us to chain multiple method calls together in a readable and intuitive manner. To implement it, we need to declare methods that return objects of the same type.
Fluent interfaces are all about readability. We use them to reduce syntactical “noise”, and to clearly express what the code does. We often code them as facade classes over existing code.
The fluent interface pattern is often used to build an internal Domain Specific Language (DSL). A domain-specific language is a computer language targeted at a specific type of problem, and is generally not usable to solve problems outside its particular domain.
POJO Example
As an example, here’s a standard POJO class without a fluent API. All setters return void
as is usual:
public class Person {
private String firstName;
private String surname;
// more data fields...
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public void setSurname(String surname) {
this.surname = surname;
}
// more code...
} // end of class
Using it in code looks like this:
Person p = new Person();
p.setFirstName("Joe");
p.setSurname("Bloggs");
Now let’s change the setters to a fluent API. Instead of returning void
, we specify Person
as the return value, and return the current object itself, i.e. the this
reference.
public class Person {
private String firstName;
private String surname;
// more data fields...
public Person setFirstName(String firstName) {
this.firstName = firstName;
return this;
}
public Person setSurname(String surname) {
this.surname = surname;
return this;
}
// more code...
} // end of class
Using it in code now looks like this:
// using method chaining
Person p = new Person().setFirstName("Joe").setSurname("Bloggs");
More intuitive, less coding and no repetition!
String and StringBuilder APIs
We all know the standard building block String
class. It uses a fluent interface for most methods returning a String
object, such as replace()
, substring()
, trim()
, toLowerCase()
, toUpperCase()
, etc. They don’t return a reference to the same this
object, but rather a brand-new String
object. The methods can be chained together as follows:
String s = "Who is General Failure and why is he reading my hard disk?";
s = s.substring(0, 23).trim().toLowerCase();
Another example of a fluent interface is the overloaded append()
methods of the StringBuilder
and StringBuffer
classes. These methods always return an instance of the same class.
Here is a simple example:
StringBuilder sb = new StringBuilder();
sb.append("My name is ")
.append(name)
.append(" ")
.append(surname)
.append(" and my age is ")
.append(age);
The Streams API
An excellent example of fluent API design is the Java Streams API. We reduce verbose code by chaining stream-related method calls together, rather than using the traditional way of externally iterating over a collection. Manipulating streams of data using a fluent interface API allows us to express our intentions in a clear, declarative way.
Let’s look at a simple example using the standard fluent Stream
map-reduce-filter paradigm. We first create a Stream
object from a list of numbers with the static Stream.of()
method. After that we can manipulate the resulting stream using its fluent API. After each method call, an object of the same Stream
type is returned. We terminate the stream operation with a method that returns a different type, here a Collector
that returns a String
:
Stream<Integer> fibonacci = Stream.of(0,1,1,2,3,5,8,13,21,34,55,89,144);
String result = fibonacci.distinct()
.filter(n -> n % 2 != 0) // odd numbers only
.limit(3)
.map(n -> "#" + n)
.peek(System.out::println) // for debugging
.collect(Collectors.joining(", "));
System.out.println(result);
The Builder Design Pattern
The Builder design pattern is a creational design pattern that separates the construction of a complex object from its representation. The Builder class in the pattern usually implements a fluent interface that allows the step-by-step creation of objects. This interface makes it easy to use as the following code shows:
Person.Builder builder = Person.builder();
personBuilder = builder.firstName("Joe")
.surname("Bloggs")
.nickName("thebloggman")
.setAge(31)
.email("joe.bloggs@snailmail.com");
Person person = builder.build();
The Person.Builder
class uses a fluent interface API. We first create a Person.Builder
using the Person.builder()
method. Then we chain multiple methods calls that set various attributes of a person. Each of these methods returns the same Person.Builder
object. Finally, we call the build()
method which returns a new Person
object with the appropriate attributes set.
The Builder design pattern is a good example of the implementation of a fluent interface API.
The Interface Segregation Principle vs Fluent Interfaces
One of the downsides to fluent interfaces is that they are generally large interfaces. As such they have the potential to violate the Interface Segregation Principle (the I in the SOLID design principles).
As an example, the Stream
interface has close to 40 methods in Java 8 and almost 50 in Java 21.
If we have created a facade class implementing a fluent interface on top of code that follows the SOLID design principles, we can limit the violations to that facade class.
Summary and Further Reading
To summarise:
-
APIs with fluent interfaces make code consuming them easier to write, read and understand.
-
Fluent APIs are harder to write, and can violate the Interface Segregation principle.
The term fluent interface was coined in 2005 by Eric Evans and Martin Fowler, even though that style of interface dates from the development of method cascading in the Smalltalk language in the 1970s. Martin Fowler’s original article is here.
There’s a nicely written technical article at Belief Driven Design on fluent builders.
As usual, Wikipedia has a very detailed page here.
The numbers used in the Stream
example were from the Fibonacci sequence. It’s a very simple numerical sequence in which each number is the sum of the two preceding ones. For its simplicity, it has some fascinating properties and appears in many unexpected places, even in nature.
Was this interesting? Please share your comments, and as always, stay safe and keep learning!