📘 Premium Read: Access my best content on Medium member-only articles — deep dives into Java, Spring Boot, Microservices, backend architecture, interview preparation, career advice, and industry-standard best practices.
🎓 Top 15 Udemy Courses (80-90% Discount): My Udemy Courses - Ramesh Fadatare — All my Udemy courses are real-time and project oriented courses.
▶️ Subscribe to My YouTube Channel (176K+ subscribers): Java Guides on YouTube
▶️ For AI, ChatGPT, Web, Tech, and Generative AI, subscribe to another channel: Ramesh Fadatare on YouTube
The Decorator Pattern is a solid, real-world solution for extending behavior in Java applications — without modifying existing code.

In Java development, it’s common to enhance or modify the behavior of objects — without changing their class. This is especially true in backend systems where functionality often needs to be added dynamically, like logging, validation, caching, or monitoring.
The Decorator Pattern makes this easy and clean.
In this article, we’ll look at:
- What the Decorator Pattern is
- What problem it solves
- A real-world example in Java
- A Spring Boot use case
- When to use (and when not to)
Let’s dive in.
The Problem: Inheritance Can Be Limiting
Say you’re building a notification system. You start with an interface:
public interface Notifier {
void send(String message);
}
Now, you create a basic implementation:
public class EmailNotifier implements Notifier {
public void send(String message) {
System.out.println("Email: " + message);
}
}
Later, your team asks for additional features:
- Log each notification
- Send SMS alongside email
- Add retry logic
You could subclass EmailNotifier
, but it becomes messy if you need combinations:
- Email + Logging
- Email + SMS
- Email + Retry + Logging
You end up with multiple subclasses for every combination.
❌ Inheritance is rigid
❌ Hard to extend
❌ Doesn’t follow Single Responsibility Principle
The Decorator Pattern Fix
The Decorator Pattern solves this by allowing you to “wrap” an object with other objects that add behavior.
Instead of modifying the original class, you create a chain of objects, each responsible for one piece of functionality.
It promotes composition over inheritance.
Real-World Java Example: Notifier System
Let’s build a simple notification system using the Decorator Pattern.
Step 1: Create the Notifier Interface
public interface Notifier {
void send(String message);
}
Step 2: Basic Email Notifier
public class EmailNotifier implements Notifier {
public void send(String message) {
System.out.println("Sending EMAIL: " + message);
}
}
Step 3: Abstract Decorator
public abstract class NotifierDecorator implements Notifier {
protected Notifier wrapped;
public NotifierDecorator(Notifier wrapped) {
this.wrapped = wrapped;
}
public void send(String message) {
wrapped.send(message);
}
}
Step 4: Add Logging Decorator
public class LoggingNotifier extends NotifierDecorator {
public LoggingNotifier(Notifier wrapped) {
super(wrapped);
}
public void send(String message) {
System.out.println("LOG: Sending notification -> " + message);
super.send(message);
}
}
Step 5: Add SMS Decorator
public class SmsNotifier extends NotifierDecorator {
public SmsNotifier(Notifier wrapped) {
super(wrapped);
}
public void send(String message) {
super.send(message);
System.out.println("Sending SMS: " + message);
}
}
Step 6: Add Retry Decorator
public class RetryNotifier extends NotifierDecorator {
public RetryNotifier(Notifier wrapped) {
super(wrapped);
}
public void send(String message) {
try {
super.send(message);
} catch (Exception e) {
System.out.println("Retrying...");
super.send(message);
}
}
}
Step 7: Usage
public class Main {
public static void main(String[] args) {
Notifier notifier = new LoggingNotifier(
new SmsNotifier(
new RetryNotifier(
new EmailNotifier()
)
)
);
notifier.send("Your invoice is ready.");
}
}
Output:
LOG: Sending notification -> Your invoice is ready.
Sending EMAIL: Your invoice is ready.
Sending SMS: Your invoice is ready.
✅ No subclassing
✅ Each decorator adds one responsibility
✅ You can mix and match behaviors easily
Real-World Scenario: Order Processing
Imagine you’re processing an order and want to apply additional steps like:
- Fraud check
- Logging
- Metrics tracking
- Notification
Each of these could be modeled as a decorator over a basic order processor.
This makes the system easy to extend without modifying the core logic.
Spring Boot Example: Wrapping Services
Let’s say you have a service that processes payments:
public interface PaymentService {
void process(String orderId);
}
Step 1: Basic Service
@Service
public class BasicPaymentService implements PaymentService {
public void process(String orderId) {
System.out.println("Processing payment for order: " + orderId);
}
}
Step 2: Logging Decorator
@Component
@Primary
public class LoggingPaymentService implements PaymentService {
private final PaymentService delegate;
public LoggingPaymentService(BasicPaymentService delegate) {
this.delegate = delegate;
}
public void process(String orderId) {
System.out.println("LOG: Payment processing started");
delegate.process(orderId);
System.out.println("LOG: Payment processing finished");
}
}
Step 3: Controller
@RestController
@RequestMapping("/payment")
public class PaymentController {
private final PaymentService service;
public PaymentController(PaymentService service) {
this.service = service;
}
@PostMapping("/{orderId}")
public ResponseEntity<String> pay(@PathVariable String orderId) {
service.process(orderId);
return ResponseEntity.ok("Payment done");
}
}
Now, when /payment/123
is called, you’ll see:
LOG: Payment processing started
Processing payment for order: 123
LOG: Payment processing finished
You can add more decorators likeMetricsPaymentService
,RetryPaymentService
, and so on — all without changing the coreBasicPaymentService
.
Benefits of the Decorator Pattern

Decorator in Java’s Standard Library
Java already uses the Decorator Pattern in its standard APIs.
Example: BufferedReader
BufferedReader reader = new BufferedReader(
new InputStreamReader(
new FileInputStream("file.txt")));
Each class adds behavior:
FileInputStream
: reads bytes from fileInputStreamReader
: converts bytes to charactersBufferedReader
: adds buffering
Same idea — small components, composed together.
✅ When to Use the Decorator Pattern
Use it when:
- You want to add responsibilities without changing the class
- You want to avoid subclassing for every combination
- You want flexible, composable behaviors
- You follow the Single Responsibility Principle
🚫 When Not to Use It
Avoid it when:
- You don’t have shared interfaces
- There’s no need to dynamically add features
- You have too many small wrappers that hurt readability
- Simpler alternatives like configuration or DI would suffice
Conclusion
The Decorator Pattern is a solid, real-world solution for extending behavior in Java applications — without modifying existing code.
Instead of putting everything in one class or using rigid inheritance trees, decorators let you:
- Add logging, retry, validation, monitoring
- Compose behavior flexibly
- Follow good design principles
It’s widely used in Java I/O, Spring Boot service layers, message processing, and notification systems.
Once you start applying it, you’ll find your code becomes easier to maintain and extend — which is exactly why Java developers rely on the Decorator Pattern in production systems.
Comments
Post a Comment
Leave Comment