Chaturmind
LearnDSASystem DesignInterview PrepDevOpsEngineering GrowthBlog
Start learning
Chaturmind

Structured learning paths for engineers who want to go deep. Written by practitioners.

Learn

  • Java
  • DSA
  • System Design
  • Spring Boot
  • AI / ML
  • DevOps
  • Engineering Growth
  • Java Interview Prep

Company

  • Blog
  • Contact

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Chaturmind. All rights reserved.

Built for engineers who want to go deep.


← Java Interview Prep: 5–8 Years

Advanced Core Java

  • Advanced OOP & Design Scenarios — Interview Questions
  • Advanced Concurrency, Collections & Memory — Interview Questions
  • Modern Java Language Features & Annotations — Interview Questions
  • Class Loading, Reflection, Serialization & Idioms — Interview Questions

Java Design Patterns in Depth

  • Design Patterns Overview & Singleton — Interview Questions
  • Factory & Abstract Factory Patterns — Interview Questions
  • Builder & Prototype Patterns — Interview Questions
  • Adapter & Bridge Patterns — Interview Questions
  • Composite & Decorator Patterns — Interview Questions
  • Facade & Proxy Patterns — Interview Questions
  • Chain of Responsibility & Observer Patterns — Interview Questions
  • Strategy & Template Method Patterns — Interview Questions
  • Command Pattern — Interview Questions

Advanced Spring Boot

  • Logging, Configuration & Actuator (Advanced) — Interview Questions
  • Transactions, Multiple Datasources & Query Tuning — Interview Questions
  • Validation & REST API Design (Advanced) — Interview Questions
  • Reactive, Async & Scheduling in Spring Boot — Interview Questions
  • Deployment, High Availability, Scaling & Caching — Interview Questions
Chaturmind
← Java Interview Prep: 5–8 Years

Advanced Core Java

  • Advanced OOP & Design Scenarios — Interview Questions
  • Advanced Concurrency, Collections & Memory — Interview Questions
  • Modern Java Language Features & Annotations — Interview Questions
  • Class Loading, Reflection, Serialization & Idioms — Interview Questions

Java Design Patterns in Depth

  • Design Patterns Overview & Singleton — Interview Questions
  • Factory & Abstract Factory Patterns — Interview Questions
  • Builder & Prototype Patterns — Interview Questions
  • Adapter & Bridge Patterns — Interview Questions
  • Composite & Decorator Patterns — Interview Questions
  • Facade & Proxy Patterns — Interview Questions
  • Chain of Responsibility & Observer Patterns — Interview Questions
  • Strategy & Template Method Patterns — Interview Questions
  • Command Pattern — Interview Questions

Advanced Spring Boot

  • Logging, Configuration & Actuator (Advanced) — Interview Questions
  • Transactions, Multiple Datasources & Query Tuning — Interview Questions
  • Validation & REST API Design (Advanced) — Interview Questions
  • Reactive, Async & Scheduling in Spring Boot — Interview Questions
  • Deployment, High Availability, Scaling & Caching — Interview Questions
HomeLearnJava Interview PrepJava Interview Prep: 5–8 YearsJava Design Patterns in Depth
✓ FreeAdvanced· 7 min read

Chain of Responsibility & Observer Patterns — Interview Questions

Chain of Responsibility — how it works, Java implementation, real uses (filters, approvals, validation), loose coupling and drawbacks — and Observer — purpose, why java.util.Observer/Observable are deprecated and what to use instead, Observer vs Pub/Sub, staggered notifications, and thread-safety challenges.

Published September 25, 2026


How to use this lesson

Both patterns are everywhere in Java frameworks: servlet filters and the Spring Security filter chain are Chains of Responsibility, and Spring application events and UI listeners are Observers. Use those as your examples, and be ready for the concurrency follow-ups on Observer.

Q1. What is the Chain of Responsibility pattern for?

Short answer: It passes a request along a chain of handlers. Each handler either handles it, passes it on, or does some work and passes it on. The sender doesn't know which handler (or how many) will process it, which decouples the sender from the receivers.

