📘 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
👋 Introduction
In Java applications, it’s common to deal with events or changes that other parts of the system need to react to. For example:
- When a user places an order, you might need to send an email, update inventory, and log the event.
- When a user updates their profile, other components might need to sync that change elsewhere.
Doing all of that in one method can lead to tightly coupled code and poor separation of concerns.
This is where the Observer Pattern comes in.
It allows you to build a publish/subscribe system where observers automatically get notified when something changes — without the subject needing to know who they are or what they do.
In this article, we’ll explore:
- What the Observer Pattern is
- A real-world Java example
- A Spring Boot use case
- When and why to use it
- When not to use it
Let’s get started.
What is the Observer Pattern?
The Observer Pattern defines a one-to-many relationship between objects, so that when one object (the "subject") changes state, all its dependents (observers) are notified and updated automatically.
You have two key parts:
- Subject: The main object that holds the data or triggers the event
- Observer(s): Components that want to listen for changes or updates
Think of it like YouTube: When you subscribe to a channel, you get notified whenever they upload a new video.
❌ The Problem Without Observer Pattern
Let’s say you have a system where users place orders, and you need to:
- Save the order to the database
- Send a confirmation email
- Notify the shipping team
- Log the activity
Without the Observer Pattern, you might write everything in the same method:
public void placeOrder(Order order) {
saveToDatabase(order);
sendConfirmationEmail(order);
notifyShipping(order);
logOrder(order);
}
❌ This mixes responsibilities ❌ Hard to maintain ❌ Adding a new behavior requires editing this method
✅ Observer Pattern Fix
With the Observer Pattern, you separate each responsibility into its own class and let them "subscribe" to order events. When an order is placed, each observer is notified automatically.
Real-World Java Example: Order Event System
Let’s walk through a basic implementation.
Step 1: Define the Observer Interface
public interface OrderObserver {
void onOrderPlaced(Order order);
}
Step 2: Create the Subject (Publisher)
public class OrderService {
private final List<OrderObserver> observers = new ArrayList<>();
public void addObserver(OrderObserver observer) {
observers.add(observer);
}
public void placeOrder(Order order) {
System.out.println("Placing order: " + order.getId());
// Notify all observers
for (OrderObserver observer : observers) {
observer.onOrderPlaced(order);
}
}
}
Step 3: Create Observer Implementations
public class EmailNotifier implements OrderObserver {
public void onOrderPlaced(Order order) {
System.out.println("Sending email for order: " + order.getId());
}
}
public class InventoryUpdater implements OrderObserver {
public void onOrderPlaced(Order order) {
System.out.println("Updating inventory for order: " + order.getId());
}
}
public class OrderLogger implements OrderObserver {
public void onOrderPlaced(Order order) {
System.out.println("Logging order: " + order.getId());
}
}
Step 4: Putting It All Together
public class Main {
public static void main(String[] args) {
Order order = new Order("ORD-123");
OrderService orderService = new OrderService();
orderService.addObserver(new EmailNotifier());
orderService.addObserver(new InventoryUpdater());
orderService.addObserver(new OrderLogger());
orderService.placeOrder(order);
}
}
Order Class (for completeness)
public class Order {
private final String id;
public Order(String id) {
this.id = id;
}
public String getId() {
return id;
}
}
Output:
Placing order: ORD-123
Sending email for order: ORD-123
Updating inventory for order: ORD-123
Logging order: ORD-123
✅ Each observer is separate ✅ Easy to add/remove observers ✅ OrderService doesn’t care who listens
Spring Boot Use Case: Application Events
Spring Boot provides built-in support for the Observer Pattern using ApplicationEventPublisher and @EventListener.
Let’s implement a similar order event system using Spring Boot.
Step 1: Define the OrderPlacedEvent
public class OrderPlacedEvent extends ApplicationEvent {
private final String orderId;
public OrderPlacedEvent(Object source, String orderId) {
super(source);
this.orderId = orderId;
}
public String getOrderId() {
return orderId;
}
}
Step 2: Publish the Event
@Service
public class OrderService {
private final ApplicationEventPublisher publisher;
public OrderService(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
public void placeOrder(String orderId) {
System.out.println("Placing order: " + orderId);
// Publish event
publisher.publishEvent(new OrderPlacedEvent(this, orderId));
}
}
Step 3: Create Observers Using @EventListener
@Component
public class EmailListener {
@EventListener
public void handle(OrderPlacedEvent event) {
System.out.println("Email: Order " + event.getOrderId() + " received.");
}
}
@Component
public class InventoryListener {
@EventListener
public void handle(OrderPlacedEvent event) {
System.out.println("Inventory: Reserved items for order " + event.getOrderId());
}
}
@Component
public class LoggerListener {
@EventListener
public void handle(OrderPlacedEvent event) {
System.out.println("Log: Order event for " + event.getOrderId());
}
}
Step 4: Controller to Trigger Order
@RestController
@RequestMapping("/orders")
public class OrderController {
private final OrderService service;
public OrderController(OrderService service) {
this.service = service;
}
@PostMapping("/{id}")
public ResponseEntity<String> placeOrder(@PathVariable String id) {
service.placeOrder(id);
return ResponseEntity.ok("Order " + id + " placed.");
}
}
Request:
POST /orders/ORD-456
Output:
Placing order: ORD-456
Email: Order ORD-456 received.
Inventory: Reserved items for order ORD-456
Log: Order event for ORD-456
✅ No tight coupling between components ✅ Easily add/remove listeners ✅ Follows Spring best practices
✅ Benefits of the Observer Pattern
Problem Without Observer | Observer Pattern Fix |
---|---|
All logic in one method | Each action in its own class |
Hard to test or extend | Observers are independent and testable |
High coupling between classes | Loose coupling through publish-subscribe |
Changes require method rewrite | Just add or remove observers |
✅ When to Use the Observer Pattern
Use it when:
- You have multiple components reacting to the same event
- You want loose coupling between the event source and handlers
- You need dynamic subscription behavior
- You are working in an event-driven system (e.g., order events, login events)
🚫 When Not to Use It
Avoid it when:
- You only have one or two consumers — simple method calls might be enough
- You need strict execution order — observer order isn’t guaranteed
- You want tight control over the logic path
- You are handling sensitive workflows where auditing or rollback is important
🧪 Testability
Observer Pattern improves testability:
- You can test each observer in isolation
- No need to mock unrelated logic
- You can verify whether an event is triggered using mock frameworks
Example:
@Test
void testEmailNotification() {
OrderPlacedEvent event = new OrderPlacedEvent(this, "ORD-789");
EmailListener listener = new EmailListener();
listener.handle(event);
// assert output or verify behavior
}
🏁 Conclusion
The Observer Pattern is a practical and powerful tool in Java. It helps you build clean, extensible systems by:
- Decoupling logic
- Improving maintainability
- Supporting real-time reactions
- Scaling without rewriting code
Whether you're building your own observer system or using Spring’s event model, the Observer Pattern makes your codebase more modular and easier to evolve.
That’s why Java developers rely on it — especially in systems that are growing, changing, or event-driven.
Comments
Post a Comment
Leave Comment