Decorator Pattern explained simply
When you want to add new functionalities to an object without modifying its structure, the Decorator Pattern comes to the rescue.
The Decorator Pattern lets you dynamically wrap objects with new behavior. Think of it like HTTP middleware: each layer wraps the handler beneath it, doing something useful before or after the actual request processing, without touching the original handler at all.
What is the Decorator Pattern?
At its core:
- Attach additional responsibilities to an object dynamically.
- A flexible alternative to subclassing for extending functionality.
- You can "decorate" objects multiple times with different decorators.
If you have ever stacked middleware in a web framework -- logging, authentication, compression, rate limiting -- you have already used the Decorator Pattern, whether you realized it or not.
Here is a UML class diagram showing how the pieces fit together. Every decorator implements HttpHandler and wraps another HttpHandler, so the client never knows how many layers are in the stack.
<mxfile>
<diagram name="Decorator Pattern">
<mxGraphModel dx="1100" dy="780" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1100" pageHeight="700">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<!-- HttpHandler interface -->
<mxCell id="2" value="<<interface>>
HttpHandler
─────────────────
+ handle(request: HttpRequest): HttpResponse" style="shape=rectangle;whiteSpace=wrap;html=1;align=center;fontSize=12;fontFamily=monospace;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
<mxGeometry x="400" y="30" width="300" height="80" as="geometry"/>
</mxCell>
<!-- BaseHandler -->
<mxCell id="3" value="BaseHandler
─────────────────
+ handle(request): HttpResponse" style="shape=rectangle;whiteSpace=wrap;html=1;align=center;fontSize=12;fontFamily=monospace;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1">
<mxGeometry x="120" y="200" width="280" height="70" as="geometry"/>
</mxCell>
<!-- HttpHandlerDecorator abstract -->
<mxCell id="4" value="<<abstract>>
HttpHandlerDecorator
─────────────────
- wrapped: HttpHandler
+ handle(request): HttpResponse" style="shape=rectangle;whiteSpace=wrap;html=1;align=center;fontSize=12;fontFamily=monospace;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="1">
<mxGeometry x="500" y="200" width="300" height="90" as="geometry"/>
</mxCell>
<!-- LoggingDecorator -->
<mxCell id="5" value="LoggingDecorator
─────────────────
+ handle(request): HttpResponse" style="shape=rectangle;whiteSpace=wrap;html=1;align=center;fontSize=12;fontFamily=monospace;fillColor=#f8cecc;strokeColor=#b85450;" vertex="1" parent="1">
<mxGeometry x="330" y="380" width="250" height="70" as="geometry"/>
</mxCell>
<!-- AuthenticationDecorator -->
<mxCell id="6" value="AuthenticationDecorator
─────────────────
+ handle(request): HttpResponse" style="shape=rectangle;whiteSpace=wrap;html=1;align=center;fontSize=12;fontFamily=monospace;fillColor=#f8cecc;strokeColor=#b85450;" vertex="1" parent="1">
<mxGeometry x="620" y="380" width="260" height="70" as="geometry"/>
</mxCell>
<!-- CompressionDecorator -->
<mxCell id="7" value="CompressionDecorator
─────────────────
+ handle(request): HttpResponse" style="shape=rectangle;whiteSpace=wrap;html=1;align=center;fontSize=12;fontFamily=monospace;fillColor=#f8cecc;strokeColor=#b85450;" vertex="1" parent="1">
<mxGeometry x="50" y="380" width="250" height="70" as="geometry"/>
</mxCell>
<!-- BaseHandler implements HttpHandler -->
<mxCell id="8" style="endArrow=block;dashed=1;endFill=0;strokeColor=#6c8ebf;" edge="1" source="3" target="2" parent="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<!-- HttpHandlerDecorator implements HttpHandler -->
<mxCell id="9" style="endArrow=block;dashed=1;endFill=0;strokeColor=#6c8ebf;" edge="1" source="4" target="2" parent="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<!-- HttpHandlerDecorator has-a HttpHandler (composition) -->
<mxCell id="10" value="wraps" style="endArrow=diamond;endFill=1;strokeColor=#d6b656;" edge="1" source="4" target="2" parent="1">
<mxGeometry x="0.2" relative="1" as="geometry">
<mxPoint as="offset"/>
</mxGeometry>
</mxCell>
<!-- LoggingDecorator extends HttpHandlerDecorator -->
<mxCell id="11" style="endArrow=block;endFill=0;strokeColor=#b85450;" edge="1" source="5" target="4" parent="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<!-- AuthenticationDecorator extends HttpHandlerDecorator -->
<mxCell id="12" style="endArrow=block;endFill=0;strokeColor=#b85450;" edge="1" source="6" target="4" parent="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<!-- CompressionDecorator extends HttpHandlerDecorator -->
<mxCell id="13" style="endArrow=block;endFill=0;strokeColor=#b85450;" edge="1" source="7" target="4" parent="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
A Real-Life Analogy
Think about ordering coffee. You start with an espresso, then add milk, then sugar, then whipped cream. Each add-on wraps the base drink and adds something extra without changing the original recipe.
The same thing happens in software. You start with a basic HTTP handler, then wrap it with logging, then authentication, then compression. Each layer does its own thing and delegates to the next one down.
Structure
- Component (
HttpHandler): The interface that both the real object and decorators implement. - ConcreteComponent (
BaseHandler): The basic object that actually handles the request. - Decorator (
HttpHandlerDecorator): An abstract class that implements the component interface and holds a reference to a wrapped component. - ConcreteDecorators (
LoggingDecorator,AuthenticationDecorator,CompressionDecorator): Each adds specific behavior before or after delegating to the wrapped handler.
Java Example: HTTP Middleware Chain
Let's build a real-world example. We will create an HTTP handler pipeline where each decorator adds a cross-cutting concern.
First, some simple models for requests and responses:
public class HttpRequest {
private final String path;
private final String method;
private final Map<String, String> headers;
private final String body;
public HttpRequest(String path, String method, Map<String, String> headers, String body) {
this.path = path;
this.method = method;
this.headers = headers;
this.body = body;
}
public String getPath() { return path; }
public String getMethod() { return method; }
public String getHeader(String key) { return headers.get(key); }
public String getBody() { return body; }
}
public class HttpResponse {
private final int statusCode;
private final String body;
public HttpResponse(int statusCode, String body) {
this.statusCode = statusCode;
this.body = body;
}
public int getStatusCode() { return statusCode; }
public String getBody() { return body; }
}
Now define the HttpHandler interface. This is the Component -- the contract that everything in the chain will follow:
public interface HttpHandler {
HttpResponse handle(HttpRequest request);
}
The BaseHandler is our ConcreteComponent. It does the actual work of producing a response:
public class BaseHandler implements HttpHandler {
@Override
public HttpResponse handle(HttpRequest request) {
return new HttpResponse(200, "Hello from " + request.getPath());
}
}
Here is the abstract Decorator. It implements HttpHandler and wraps another HttpHandler. By default, it just delegates straight through:
public abstract class HttpHandlerDecorator implements HttpHandler {
protected final HttpHandler wrapped;
public HttpHandlerDecorator(HttpHandler wrapped) {
this.wrapped = wrapped;
}
@Override
public HttpResponse handle(HttpRequest request) {
return wrapped.handle(request);
}
}
Now the fun part -- the concrete decorators. Each one hooks in before or after the delegation call.
LoggingDecorator logs the incoming request and the outgoing response:
public class LoggingDecorator extends HttpHandlerDecorator {
public LoggingDecorator(HttpHandler wrapped) {
super(wrapped);
}
@Override
public HttpResponse handle(HttpRequest request) {
System.out.println("[LOG] " + request.getMethod() + " " + request.getPath());
HttpResponse response = wrapped.handle(request);
System.out.println("[LOG] Response: " + response.getStatusCode());
return response;
}
}
AuthenticationDecorator checks for a valid auth token. If the token is missing, it short-circuits the chain and returns a 401. The handler below it never gets called:
public class AuthenticationDecorator extends HttpHandlerDecorator {
public AuthenticationDecorator(HttpHandler wrapped) {
super(wrapped);
}
@Override
public HttpResponse handle(HttpRequest request) {
String token = request.getHeader("Authorization");
if (token == null || !token.startsWith("Bearer ")) {
return new HttpResponse(401, "Unauthorized: missing or invalid token");
}
return wrapped.handle(request);
}
}
CompressionDecorator lets the request pass through, then compresses the response body on the way back:
public class CompressionDecorator extends HttpHandlerDecorator {
public CompressionDecorator(HttpHandler wrapped) {
super(wrapped);
}
@Override
public HttpResponse handle(HttpRequest request) {
HttpResponse response = wrapped.handle(request);
String compressed = gzipCompress(response.getBody());
System.out.println("[COMPRESS] " + response.getBody().length()
+ " bytes -> " + compressed.length() + " bytes");
return new HttpResponse(response.getStatusCode(), compressed);
}
private String gzipCompress(String data) {
// In a real implementation, this would use GZIPOutputStream.
// Simplified here for clarity.
return "gzip(" + data + ")";
}
}
What's happening here? Each decorator only cares about its own job -- logging, authentication, or compression. It doesn't know or care what other decorators exist in the chain. That's the beauty of it.
Stacking the Decorators
Now let's wire it all together. The order matters: the outermost decorator runs first.
public class MainProgram {
public static void main(String[] args) {
// Build the handler chain: Compression -> Auth -> Logging -> BaseHandler
HttpHandler handler = new CompressionDecorator(
new AuthenticationDecorator(
new LoggingDecorator(
new BaseHandler()
)
)
);
// Simulate an authenticated request
Map<String, String> headers = Map.of("Authorization", "Bearer abc123");
HttpRequest request = new HttpRequest("/api/users", "GET", headers, "");
HttpResponse response = handler.handle(request);
System.out.println("Final response: " + response.getStatusCode()
+ " - " + response.getBody());
}
}
Output:
[LOG] GET /api/users
[LOG] Response: 200
[COMPRESS] 21 bytes -> 27 bytes
Final response: 200 - gzip(Hello from /api/users)
The request flows inward through each decorator until it hits BaseHandler, then the response flows back outward. Logging sees it first and last, auth checks the token, and compression squeezes the response. Swap the order, remove a layer, add a new one -- the rest of the code never changes.
If the token was missing, the auth decorator would have returned a 401 and BaseHandler would never have been called. That's a decorator short-circuiting the chain.
Why Use the Decorator Pattern?
- Flexible and Scalable: Add new concerns (rate limiting, caching, CORS headers) without touching existing handlers.
- Avoids Class Explosion: No need to create
LoggingAuthHandler,LoggingCompressionHandler,AuthCompressionHandler, and every other permutation. - Single Responsibility Principle: Each decorator does exactly one thing.
- Runtime Composition: You can build different middleware stacks for different endpoints at runtime rather than at compile time.
Real-World Use Cases
This pattern is everywhere:
- Java Servlet Filters: Each filter wraps the next one in the chain, adding authentication, logging, or encoding.
- Spring HandlerInterceptors: Pre-handle and post-handle hooks that wrap your controller logic.
- Express.js middleware:
app.use(cors()),app.use(morgan('dev')),app.use(authenticate)-- each one is a decorator around the next. - Java I/O Streams:
new BufferedReader(new InputStreamReader(new FileInputStream("file.txt")))-- the classic textbook example. - Python WSGI middleware: Wrap a WSGI app with layers for session handling, error reporting, and static file serving.
Summary
The Decorator Pattern lets you dynamically add behavior to objects without modifying them. In the HTTP middleware world, this means you can stack logging, authentication, compression, and whatever else you need as independent, reusable layers around a simple base handler.
It's a cleaner, more flexible alternative to subclassing. Instead of baking every combination of behavior into a class hierarchy, you compose the behavior you need at runtime. Your codebase stays extensible and maintainable, and adding a new concern is just one more wrapper.