Decorator Pattern in Java with Real-World Examples

📘 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.

By Ramesh Fadatare (Java Guides)

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 like MetricsPaymentService, RetryPaymentService, and so on — all without changing the core BasicPaymentService.

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 file
  • InputStreamReader: converts bytes to characters
  • BufferedReader: 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

Spring Boot 3 Paid Course Published for Free
on my Java Guides YouTube Channel

Subscribe to my YouTube Channel (165K+ subscribers):
Java Guides Channel

Top 10 My Udemy Courses with Huge Discount:
Udemy Courses - Ramesh Fadatare