Poutsma Principles Consulting & Training

Designing for Ctrl + Space

Part 1 of Crafting Fluent APIs

Though undoubtedly there are still vi and Notepad users out there, code completion has become the primary way most developers interact with your API. As such, it is worth considering what they see when they hit Ctrl + Space in their IDE of choice.

APIs with a clean, minimal completion surface are easier to use and harder to misuse—especially when designed fluently, using method chaining and step-by-step invocation.

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


Most developers, when exploring an unfamiliar API, do not start with the documentation. They type an object name, hit Ctrl + Space, and scan the list of suggestions. That list becomes both documentation and user guide.

Each additional option in that list increases cognitive load. The more methods a developer has to scan and distinguish between, the harder it becomes to discover the right one. A key goal in API design, then, is to reduce the number of visible options at any given moment.

When a developer cannot see the forest for the trees, the API is too noisy. Ideally, the completion menu should contain only a few relevant choices at each step.

RestTemplate

Take the RestTemplate, introduced in Spring Framework 3. It follows the same template pattern as JdbcTemplate, JmsTemplate, and others.

Like those, RestTemplate exposes a flat surface—dozens of similar-looking methods: convenience methods per HTTP method (getForObject, postForEntity, etc.), as well as generic methods (exchange and execute) for more flexibility. Moreover, each method has three overloaded variants to be able to offer URL template variables as varargs, as a Map, or to offer the URL as a java.net.URI.

All of these appear at once, creating an overwhelming set of choices.

Method Options for RestTemplate

To be fair, RestTemplate did not start with so many methods. Over time, more were added out of necessity (like patchForObject), but each new method added surface area and complexity.

Eventually, we made the decision to stop adding features to RestTemplate. Every addition meant another overload, and another burden for the user.

WebClient and RestClient

With Spring Framework 5 and the new reactive stack, we had the opportunity to apply what we had learned. WebClient was born, and later, RestClient for the non-reactive use case.

A key design goal was to limit the number of options offered at each step, using method chaining and narrower return types to guide the developer. The implementation behind this — particularly how return types control visibility — will be covered in a future post.

What used to be a single overloaded method call in RestTemplate became a chain of calls in RestClient, each handling one concern in the HTTP exchange.

First, choose the HTTP method:

Choosing a HTTP method with RestClient

Then specify the URI. As with RestTemplate, multiple variants are supported.

Second Option for RestClient

Because the method is POST, the next step offers options for setting the request body. If this were GET, body-related methods would not appear.

Third Option for RestClient

Finally, choose how to handle the response:

Fourth Option for RestClient

Compare this to the earlier approach:

String response = 
    template.postForObject(         // method
        "https://example.com/post", // URI
        "Hello World",              // request object
        String.class);              // response type

Now, using RestClient:

String response = 
    client.post()                          // method
          .uri("https://example.com/post") // URI
          .body("Hello World")             // request object
          .retrieve()
          .body(String.class);             // response type

Each step surfaces only what is relevant at that stage—a much cleaner experience for the developer.


The Broader Lesson

The key difference between RestTemplate and RestClient lies not in what they do, but in how they guide the developer to do it.

Where RestTemplate exposes a wide, flat surface with dozens of methods and overloads, WebClient and RestClient breaks the interaction into smaller, focused steps—each with a limited set of choices. This narrowing of the completion surface at each stage reduces cognitive load and makes the API easier to explore through code completion alone.

Fluent API design is not just about chaining calls. It is about structuring the journey—helping the developer move through a series of decisions in the right order, with just the information they need, when they need it.

In modern development, code completion is the user’s first interface with your API. Designing that experience deliberately is what separates usable APIs from user-friendly APIs.

When Fixing a Mistake Takes Five Years: the HttpMethod enum

One of the differences between working on a library or framework and building an application is what Dan North has called the Change Event Horizon: the amount of time it takes to undo a mistake.

In an internally deployed application, the horizon is short—you can fix a mistake in the next release. But in a library or framework, especially one as widely used as Spring, the horizon stretches much further. Mistakes can linger for years.

That is because every feature—no matter how flawed—is used in someone’s codebase. Fixing it means breaking backward compatibility. Unless the issue is critical, such as a security vulnerability, breaking user code is usually off the table.

Even when users agree with the change in principle, they might not be able to adopt it. The outdated API might be used in a third-party library they cannot change.

Maintaining backward compatibility—even across major versions—has been a key factor in Spring Framework’s long-term popularity. I will undoubtedly return to this topic in future posts. In this one, I want to highlight a specific mistake from Spring 5 that was quietly fixed in Spring 6—without most users ever noticing.


The HttpMethod Enumeration

