Fluent interfaces are a term coined by Martin Fowler and Eric Evans. It’s a design approach in software development that aims to create an object-oriented API. The goal is to make the API more readable by making method calls more expressive and easier to understand.
int sum = widgets.stream()
.filter(w -> w.getColor() == RED)
.mapToInt(w -> w.getWeight())
.sum();Fluent interfaces rely on method chaining to simplify their usage for clients. One of the clearest examples of fluent interfaces is the Steam API introduced in Java 8:
Here, the intention is to convert a collection of widgets into a stream and operate on it. First, filtering widgets that are red in color, then extracting the weight value (an integer), and finally summing up these values and assigning them to the ‘sum’ variable.
Using an object with fluent interfaces typically involves three stages:
- Creating an element that initiates the message chaining.
- Chaining messages that form the object’s usage.
- Receiving the result from the fluent interface, which could be the construction of an object or, as seen in the example above, a function like sum.
Implementing Fluent Interfaces
Let’s consider a class called Checkout, which models a supermarket purchase. To create a checkout, you need a shopping cart, a payment method, and optionally a coupon and a loyalty card.
public class Checkout {
private ShoppingCart cart;
private PaymentMethod paymentMethod;
private Coupon coupon;
private LoyaltyCard loyaltyCard;
public Checkout(ShoppingCart cart, PaymentMethod paymentMethod, Coupon coupon, LoyaltyCard loyaltyCard) {
this.cart = cart;
this.paymentMethod = paymentMethod;
this.coupon = coupon;
this.loyaltyCard = loyaltyCard;
}
// FUNCTIONALITY
}In the first approach, we use a builder that implements the fluent API design to model the creation of a checkout:
// Fluent Interface implemented with basic Builder pattern
public class CheckoutFluent {
private ShoppingCart cart;
private PaymentMethod paymentMethod;
private Coupon coupon;
private LoyaltyCard loyaltyCard;
private CheckoutFluent(){
}
public static CheckoutFluent getBuilderInstance(){
return new CheckoutFluent();
}
@Override
public CheckoutFluent withShoppingCart(ShoppingCart cart) {
this.cart= cart;
return this;
}
@Override
public CheckoutFluent withPaymentMethodCard(Card card) {
this.paymentMethod= card;
return this;
}
@Override
public CheckoutFluent withPaymentMethodCash() {
this.paymentMethod= new Cash();
return this;
}
@Override
public CheckoutFluent withValidCoupon(Coupon coupon) {
this.coupon= coupon;
return this;
}
@Override
public CheckoutFluent withoutCoupons() {
this.coupon= null;
return this;
}
@Override
public CheckoutFluent withLoyaltyCard(LoyaltyCard card) {
this.loyaltyCard= card;
return this;
}
@Override
public CheckoutFluent withoutLoyaltyCard() {
this.loyaltyCard= null;
return this;
}
@Override
public Checkout build() {
return new Checkout(cart,
paymentMethod,
coupon,
loyaltyCard
);
}
}However, a problem with this approach is that clients using this builder are not obligated to add the mandatory fields and can easily skip them.
public static void main( String[] args )
{
ShoppingCart cart= new ShoppingCart();
LoyaltyCard loyaltyCard= new LoyaltyCard();
Checkout checkout= CheckoutFluent.getBuilderInstance()
.withShoppingCart(cart)
.build();
// USE THE VAR checkout
}Example where skip phases in the buildTo address this, let’s explore a second option that takes this into consideration.
Fluent Interfaces a more advanced approach
In this case multiple interfaces are created, each identifying a step in the creation of a Checkout.