Learn it in depth → Chain of Responsibility

Q2. What is the Observer pattern for?

Short answer: Observer defines a one-to-many dependency. When a subject changes state, all registered observers are notified automatically, and the subject doesn't know what they do. It's used for event listeners, UI updates, domain events and cache invalidation.

Learn it in depth → Observer Pattern

Q3. How does Chain of Responsibility work?

Short answer: Handlers share an interface (handle(request)), and each knows its successor, or the chain is iterated by a runner. Two common variants:

  • "First one wins": stop at the first handler that can handle the request, as in support escalation, or picking a payment method.
  • "Pipeline": every handler does its part, and may short-circuit, as with servlet filters, validation steps and security checks.

Q4. How do you implement Chain of Responsibility in Java?

Short answer: In modern Java, a list of handler beans, iterated in order, is usually cleaner than linked next references:

public interface RefundRule {                      // the handler
    Optional<Decision> evaluate(RefundRequest r);  // empty = "not my concern, continue"
}

@Component @Order(1) class FraudHoldRule implements RefundRule {
    public Optional<Decision> evaluate(RefundRequest r) { return r.flaggedForFraud() ? Optional.of(Decision.manualReview("fraud")) : Optional.empty(); }
}
@Component @Order(2) class AutoApproveSmallRule implements RefundRule {
    public Optional<Decision> evaluate(RefundRequest r) { return r.amount().compareTo(new BigDecimal("1000")) <= 0 ? Optional.of(Decision.approve()) : Optional.empty(); }
}

@Service
class RefundDecider {
    private final List<RefundRule> rules;                                   // Spring injects them in @Order order
    RefundDecider(List<RefundRule> rules) { this.rules = rules; }
    Decision decide(RefundRequest r) {
        return rules.stream().map(rule -> rule.evaluate(r)).flatMap(Optional::stream)
                    .findFirst().orElse(Decision.manualReview("no rule matched"));   // explicit default: never unhandled
    }
}

Q5. Give an example of when you'd use Chain of Responsibility.

