Responding to Real Usage
Part 3 of Crafting Fluent APIs 28 May 2025I 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.