Each step is responsible for creating a part of the Checkout and exposes the interface for the next step, ultimately leading to the build phase, which returns the constructed Checkout object.
interface CheckoutFluentBuilder {
interface SelectShoppingCartPhase{
SelectPaymentMethodPhase withShoppingCart(ShoppingCart cart);
}
interface SelectPaymentMethodPhase{
SelectCouponPhase withPaymentMethodCard(Card card);
SelectCouponPhase withPaymentMethodCash();
}
interface SelectCouponPhase{
SelectLoyaltyCardPhase withValidCoupon(Coupon coupon);
SelectLoyaltyCardPhase withoutCoupons();
}
interface SelectLoyaltyCardPhase{
BuildCheckoutPhase withLoyaltyCard(LoyaltyCard card);
BuildCheckoutPhase withoutLoyaltyCard();
}
interface BuildCheckoutPhase{
Checkout build();
}
}
Implementing the interfaces
public class CheckoutFluent implements CheckoutFluentBuilder.SelectShoppingCartPhase,
CheckoutFluentBuilder.SelectPaymentMethodPhase,
CheckoutFluentBuilder.SelectCouponPhase,
CheckoutFluentBuilder.SelectLoyaltyCardPhase,
CheckoutFluentBuilder.BuildCheckoutPhase {
private ShoppingCart cart;
private PaymentMethod paymentMethod;
private Optional<Coupon> coupon;
private Optional<LoyaltyCard> loyaltyCard;
private CheckoutFluent(){
}
public static CheckoutFluentBuilder.SelectShoppingCartPhase getBuilderInstance(){
return new CheckoutFluent();
}
@Override
public SelectPaymentMethodPhase withShoppingCart(ShoppingCart cart) {
this.cart= cart;
return this;
}
@Override
public SelectCouponPhase withPaymentMethodCard(Card card) {
this.paymentMethod= card;
return this;
}
@Override
public SelectCouponPhase withPaymentMethodCash() {
this.paymentMethod= new Cash();
return this;
}
@Override
public SelectLoyaltyCardPhase withValidCoupon(Coupon coupon) {
this.coupon= Optional.of(coupon);
return this;
}
@Override
public SelectLoyaltyCardPhase withoutCoupons() {
this.coupon= Optional.empty();
return this;
}
@Override
public BuildCheckoutPhase withLoyaltyCard(LoyaltyCard card) {
this.loyaltyCard= Optional.of(card);
return this;
}
@Override
public BuildCheckoutPhase withoutLoyaltyCard() {
this.loyaltyCard= Optional.empty();
return this;
}
@Override
public Checkout build() {
return new Checkout(cart,
paymentMethod,
coupon,
loyaltyCard
);
}
}The implementation of these interfaces follows a similar pattern to the first case, with the key difference being the return type of each fluent interface method. This leads us to the usage example:
public static void main( String[] args )
{
ShoppingCart cart= new ShoppingCart();
LoyaltyCard loyaltyCard= new LoyaltyCard();
Checkout checkout= CheckoutFluent.getBuilderInstance()
.withShoppingCart(cart)
.withPaymentMethodCash()
.withoutCoupons()
.withLoyaltyCard(loyaltyCard)
.build();
// USE THE VAR checkout
}At line 6, the user can only choose to create a shopping cart; they cannot skip directly to the build step or any other step. This approach enforces a more structured and controlled creation process.
On the other hand, when a checkout is used, we can instantly see that it has a shopping cart, pays with cash, has no coupons, but does have a loyalty card. This way of expressing the object’s construction is particularly useful when creating customized tests. For instance, if we have test cases that change due to factors like a different payment method, such as a virtual wallet, we only need to add the method to the fluent interface for selecting the payment method.
The problems of fluent interfaces
One disadvantage is that the construction stages must be well-defined and reflected in the interfaces. It’s recommended that each interface has a single responsibility, adhering to the Single Responsibility principle of SOLID, in order to contain the explosion of methods of each
As the Checkout class gains more properties, the fluent interface methods become more extensive, potentially leading to code duplication.
Mocking objects implemented with fluent interfaces can be more challenging,
Personally I only use fluent interfaces in testing scenarios or as a builder.
A skilled developer knows how to limit the use of this tool to suitable cases. Even if you don’t use it in your day-to-day work, understanding how it works is valuable, as you’ll encounter various libraries that implement it.

Be First to Comment