Poutsma Principles Consulting & Training

Fluent APIs Are More Than Just Chaining

Part 2 of Crafting Fluent APIs

A common characteristic of fluent APIs is the use of method chaining: allowing calls to be strung together in a single statement without storing intermediate results.

The simplest way to support chaining is to have each method return this.
However, doing so can leave the object in an inconsistent or invalid state—especially when methods are called out of order.

By narrowing or changing the return type at each step, fluent APIs can guide developers more carefully through valid usage flows, reducing the chance of missteps and improving overall clarity.

This is Part 2 of a series entitled Crafting Fluent APIs, where I explore various design considerations behind fluent API design. You can find an overview of the series here.


Let us consider a fluent API that composes an HTTP request, consisting of:

  • HTTP method
  • URL
  • headers
  • body

A basic version of such an API might simply return this from every method, allowing usage like this:

var request = Request
    .method("POST")
    .url("https://example.com")
    .header("Content-Type", "text/plain")
    .body("Hello World");

So far, so good.

However, this naive API would also allow the following:

var request = Request
    .body("Hello World")
    .url("https://example.com")
    .header("Content-Type", "text/plain")
    .url("https://example.net")
    .method("GET");

Besides the confusing order, this introduces a clear violation: a request with a GET method is not supposed to include a body. The API allows it, because everything is exposed at once.

The most common fix is to throw an IllegalStateException when a method is called in the wrong order or in an invalid context. But a more elegant approach is to design the API so that invalid states are impossible to reach in the first place.

RequestEntity

This principle is applied in Spring Framework 4.1’s RequestEntity API.

When you create a GET request using RequestEntity.get(...), your options are limited:

RequestEntity method options when using GET

When you create a POST request using RequestEntity.post(...), more options appear:

RequestEntity method options when using POST

In both cases, the shared headers methods are present—but in the POST case, body-specific methods like body(T), contentLength(long), and contentType(MediaType) are also available.

This behavior is not enforced by runtime checks — it is baked into the API design. The methods for GET return a HeadersBuilder, while those for POST return a BodyBuilder—pun intended, which extends HeadersBuilder.

By narrowing the return type at each step, the API forms a small DSL, guiding the developer toward correct usage and away from invalid combinations.

This approach makes it easier to do the right thing, which was my mantra when working on Spring.

Of course, not every HTTP service adheres to the spec. Some services may accept or even require a GET request with a body. To handle such cases, RequestEntity provides method(...), which bypasses the fluent DSL and exposes more flexibility.


The Broader Lesson

Method chaining alone does not make an API fluent.

A well-crafted fluent API avoids invalid states not by checking for them at runtime, but by using narrow return types that reflect the state of the interaction.

This requires thinking in terms of state transitions: What are the valid states at this point? What methods make sense in each state?

Sketching a state diagram during design can help, but ultimately, the real test is in how the API feels in code. And that feeling often comes down to what shows up when the developer presses Ctrl + Space.

About the Author

I am Arjen Poutsma, and I help teams improve Java code, design better APIs, and manage open source—through reviews, training, and coaching. If that sounds useful, please contact me.