Speaking some language fluently means that we speak without pauses needed to find appropriated words for example. In programming, fluency has almost the same meaning and we'll prove that through this article.
Data Engineering Design Patterns

Looking for a book that defines and solves most common data engineering problems? I wrote
one on that topic! You can read it online
on the O'Reilly platform,
or get a print copy on Amazon.
I also help solve your data engineering problems 👉 contact@waitingforcode.com 📩
Firstly, we'll try to define fluency in programming. After that, we'll explore the source code of Google Guava FluentIterable object to illustrate the concept of fluent programming. At the end, we'll try to write fluent and non-fluent code to show that the differences are not always very significant between fluent and non-fluent interfaces.
What is fluent interface ?
Fluent interface is also called "method chaining". Basically it means that instead of invoke methods separately, we'll invoke them in chain. A great example of this concept is builder design pattern which main advantage is separation of representation from construction of very complex objects. It guarantees that by the possibility of call setters in chained mode (each setter returns the instance of builder object, or another object depending on expected context). After call of all setters needed to construct an object, a building method called build() is invoked to construct final object.
According to this short description, we can define the goals of fluent interfaces:
readability: the mostly quoted advantage of fluent methods is the readability. In some cases, the code written in fluent way is more readable because it takes less place and handles whole logic together. You can see it in below example:
List<Guest> invitedGuests = new GuestsWrapper(guestsService.findAll()) .filterBy(invitedFilter, minAgeFilter) .orderBy(lastName);
This definition is clearly more readable that this one:
List<Guest> allGuests = guestsService.findAll(); List<Guest> invitedGuests = filterGuests(invitedFilter, minAgeFilter); return Collections.sort(invitedGuests, lastName);
The main difference is that the first snippet keeps the execution context together while in the second one, we have an impression to see 3 separated operations. Even if after looking carefully, we see that these operations concern the same thing.
object creation facility: with fluent interfaces, instead of defining complex methods (as objects having 4 different constructs), we can use builder design pattern and create object with method chaining:
Person person = new Person.Builder() .withFirstName(firstName) .withLastName(lastName) .withMaidenName(maidenName);
The use of builder in this case reduces the risk of human mistake. This risk increases with multiple and nested constructors. It's visible mainly in the situations when we need to add supplementary parameter to constructed object and we realize that the code becomes more and more dirty, and less and less readable:
public class Person { public Person(String firstName) { this(firstName, null, null); } public Person(String firstName, String lastName) { this(firstName, lastName, null); } public Person(String firstName, String lastName, String maidenName) { this(firstName, lastName, maidenName); } }
contextualization: chained methods help to keep the context when the code is read. Reading object creation with different set* calls defined in new lines is a little bit harder than in chained mode. It's because the context which, in the case of fluent code, is kept in the reader's mind. In the other hand, when new line appears with set* invocation, we can automatically think about an action detached from the previous one.
An additional danger coming from separating lines, appears when the code is written by several programmers. In this situation, one programmer can introduce strange code separating two previously logically linked lines. And he can do that simply by mistake, like in following example:
Person person = new Person(); person.setFirstName("Test"); // Code introduced by mistake // but this "mistake" separates logically // composed Person object creation Address address = new Address(); address.setCountry(countryService.getDefaultCountry()); countryService.incrementCounter(address.getCountry()); // and Person creation continues only here... person.setLastName("Test last name"); person.setBirthDate("1980-10-03");
This kind of mistake is not possible with fluent interfaces.
order: when object creation must be made by following precisely defined steps, we can achieve that with template method design pattern. But when we don't want to add an additional level of abstraction by creating extra abstract methods to implement, we can also use builder and its chained way of methods invocation.
FluentIterable as an example of fluent interface
To see fluent interfaces in action, we'll take a look on com.google.common.collect.FluentIterable class (Google Guava 18). According to its comment, it allows to "manipulate Iterable instances in a chained fashion". To see what does happen under-the-hood, we'll define an use case, representing by following snippet:boolean elementPresent = FluentIterable.from(myList) .filter(myFilter) .firstMatch() .isPresent();Let's begin by inspecting from() method:
public static <E> FluentIterable<E> from(final Iterable<E> iterable) { return (iterable instanceof FluentIterable) ? (FluentIterable<E>) iterable : new FluentIterable<E>(iterable) { @Override public Iterator<E> iterator() { return iterable.iterator(); } }; }
As you can see, it returns an instance of FluentIterable object, got through casting or through object creation. After that, we invoke filter() method of FluentIterable which looks like:
@CheckReturnValue public final FluentIterable<E> filter(Predicate<? super E> predicate) { return from(Iterables.filter(iterable, predicate)); }
It also returns an instance of FluentIterable, got also directly as a result of from() method. The only difference is that the elements are filtered by Predicate defined in the method signature and applied in Iterables.filter() call. As you can observe, we are still using FluentIterable instance. Thanks to it, we can call firstMatch() method which return an instance of Optional<E> object:
public final Optional<E> firstMatch(Predicate<? super E> predicate) { return Iterables.tryFind(iterable, predicate); }
In this way we are "fluently" passing from FluentIterable context to the context of another object, Optional<E>. After that, we're using isPresent() method which returns true if Optional contains a reference to non-null object.
Comparison of fluent and not fluent interface
To see some code with and without fluent interface, we'll try to build following HTML content:
<body><h1></h1><div><p><span></span></p></div><ul><li>The 1st list element</li><li>The 2nd list element</li><li>The 3rd list element</li></ul></body>
Below, you can find the code of two simple builder: one being fluent and another one normal object:
// FluentHtmlBuilder.java public class FluentHtmlBuilder { private String tag; private String opening; private String closing; private String content = ""; public static FluentHtmlBuilder openTag(String tag) { FluentHtmlBuilder builder = new FluentHtmlBuilder(); builder.tag = tag; builder.opening = "<"+tag+">"; return builder; } public FluentHtmlBuilder closeTag() { this.closing = "</"+this.tag+">"; return this; } public FluentHtmlBuilder withContent(String...contents) { for (String content : contents) { this.content += content; } return this; } public String asString() { return opening + content + closing; } } // NonFluentHtmlBuilder.java public class NonFluentHtmlBuilder { public String generateContent(String tag, String content) { return "<"+tag+">"+content+"</"+tag+">"; } }
As you can see, the code of non fluent builder is easier than the code of fluent one. However, in the implementation fluent builder seems to be readable easier:
public class FluentCodeTest { private String expected = "<body><h1></h1><div><p><span></span></p></div><ul><li>The 1st list element</li><li>The 2nd list element</li><li>The 3rd list element</li></ul></body>"; private String firstLi = "The 1st list element"; private String secondLi = "The 2nd list element"; private String thirdLi = "The 3rd list element"; @Test public void testWithFluent() { String code = openTag("body").withContent( openTag("h1").closeTag().asString(), openTag("div").closeTag().withContent( openTag("p").closeTag().withContent( openTag("span").closeTag().asString() ).asString() ).asString(), openTag("ul").withContent( openTag("li").withContent(firstLi).closeTag().asString(), openTag("li").withContent(secondLi).closeTag().asString(), openTag("li").withContent(thirdLi).closeTag().asString() ).closeTag().asString() ).closeTag().asString(); assertThat(code).isEqualTo(expected); } @Test public void testWithoutFluent() { NonFluentHtmlBuilder builder = new NonFluentHtmlBuilder(); String listContent1 = builder.generateContent("li", firstLi); String listContent2 = builder.generateContent("li", secondLi); String listContent3 = builder.generateContent("li", thirdLi); String ul = builder.generateContent("ul", listContent1+listContent2+listContent3); String h1 = builder.generateContent("h1", ""); String span = builder.generateContent("span", ""); String p = builder.generateContent("p", span); String div = builder.generateContent("div", p); String body = builder.generateContent("body", h1+div+ul); assertThat(body).isEqualTo(expected); } }
In the case of FluentHtmlBuilder we have the impression that whole code constructs one thing. While in the case of NonFluentHtmlBuilder we take more time to understand that the 9 generateContent() calls are linked together.
In this article we can see the utility of fluent interfaces in the way of writing code more readable and understandable. However, not always and they shouldn't be overused. The overusing can lead into some bad situations as, for example, breaking encapsulation or making tests more difficult to write.
Consulting

With nearly 16 years of experience, including 8 as data engineer, I offer expert consulting to design and optimize scalable data solutions.
As an O’Reilly author, Data+AI Summit speaker, and blogger, I bring cutting-edge insights to modernize infrastructure, build robust pipelines, and
drive data-driven decision-making. Let's transform your data challenges into opportunities—reach out to elevate your data engineering game today!
👉 contact@waitingforcode.com
đź”— past projects