Short answer:

  • Support-ticket escalation: L1, then L2, then a manager.
  • Approval workflows by amount: team lead, then manager, then finance.
  • Request processing pipelines: authentication, rate limiting, validation, logging (servlet filters, Spring Security's SecurityFilterChain, Netty handlers, OkHttp interceptors).
  • Exception handlers, and logging appenders.

Q6. How does Chain of Responsibility promote loose coupling?

Short answer: The sender only knows the chain's entry point, not the concrete handlers. Handlers don't know about each other either, beyond "next". You can add, remove or reorder handlers (often through configuration or @Order) without changing the sender or the other handlers. Each handler has one responsibility.

Q7. What are the drawbacks of Chain of Responsibility?

Short answer:

  • A request can pass through many handlers, which costs latency.
  • Nobody may handle it, unless you add an explicit default or terminal handler.
  • Order dependence: a subtle bug source when handlers are reordered.
  • Debugging "who handled this?" is harder, so log or trace each handler's decision.
  • Long chains can hide business logic.

Q8. What is the Observer pattern, and when do you use it?

Short answer: Use it when several independent parts must react to a change, and the source shouldn't depend on them. For example, when an order is placed: send an email, award loyalty points, update analytics. In Spring:

@Service
class OrderService {
    private final ApplicationEventPublisher events;
    @Transactional public Order place(Cart c) {
        Order o = repo.save(Order.from(c));
        events.publishEvent(new OrderPlaced(o.getId()));    // the subject doesn't know its observers
        return o;
    }
}
@Component class LoyaltyObserver {
    @TransactionalEventListener void on(OrderPlaced e) { loyalty.award(e.orderId()); }
}

Q9. How does the Observer pattern work in Java with Observer and Observable?

Short answer: java.util.Observable was a class that kept a list of Observers. The subject called setChanged() then notifyObservers(arg), and each observer's update(Observable, Object) ran. These types have been deprecated since Java 9:

  • Observable is a class, so subjects can't extend anything else.
  • Notifications are untyped (Object arg).
  • The ordering and threading semantics are weak.
  • They aren't serializable-safe.

Use instead:

  • Your own typed listener interfaces (OrderListener), with a CopyOnWriteArrayList of listeners.
  • java.beans.PropertyChangeSupport, for property changes.
  • java.util.concurrent.Flow (reactive streams), or Reactor, for asynchronous streams with back-pressure.
  • Spring application events.

Common trap: presenting Observer/Observable as the recommended Java mechanism today.

Q10. What's the difference between the Observer pattern and Pub/Sub?

Short answer:

  • Observer: observers register directly with the subject, in-process. Notification is usually synchronous, and the subject holds references to the observers.
  • Pub/Sub: publishers and subscribers are decoupled by an intermediary: an event bus or a message broker (Kafka, RabbitMQ). They don't know each other, which gives asynchronous, often durable and distributed delivery, with topic-based routing.

Pub/Sub scales across services and survives consumer downtime. Observer is simpler, immediate and local.

Q11. How do you handle observers that must be updated at different times?

Short answer:

  • Asynchronous dispatch: each observer, or group, gets its own executor or queue, so slow ones don't delay fast ones.
  • Priorities or ordering (@Order on listeners).
  • Filtering (conditional listeners: @EventListener(condition = "#e.amount > 1000")).
  • Batching or debouncing for noisy events.
  • Scheduled digests, or transaction phases (@TransactionalEventListener(phase = AFTER_COMMIT)).

For cross-service or durable timing, publish to a broker, and let each consumer process at its own pace.

Q12. What are the challenges of Observer in multithreaded environments?

Short answer:

  • Concurrent registration and notification: iterating the listener list while others add or remove listeners can throw ConcurrentModificationException. Use CopyOnWriteArrayList.
  • Deadlocks: don't hold the subject's lock while calling observers, because they may call back or take other locks. Copy the list, release the lock, then notify.
  • Inconsistent state: observers may see a newer or older state than the event describes. Pass immutable event objects carrying the relevant data.
  • Slow or failing observers block or break the others. Isolate them (async executors, try/catch per observer).
  • Ordering isn't guaranteed across threads.
  • Memory leaks from listeners that are never unregistered. Use weak references, or explicit lifecycle management.
private final List<PriceListener> listeners = new CopyOnWriteArrayList<>();
void publish(PriceChanged event) {                       // immutable event, no lock held while notifying
    for (PriceListener l : listeners) {
        try { l.onPriceChanged(event); } catch (RuntimeException ex) { log.warn("listener failed", ex); }
    }
}

Follow-up questions this topic invites — and their answers

Q: How do Spring's synchronous event listeners behave with exceptions? A: They run in the publisher's thread. An exception propagates to the publisher, and can roll back its transaction. Use @Async and/or @TransactionalEventListener(AFTER_COMMIT) for side effects that must not affect the main flow.

Q: Chain of Responsibility vs Decorator? A: Both wrap or forward calls. Decorators always delegate, and add behaviour, while a chain handler may stop the request (handle it or reject it). Chains are also usually assembled as lists, and decorators as nested wrappers.

Q: How do you make an event-driven flow observable in production? A: Give every event an ID and a correlation (trace) ID, log each listener's handling, and use tracing across asynchronous boundaries. Monitor queue lag and dead-letter counts for broker-based pub/sub.

Q: What is the "lapsed listener" problem? A: A listener that's registered but never removed keeps its owner reachable, so it's never garbage-collected, and it keeps receiving events. Tie listener registration to lifecycle callbacks, or use weak references.

Previous

Facade & Proxy Patterns — Interview Questions

Next

Strategy & Template Method Patterns — Interview Questions

AI Tutor

Lesson: Chain of Responsibility & Observer Patterns — Interview Questions

Quick actions

AI responses can be inaccurate. Verify critical information.