Poutsma Principles Consulting & Training

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.


How @MVC came to be

When I worked on the Spring Framework, most of my days were filled with maintenance tasks: resolving issues from the tracker and occasionally merging pull requests from contributors. While essential, these tasks were not exactly revolutionary.

But sometimes, the opportunity arises to fundamentally change how developers work.

In this post, I will share the story behind what I consider my most important contribution to the Spring Framework: the @Controller programming model.

Spring MVC before @Controller

Before Spring Framework 2.5, Spring MVC was based on inheritance. Controllers implemented the Controller interface, which looks like this:

public interface Controller {
    ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;
 }

There were several base classes to extend from, offering functionality for common web tasks. One infamous example was the misnamed SimpleFormController, which—despite its name—was anything but simple.

You would typically create one controller per path in your site, mapping request paths to controller beans in an XML configuration file. It was possible to handle multiple requests in a single class using MultiActionController, but that approach came with its own limitations.

Convention over Configuration

Around this time, Ruby on Rails entered the web development scene.

Compared to the Java landscape—including both Spring MVC and Java EE’s JSF—Rails was a breath of fresh air. It introduced ideas like convention over configuration, which were revolutionary at the time. Backed by the flexibility of the Ruby language, Rails changed the way developers thought about building web applications.

Java Annotations

Java 5 (released in 2004) introduced annotations, a way to attach metadata to Java types.

It took the community some time to figure out how best to use them. In Spring, early support focused on cross-cutting concerns like transactions and security. Annotations like @Transactional appeared in Spring 1.2 (2005), only months after Java 5’s release.

Delivering Courses

At the time, I was working as a consultant at Interface21, delivering Spring training courses. I was also the project lead for Spring Web Services—though that work usually had to wait for evenings and weekends.

I remember delivering a course in Norway, explaining the benefits of annotations to a group of students, when I had a moment of clarity. I asked my co-teacher to take over so I could think through what I’d just realized.

Spring-WS Came First

The idea was simple, yet powerful: instead of using XML to configure handler mappings, we could use method-level annotations to route incoming requests directly to handler methods. We could go further and use parameter-level annotations to map parts of the request—headers, parameters, payloads—to method arguments. This would bring a Rails-like experience to Java.

The first implementation of this idea appeared in Spring Web Services. The @PayloadRoot annotation mapped incoming SOAP messages based on the root element’s qualified name. Parameters could be annotated with @RequestPayload or @XPathParam to extract data from the request body.

From SOAP to HTTP

It didn’t take long before I realized the same approach could work for Spring MVC. Annotations could map incoming HTTP requests to handler methods, and parts of the request (like query parameters) could be bound to method parameters.

That same evening, from my hotel room in Oslo, I sent an email to my colleagues describing this new way of writing MVC controllers. The idea was enthusiastically received by Rod and the team.

A decision was made to include this new annotation-based model in Spring 2.5, which already featured other annotation-based improvements like @Autowired and component scanning. It was a late-stage decision—during the milestone phase of the 2.5 release—and Jürgen worked incredibly hard to get the first version of @RequestMapping into the first release candidate, shipped in October 2007.

Building on the Foundation

Later, when I joined the Spring Framework team, we built on the initial model, and added support for RESTful use cases, introducing annotations like @RequestBody, @ResponseBody, and more. In Spring 3.1, Rossen and I overhauled Spring MVC’s internal architecture while maintaining backward compatibility with older controller styles.

But that’s a story for another time.


'as' vs 'to' in Method Names

What is the difference between a method named toSomething() and one named asSomething()? For example: what distinguishes a toMap() method from an asMap() method?

The key difference lies in expectations about how the returned value relates to the original object.

to – Detached Conversion

When a method name starts with to, it typically converts internal data into a new representation and returns it. The returned object is detached—that is, it has no connection to the original.

as – Linked Representation

In contrast, a method starting with as usually exposes an alias of the internal data. This alternative representation is connected to the original, meaning changes made to either the alias or the original will be reflected in the other.

Examples from the JDK

This naming pattern is used throughout the JDK. Consider:

  • Collection::toArray():
    The Javadoc makes it clear that the returned array is independent:

    The returned array will be “safe” in that no references to it are maintained by this collection.

  • Arrays::asList(T...):
    Here, the Javadoc explicitly states the alias is connected:

    Changes made to the array will be visible in the returned list, and changes made to the list will be visible in the array.

ByteBuffer

The java.nio package is another area where both as and to methods are used. For example, compare ByteBuffer::asCharBuffer(), which returns a connected alias, with ByteBuffer::toString, which produces a detached representation.

Code Examples

Using asList()

String[] array = new String[]{"foo"};
List<String> list = Arrays.asList(array); // List alias of the array

list.set(0, "bar"); // change the alias
System.out.println(array[0]); // "bar"
System.out.println(list);     // "[bar]"

array[0] = "baz"; // change the original
System.out.println(array[0]); // "baz"
System.out.println(list);     // "[baz]"

Using toArray()

List<String> list = new ArrayList<>();
list.add("foo");
String[] array = list.toArray(new String[1]); // convert list to array

array[0] = "bar"; // modify the detached array
System.out.println(list);     // "[foo]"
System.out.println(array[0]); // "bar"

list.set(0, "baz"); // modify the original list
System.out.println(list);     // "[baz]"
System.out.println(array[0]); // "bar"

A Rule of Thumb

If you start a method name with as, make sure the returned object stays linked to the original. Otherwise, you will create misleading expectations.