Poutsma Principles Consulting & Training

Fluent Internals

Part 4 of Crafting Fluent APIs

So far in this series on Crafting Fluent APIs, I have focused on the surface of the API. In this post, I want to show how that surface is designed, using RestClient as an example.

This is Part 4 of a series where I explore various design considerations behind fluent API design. You can find an overview of the series here.


When reading through the Javadoc for RestClient, you may notice a large number of internal types that support the fluent API:

Some of these types have intimidating signatures, such as:

interface RequestHeadersUriSpec<S extends RequestHeadersSpec<S>>
    extends UriSpec<S>, RequestHeadersSpec<S>

In the previous post, I argued that a fluent API’s surface is more important than the complexity of the internal types that support it. That trade-off becomes clear when browsing the Javadoc. Fluent APIs are designed to be used through Ctrl + Space, not read through documentation. (Though usage examples do help.)


Instead of Javadoc, it can be more helpful to visualize the fluent flow with a state diagram. Here is a simplified version for RestClient:

State Diagram for RestClient

This diagram highlights a few key points:

  • Creating a request that cannot have a body takes you to RequestHeadersUriSpec; if it can have a body, you go to RequestBodyUriSpec.
  • From either state, you can specify a URI, or skip it if a base URL is already configured.
  • You can repeatedly specify headers (and/or a body) until you call retrieve or exchange.
  • exchange ends the chain immediately, while retrieve gives you several response-handling options first.

Let us now return to that earlier type signature:

interface RequestHeadersUriSpec<S extends RequestHeadersSpec<S>>
    extends UriSpec<S>, RequestHeadersSpec<S>

This expresses that you can set both a URI and headers at this stage. The type parameter S is used as the return type for most methods in UriSpec and RequestHeadersSpec, like:

S uri(URI uri);
S header(String headerName, String... headerValues);

This is a form of self-referencing generic. It ensures that methods inherited from different interfaces consistently return the correct type—in this case, a subtype of RequestHeadersSpec. A similar pattern is used in Java itself, in the declaration of java.lang.Enum.


The Broader Lesson

The proof of an API is not in its documentation; it is in how it feels when a developer navigates it with Ctrl + Space.

Creating that feeling often requires complex internal types and generic gymnastics, but that complexity should always serve one purpose: a clean, intuitive, and discoverable surface.

Behind every fluent API that feels obvious is a careful type design that models allowed states and transitions. Done well, the result is something that feels simple—even when it is not.

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.