Spring Framework 3.0 introduced the HttpMethod enum, listing HTTP request methods (GET, POST, DELETE, etc.). It was designed for use within ClientHttpRequest and ServerHttpRequest, part of Spring’s internal HTTP abstraction layer.

Over time, its usage expanded. First an abstraction over both client and server side was created with HttpRequest. Later, in version 5.0, it was reused for reactive support across both Netty and Servlet containers.

That latter expansion turned out to be a problem. Originally intended for RESTful, client-side usage, HttpMethod now had to represent all possible HTTP methods—including extensions like those used in WebDAV (LOCK, COPY etc.).

This mismatch came to light late in the release candidate phase of 5.0, when a bug was discovered: encountering an unknown HTTP method would throw an IllegalArgumentException. But it was too late for a clean fix.

The workaround was to introduce a getMethodValue() method that returned the HTTP method as a String. This allowed users to access the raw value, even if it was not part of the enum.

HttpMethod method = request.getMethod();
if (method != null) {
    // use enum
} else {
    String methodValue = request.getMethodValue();
    // fallback for unknown methods
}

This workaround worked—but it was ugly. It forced users to handle null values and duplicate logic. Worse, it made the enum effectively redundant.

Despite this, the enum could not be removed. Too much code relied on it. The usual option in this kind of situation is to introduce a new, better type—say, HttpMethodValue—and deprecate the old one. But then you end up maintaining two parallel APIs for years, leading to confusion and frustration.


The Fix in Spring 6

Spring 6 brought a rare opportunity: the transition from Java EE to Jakarta EE required users to recompile their code anyway. That gave us the chance to finally fix HttpMethod.

In milestone 1 of Spring 6, HttpMethod was changed from an enum to a class.

Because a Java enum is just a special kind of class, we could retain most of its behavior:

  • Constant fields for GET, POST, etc.
  • A valueOf factory method
  • A values() method
  • Private constructor
  • An implementation of Comparable and Serializable

With this change, getMethodValue() could finally be deprecated.

Most users probably never noticed. The API continued to work, and existing constants and methods were still available. Only in edge cases—like using HttpMethod in a switch statement—did source changes become necessary.


The Broader Lesson

If you are writing code that others depend on, backward compatibility matters.

Breaking changes—especially for cosmetic or subjective reasons—are a great way to frustrate your users and push them away. Keeping your API stable is a sign of respect for their time and trust.

When a mistake is made, the cleanest fix is often to introduce an improved alternative, deprecate the original, and wait. Sometimes, with enough patience (or the right major version bump), you can remove the mistake entirely.

In the case of HttpMethod, we were lucky. We fixed it without breaking most users. But chances like that are rare—and worth waiting for.

Why Does Spring Use Bubble Sort?

You might be surprised to learn that, as of version 6, Spring Framework includes an implementation of Bubble Sort.

Though hidden away in a package-protected method, you will find it here, in MediaTypeUtils:

static <T> void bubbleSort(List<T> list, BiPredicate<T, T> swap) {
    int len = list.size();
    for (int i = 0; i < len; i++) {
        for (int j = 1; j < len - i; j++) {
            T prev = list.get(j - 1);
            T cur = list.get(j);
            if (swap.test(prev, cur)) {
                list.set(j, prev);
                list.set(j - 1, cur);
            }
        }
    }
}

Bubble Sort is widely known to be inefficient. So why is it used in Spring? Why not rely on more efficient algorithms like Merge Sort, which backs Java’s own Collections.sort and Arrays.sort?

To understand the reasoning, we need to look at where and how this method is used.


Sorting Media Types by Specificity

Since this method appears in MediaTypeUtils, it is no surprise that its usage is related to media types (text/html, image/png, application/json). Specifically, it is used in the sortBySpecificity method.

This sorting process is part of content negotiation in Spring MVC, which happens when an HTTP request is resolved to a @Controller method. It is closely tied to the Accept header, which indicates which response media types the client can accept.

Both the values in the Accept header and the produces element on @RequestMapping can be provided in any order. To match the request to the most appropriate method, Spring MVC determines the most specific acceptable media type that is compatible with the method’s produces condition. To do that, it first needs to sort the media types by specificity.

How Specificity is Determined

