Poutsma Principles Consulting & Training

Responding to Real Usage

Part 3 of Crafting Fluent APIs

I ended the last post in this series on Fluent APIs with:

ultimately, the real test is in how the API feels in code.

As an API author, it is difficult to predict exactly how developers will use your API until you see it used in practice. And because of the Change Event Horizon, you are no longer able to change much once it is released.

That is why it is so important to have an extensive milestone and release candidate phase: to give your users time to try new functionality and provide feedback. It also gives you a chance to observe how people actually use your API—whether by scanning GitHub, watching conference demos, or through other means.

This is Part 3 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.


Spring Framework’s reactive HTTP client, WebClient, was introduced in version 5.0 in 2017. Shortly after the first release candidate went out, the Spring I/O conference in Barcelona took place.

I was in the audience as my colleagues Mark Heckler and Josh Long gave a talk on Reactive Spring. In their presentation, they showed how to initialize a WebClient with a base URL:

WebClient client = WebClient.builder()
    .baseUrl("https://example.com")
    .build();

And then how to make a request to the base URL by passing an empty string to the uri method:

Mono<String> result = client.get()
    .uri("")
    .retrieve()
    .bodyToMono(String.class);

Watching this demo, I was immediately bothered. In our fluent API design, we had made the URI a required step—and here that decision forced users to pass an empty string just to proceed. This was not what fluency should feel like.

It reminded me of Steve Jobs motivating the original Macintosh team: by shaving off seconds from boot time, they could save dozens of lives.

We were not saving lives—but we could certainly save lines.

After the conference, we updated the API in the next release candidate to make the uri step optional. Now, with a base URL configured, you can write:

Mono<String> result = client.get()
    .retrieve()
    .bodyToMono(String.class);

Cleaner, simpler, and better aligned with real usage.


The Broader Lesson

Like any user interface, the only real test of a fluent API is watching people use it.

Developers will use your API in ways you did not expect. When that happens, your job is not to correct them; it is to learn from them, and adapt the design accordingly.

The resulting API might be more complex behind the scenes—involving more intermediate types or branching logic—but what matters is that the surface becomes simpler, more intuitive, and more aligned with real usage.

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.