Summary
This postmortem analyzes the Jetty 12 API removal of HttpInput.Interceptor and HttpOutput.Interceptor, why these hooks disappeared, what breaks during migration, and how senior engineers replace this functionality using Jetty 12’s new Handler/Filter pipeline and Content.Source / Content.Sink abstractions.
Root Cause
The failure occurs because Jetty 12 removed the legacy Servlet‑container–centric interception APIs (HttpInput.Interceptor, HttpOutput.Interceptor) as part of its architectural shift toward:
- Jakarta EE 10 namespace changes
- A unified, non-blocking content pipeline
- A redesigned I/O abstraction (
Content.Source,Content.Sink) - A stricter separation between request processing and transport layers
Your old code depended on APIs that no longer exist, so the migration breaks at compile time.
Why This Happens in Real Systems
Large production systems hit this issue because:
- Jetty 12 is a major architectural rewrite, not a drop‑in upgrade.
- Interceptors were historically used for:
- Request/response body filtering
- Security scanning
- Logging and auditing
- Payload transformation
- These responsibilities now belong to Handlers, Filters, or custom Content wrappers.
Real-World Impact
Teams migrating from Jetty 9–11 typically experience:
- Broken request/response body manipulation
- Inability to inject logic before/after body reads
- Unexpected behavior when relying on old I/O semantics
- Refactoring cost because the new API requires a different mental model
Example or Code (if necessary and relevant)
Below is a minimal example of how Jetty 12 replaces input interception using a custom Content.Source wrapper.
import org.eclipse.jetty.server.*;
import org.eclipse.jetty.io.Content;
import java.nio.ByteBuffer;
public class FilteringSource implements Content.Source {
private final Content.Source next;
public FilteringSource(Content.Source next) {
this.next = next;
}
@Override
public Content.Chunk read() {
Content.Chunk chunk = next.read();
if (chunk == null || chunk.isLast()) {
return chunk;
}
ByteBuffer buf = chunk.getByteBuffer();
byte[] data = new byte[buf.remaining()];
buf.get(data);
// modify data here
byte[] modified = myFilter(data);
return Content.Chunk.from(ByteBuffer.wrap(modified), false);
}
}
How Senior Engineers Fix It
Experienced engineers replace the old interceptors by using Jetty 12’s new content pipeline:
1. Wrap the request body using Content.Source
- Implement a custom
Content.Source - Replace the request’s source via:
- A custom
Handler - A
Filterthat installs a newHttpChannel.Listener
- A custom
2. Wrap the response body using Content.Sink
- Implement a custom
Content.Sink - Attach it to the response before writing begins
3. Use Jetty 12’s Handler chain
Senior engineers typically introduce a custom Handler that:
- Reads the existing
Content.Source - Wraps it with a filtering implementation
- Replaces it on the request before the servlet executes
4. Avoid touching Jetty internals
Jetty 12 intentionally hides low‑level I/O details. The supported extension points are:
- Handlers
- Filters
- Content.Source / Content.Sink
- HttpChannel.Listener
Why Juniors Miss It
Junior engineers often struggle because:
- They expect Jetty 12 to be a drop‑in upgrade, not a redesign.
- They search for the old interceptor classes instead of the new content pipeline.
- They assume request/response body interception belongs inside the servlet layer.
- They overlook Jetty’s architectural shift toward stream‑based, non-blocking content handling.
The key takeaway: Jetty 12 requires a new mental model—interception now happens through content sources/sinks and handlers, not servlet I/O hooks.