Given two media types, Spring uses the following rules to determine which is more specific:

  1. If one has a wildcard type (*/*) and the other does not, the non-wildcard is more specific.
    Example: text/* is more specific than */*.
  2. If one has a wildcard subtype and the other does not, the non-wildcard is more specific.
    Example: text/plain is more specific than text/*.
  3. If both have the same type and subtype, the one with more parameters is more specific.
    Example: text/plain;format=flowed is more specific than text/plain.
  4. Otherwise, the media types are considered equally specific.

Why Not Use Collections.sort?

At first glance, this seems like a problem a Comparator could solve. In fact, Spring 5 and earlier used a dedicated Comparator to sort media types by specificity. However, that comparator was deprecated in Spring 6. Why?

Because the specificity relation described above forms a partial order, not a total one.

The Javadoc for Comparator specifies:

A comparison function, which imposes a total ordering on some collection of objects.

But consider text/html and text/plain. Neither is more specific than the other: they are incomparable, which is a valid relationship in a partial order.

A Comparator has no way to indicate these elements are not comparable; it must yield a less than, equal, or greater than relationship.

Why Bubble Sort Works

Bubble Sort does not require a Comparator. Instead, it compares adjacent elements and swaps them if needed, according to a provided condition.

This makes it well-suited to working with partial orders: it only needs to know if one pair is “wrong,” without assuming that every pair can be meaningfully compared.

Additionally, Bubble Sort is a stable sorting algorithm, meaning it preserves the original order of elements that are equal or incomparable.

Preserving original order is crucial when handling Accept header values. If you look at the list of default Accept header values used by browsers, they almost always start with text/html, as browsers obviously mainly consume HTML. An unstable sort might reorder the list, causing a server to prefer PNGs over HTML; despite the browser clearly expecting the latter.

More efficient algorithms could be adapted for this use case, but Bubble Sort’s simplicity made it a pragmatic choice. That said, Bubble Sort is still inefficient in general, which is why Spring enforces a size limit on the list of media types it is willing to sort this way.

The Broader Lesson

Efficiency is important—but correctness matters even more. Using Bubble Sort in a modern framework may seem counterintuitive, but here it was a deliberate and practical choice.

Choosing the right tool is not about chasing the fastest algorithm; it is about understanding the problem deeply enough to know what truly matters.

Sometimes the “inefficient” algorithm is exactly the right one—when you know your constraints and respect the edge cases.


An earlier version of this post incorrectly described the problem as one of transitivity; thanks to a helpful Reddit comment by JustAGuyFromGermany, it now attributes the issue to the lack of a total order.

Why Are There So Many URI Methods?

If you have worked with Spring Framework’s HTTP clients—RestTemplate, WebClient, or RestClient—you may have noticed that each of them offers multiple overloaded methods to specify the request URI.

Three Ways to specify a URL

Take RestClient as an example (ignoring the builder-based variants). It offers the following overloads:

  • uri(String uriTemplate, Object... uriVariables)
  • uri(String uriTemplate, Map<String, ?> uriVariables)
  • uri(URI uri)

WebClient has the exact same design, and RestTemplate has comparable variants for each HTTP method.

The Varargs Variant

The first form, using a String and Object..., is the most commonly used. It allows straightforward substitution of URI variables using a varargs array:

restClient.get().uri("https://example.com/{path}", "foo");

The Map Variant

The second form, which takes a String and a Map<String, ?>, allows you to substitute the same variable more than once:

Map<String, ?> vars = Map.of("path", "foo");
restClient.get().uri("https://example.com/{path}/{path}", vars);

The URI Variant

The third variant takes a pre-constructed java.net.URI:

restClient.get().uri(new URI("https://example.com/foo"));

At first glance, this might seem redundant. Why not just pass the URI as a string?

restClient.get().uri("https://example.com/foo");

In this simple case, both calls behave the same. But under the hood, there is a subtle and important difference related to URI encoding.


Why Parameter Types Matter

When you pass a String, Spring will encode the URI for you. But if your string is already encoded, this can lead to double-encoding.

For example:

restClient.get().uri("http://example.com/foo%20bar");

This looks like a request to http://example.com/foo bar, but it will actually go to http://example.com/foo%2520bar. The % character is not a valid character in a URI path—it gets encoded again as %25.

Why Not Be Smarter?

You might wonder why Spring does not automatically detect whether a string is already encoded. The challenge is that what seems “smart” to one developer may break expectations for another.

Some applications expect percent signs to be encoded again. Trying to infer the correct behavior would introduce ambiguity and inconsistencies.

Avoiding Ambiguity

This is exactly why the third method exists. When you pass a URI, Spring can safely assume that you have already handled encoding, because the Javadoc of URI(String) specifies:

The single-argument constructor requires any illegal characters in its argument to be quoted and preserves any escaped octets and other characters that are present.

By using URI explicitly, you avoid any ambiguity. You are telling Spring: do not touch this—I know what I am doing.


Avoid strings where other types are more appropriate

Joshua Bloch summarizes this concept in Effective Java:

Avoid the natural tendency to represent objects as strings when better data types exist or can be written. Used inappropriately, strings are more cumbersome, less flexible, and more error-prone that other types.

Effective Java, Third Edition (Item 62)

Passing a URI instead of a String is a textbook example of this principle.


The Broader Lesson

Using specific types in method signatures helps enforce correctness. For example:

  • A method that accepts a URI guarantees that encoding has already been handled.
  • A method that accepts a java.time object like LocalDate or ZonedDateTime guarantees that parsing and validation are complete.

In contrast, accepting a String means carrying the uncertainty of format, validity, and edge cases.

When designing APIs, prefer specific, well-defined types over general-purpose strings. Doing so makes your contracts clearer, reduces bugs, and makes your code easier to reason about—for both users and maintainers.

The Origin of the “Poutsma Principle”

One of the first things I probably need to explain is the name of this site: The Poutsma Principles. At first glance, it might sound like shameless self-promotion. And yes, maybe a little—but I can safely say I did not come up with the name myself.


A Meeting in Southampton

The name Poutsma Principle was born at a SpringSource engineering meeting in the summer of 2008, held in our Southampton office. These meetings took place once a quarter or so, and brought together the different teams working on Spring portfolio projects.

At that time, we were preparing the first release candidate of Spring Integration, which had been announced the year before. Mark Fisher—project lead of Spring Integration—was presenting the API to the rest of us.

I no longer recall all the details of his presentation, but I clearly remember two things that stood out.

Role Confusion

The API defined two main interfaces: MessageSource and MessageTarget. These defined the contracts for components that produce and consume messages, respectively. They were quite generic: many classes implemented one or both of these interfaces. Even a core class like MessageChannel implemented both.

On the surface, this made sense: if multiple components can receive messages, it is logical to share a common interface. However, conceptually this created an unfortunate side effect: it blurred the distinction between channels and endpoints. While both could technically receive messages, they serve very different roles in Enterprise Application Integration (EAI). Making them implement the same interface suggested they were interchangeable—contradicting the model Spring Integration was aiming for.

The Message Interface: Too Much, Too Mutable

The second issue I had was with the Message interface itself, which at the time looked like this:

public interface Message<T> {

    Object getId();

    MessageHeader getHeader();

    T getPayload();

    void setPayload(T payload);

    boolean isExpired();

    void copyHeader(MessageHeader header, boolean overwriteExistingValues);
}

To me, this interface was trying to do too much. The id and expiration felt like metadata better suited for the header. The copyHeader method belonged in a utility class. Most importantly, the presence of setPayload made the message mutable, and therefore not inherently thread-safe.

I Do Not Understand It—So It Must Be Wrong

When Mark finished presenting, I was confused, and said:

I do not understand it, and that probably means it is wrong.

That raised a big laugh—understandably so. In hindsight, it probably sounded more arrogant than intended. Let us chalk it up to Dutch directness.

What I meant was this:
At the time, I had been working with Spring for about four years. I had also studied Gregor Hohpe’s Enterprise Integration Patterns, and loved the clarity it brought to EAI concepts. So when I struggled to understand Spring Integration’s core API—despite being in its intended audience—I thought that might signal a deeper issue.

Revisiting the Design

Fortunately, Mark was open to feedback. A few weeks later, we sat down and worked through the issues together.

What emerged from that conversation was a clearer architectural model: channels and endpoints had distinct roles.

We also reworked the Message API. We made the Message interface immutable by removing setPayload, prioritizing thread safety. To maintain usability, we introduced MessageBuilder—a fluent API for creating or modifying messages safely. The id and expiration properties were moved into the headers.

The result was this much cleaner interface, which made it into Spring Integration 1.0, and eventually became foundational enough to move into the Spring Framework itself.

public interface Message<T> {

    MessageHeaders getHeaders();

    T getPayload();
}

Simple, focused, and easier to reason about.


Naming the Principle

At the next engineering meeting in Linz, Mark presented the revised design. One of his slides read:

Spring Integration now conforms to the Poutsma Principle.

The joke, of course, was that the design now passed the “Arjen understands it” test. That got another laugh—and the name stuck.

Beyond SpringSource

In 2010, the term made its way outside SpringSource. Iwein Fuld, a Spring Integration committer, asked to include it in the Xebia Essentials flash card series. I was honoured and happily agreed.

Wilfred Springer, who helped produce the cards, told me the principle reminded him of a quote from Bill Watterson, author of Calvin and Hobbes:

As far as I am concerned, if something is so complicated that you cannot explain it in 10 seconds, then it is probably not worth knowing anyway.


And that is how the Poutsma Principle was born.
What began as a bit of team banter ended up capturing a value I still try to follow: if your API needs too much explaining, it might need rethinking.