Decorator Design Pattern in Java
Learn Decorator Design Pattern in Java with real-world examples, Spring Security, Java IO, banking use cases, UML diagrams, code examples, benefits, limitations, and interview questions.
What You Will Learn
In this article, you'll learn:
- What is Decorator Pattern?
- Why do we need it?
- Problems it solves
- Real-world examples
- UML diagrams
- Java implementation
- Spring Framework examples
- Banking and Insurance use cases
- Java IO examples
- Benefits and limitations
- Interview questions
Introduction
In enterprise applications, requirements evolve continuously.
Today you may have:
Order Service
Tomorrow the business asks for:
Order Service
+ Logging
+ Security
+ Auditing
+ Metrics
+ Monitoring
+ Tracing
A naive solution would create many subclasses:
OrderService
LoggedOrderService
SecuredOrderService
AuditedOrderService
LoggedSecuredOrderService
LoggedSecuredAuditedOrderService
As features increase, the number of classes grows exponentially.
This problem is called:
Class Explosion
Decorator Pattern solves this problem elegantly.
What is Decorator Pattern?
Decorator is a Structural Design Pattern that allows behavior to be added dynamically to an object without modifying its source code.
Instead of changing the original object:
Modify Existing Class ❌
We wrap the object:
Wrap Existing Object ✅
and add additional behavior.
Purpose of Decorator Pattern
The main purpose is:
Add new functionality to an object dynamically at runtime.
Decorator follows:
Open For Extension
Closed For Modification
which is one of the core SOLID principles.
Real World Analogy
Imagine ordering coffee.
Base Coffee:
Simple Coffee
Optional add-ons:
Milk
Sugar
Chocolate
Caramel
Different customers choose different combinations.
Instead of creating:
MilkCoffee
SugarCoffee
MilkSugarCoffee
ChocolateMilkSugarCoffee
We dynamically add toppings.
Decorator works exactly the same way.
Problem Without Decorator
flowchart TD
A[Coffee]
A --> B[Milk Coffee]
A --> C[Sugar Coffee]
A --> D[Chocolate Coffee]
B --> E[Milk Sugar Coffee]
B --> F[Milk Chocolate Coffee]
C --> G[Sugar Chocolate Coffee]
As features increase:
Classes Increase Rapidly
Solution With Decorator
flowchart LR
A[Simple Coffee]
A --> B[Milk Decorator]
B --> C[Sugar Decorator]
C --> D[Chocolate Decorator]
Each decorator adds behavior independently.
Key Components
Component
Common interface.
Example:
Coffee
Concrete Component
Original implementation.
Example:
SimpleCoffee
Decorator
Base wrapper.
Example:
CoffeeDecorator
Concrete Decorators
Add specific behavior.
Examples:
MilkDecorator
SugarDecorator
ChocolateDecorator
UML Diagram
classDiagram
class Coffee {
<<interface>>
+getDescription()
+getCost()
}
class SimpleCoffee
class CoffeeDecorator
class MilkDecorator
class SugarDecorator
class ChocolateDecorator
Coffee <|.. SimpleCoffee
Coffee <|.. CoffeeDecorator
CoffeeDecorator <|-- MilkDecorator
CoffeeDecorator <|-- SugarDecorator
CoffeeDecorator <|-- ChocolateDecorator
CoffeeDecorator --> Coffee
Coffee Example
Step 1: Component Interface
public interface Coffee {
String getDescription();
double getCost();
}
Step 2: Concrete Component
public class SimpleCoffee implements Coffee {
@Override
public String getDescription() {
return "Simple Coffee";
}
@Override
public double getCost() {
return 5.0;
}
}
Step 3: Base Decorator
public abstract class CoffeeDecorator
implements Coffee {
protected Coffee coffee;
public CoffeeDecorator(
Coffee coffee) {
this.coffee = coffee;
}
}
Step 4: Milk Decorator
public class MilkDecorator
extends CoffeeDecorator {
public MilkDecorator(
Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return coffee.getDescription()
+ ", Milk";
}
@Override
public double getCost() {
return coffee.getCost() + 2;
}
}
Step 5: Sugar Decorator
public class SugarDecorator
extends CoffeeDecorator {
public SugarDecorator(
Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return coffee.getDescription()
+ ", Sugar";
}
@Override
public double getCost() {
return coffee.getCost() + 1;
}
}
Client Code
public class DecoratorDemo {
public static void main(String[] args) {
Coffee coffee =
new SimpleCoffee();
coffee =
new MilkDecorator(coffee);
coffee =
new SugarDecorator(coffee);
System.out.println(
coffee.getDescription());
System.out.println(
coffee.getCost());
}
}
Output
Simple Coffee, Milk, Sugar
8.0
Execution Flow
sequenceDiagram
participant Client
participant Sugar
participant Milk
participant Coffee
Client->>Sugar:getCost()
Sugar->>Milk:getCost()
Milk->>Coffee:getCost()
Coffee-->>Milk:5
Milk-->>Sugar:7
Sugar-->>Client:8
Banking Example
Money Transfer Processing
Base Functionality:
Transfer Money
Additional Features:
Fraud Detection
Audit Logging
Encryption
Compliance Validation
Decorator allows independent addition of each feature.
Banking Architecture
flowchart LR
A[Money Transfer]
A --> B[Fraud Decorator]
B --> C[Audit Decorator]
C --> D[Encryption Decorator]
D --> E[Compliance Decorator]
Insurance Example
Claim Processing
Core Functionality:
Claim Validation
Additional Processing:
Fraud Analysis
Risk Assessment
Audit Tracking
Notification
Each feature can be implemented as a decorator.
Spring Security Example
One of the best real-world examples.
Incoming Request:
HTTP Request
Processing Chain:
flowchart LR
A[Request]
A --> B[Authentication Filter]
B --> C[Authorization Filter]
C --> D[CSRF Filter]
D --> E[Exception Filter]
E --> F[Application]
Every filter decorates the request.
Servlet Wrapper Example
Java Servlet API heavily uses Decorator.
public class CustomRequestWrapper
extends HttpServletRequestWrapper {
public CustomRequestWrapper(
HttpServletRequest request) {
super(request);
}
@Override
public String getHeader(String name) {
return "Modified Header";
}
}
Java IO Example
Decorator Pattern is everywhere in Java IO.
InputStream input =
new FileInputStream("orders.txt");
input =
new BufferedInputStream(input);
input =
new DataInputStream(input);
Each wrapper adds new functionality.
Logging Decorator Example
public interface OrderService {
void createOrder();
}
Core Service
public class OrderServiceImpl
implements OrderService {
@Override
public void createOrder() {
System.out.println(
"Order Created");
}
}
Logging Decorator
public class LoggingDecorator
implements OrderService {
private final OrderService service;
public LoggingDecorator(
OrderService service) {
this.service = service;
}
@Override
public void createOrder() {
System.out.println(
"Logging Started");
service.createOrder();
System.out.println(
"Logging Completed");
}
}
Output
Logging Started
Order Created
Logging Completed
Microservices Example
API Gateway Request Processing
Request
→ Authentication
→ Authorization
→ Rate Limiting
→ Logging
→ Tracing
→ Service
Each layer acts like a decorator.
Framework Examples
| Framework | Example |
|---|---|
| Spring Security | Filter Chain |
| Servlet API | Request Wrapper |
| Java IO | BufferedInputStream |
| Java IO | DataInputStream |
| Kafka | Message Interceptors |
| SLF4J | Logging Wrappers |
Benefits
✅ Runtime Behavior Addition
✅ Avoids Class Explosion
✅ Composition Over Inheritance
✅ Open Closed Principle
✅ Reusable Components
✅ Easy Feature Addition
✅ Better Maintainability
Limitations
❌ Many Small Classes
❌ Deep Decorator Chains
❌ Harder Debugging
❌ Order Of Decorators Matters
When To Use
Use Decorator when:
- Features are optional
- Behavior changes at runtime
- Inheritance becomes complex
- Cross-cutting concerns exist
Examples:
- Logging
- Security
- Validation
- Monitoring
- Auditing
When Not To Use
Avoid Decorator when:
- Behavior never changes
- Simpler inheritance works
- Extra abstraction is unnecessary
Decorator vs Adapter
| Feature | Decorator | Adapter |
|---|---|---|
| Goal | Add Behavior | Change Interface |
| Client Interface | Same | Different |
| Focus | Enhancement | Compatibility |
Decorator vs Composite
| Feature | Decorator | Composite |
|---|---|---|
| Purpose | Add Functionality | Build Hierarchies |
| Structure | Wrapper | Tree |
| Example | Coffee Add-ons | File System |
Interview Questions
What is Decorator Pattern?
Decorator dynamically adds behavior to an object without modifying the original class.
Why use Decorator instead of inheritance?
To avoid class explosion and support runtime flexibility.
Real World Example?
Coffee with toppings.
Where is Decorator used in Java?
Java IO Streams.
Where is Decorator used in Spring?
Spring Security Filter Chain.
Difference Between Decorator and Adapter?
Decorator adds behavior.
Adapter changes interface.
Key Takeaways
- Decorator is a Structural Design Pattern.
- Adds functionality dynamically at runtime.
- Uses composition instead of inheritance.
- Prevents class explosion.
- Widely used in Spring Security, Java IO, API Gateways, and Microservices.
- Excellent for logging, monitoring, security, auditing, and validation.