Chain of responsibility design pattern example

πŸ“¦ Chain of Responsibility Pattern in Java — Step-by-Step Guide

The Chain of Responsibility (CoR) is a behavioral design pattern that allows a request to pass through a chain of handlers.
Each handler decides either to process the request or pass it to the next handler in the chain.

This pattern helps in decoupling senders and receivers — the sender doesn’t need to know which handler will eventually process the request.


🧠 Concept

  • A chain is a series of objects (handlers) connected together.

  • Each handler knows only about the next handler in the chain.

  • When a request comes in:

    • The current handler checks if it can handle it.

    • If yes → it processes it.

    • If not → it forwards it to the next handler in the chain.


🚚 Real-World Analogy — Parcel Delivery Service

Imagine a parcel service with branches in:

Bangalore → Bombay → Delhi

When a parcel is sent:

  • A Bangalore branch first receives it.

  • If the parcel is for Bombay, Bangalore forwards it to Bombay.

  • If it’s for Delhi, it goes from Bangalore → Bombay → Delhi.

Each branch only handles parcels for its location, and passes others onward.
That’s exactly how the Chain of Responsibility Pattern works!


⚙️ Implementation — Step by Step


🧩 Step 1: Create the Chain Interface

This defines the contract for all handlers.

package com.vinod.design.chain; public interface Chain { // Sets the next handler in the chain void setNextDestination(Chain nextDestination); // Processes the incoming parcel void processParcel(Parcel parcel); }

🧩 Step 2: Create Handler — Bangalore

package com.vinod.design.chain; public class Bangalore implements Chain { private Chain nextDestination; @Override public void setNextDestination(Chain nextDest) { this.nextDestination = nextDest; } @Override public void processParcel(Parcel parcel) { if (parcel.getDestination().equalsIgnoreCase("Bangalore")) { System.out.println("Processing parcel in Bangalore: " + parcel); } else { // Forward to next branch if not for Bangalore if (nextDestination != null) { nextDestination.processParcel(parcel); } } } }

🧩 Step 3: Create Handler — Bombay

package com.vinod.design.chain; public class Bombay implements Chain { private Chain nextDestination; @Override public void setNextDestination(Chain nextDest) { this.nextDestination = nextDest; } @Override public void processParcel(Parcel parcel) { if (parcel.getDestination().equalsIgnoreCase("Bombay")) { System.out.println("Processing parcel in Bombay: " + parcel); } else if (nextDestination != null) { nextDestination.processParcel(parcel); } } }

🧩 Step 4: Create Handler — Delhi

package com.vinod.design.chain; public class Delhi implements Chain { private Chain nextDestination; @Override public void setNextDestination(Chain nextDest) { this.nextDestination = nextDest; } @Override public void processParcel(Parcel parcel) { if (parcel.getDestination().equalsIgnoreCase("Delhi")) { System.out.println("Processing parcel in Delhi: " + parcel); } else { System.out.println("No branch found for destination: " + parcel.getDestination()); } } }

🧩 Step 5: Create the Parcel Class

package com.vinod.design.chain; public class Parcel { private String source; private String destination; private String details; public String getSource() { return source; } public void setSource(String source) { this.source = source; } public String getDestination() { return destination; } public void setDestination(String destination) { this.destination = destination; } public String getDetails() { return details; } public void setDetails(String details) { this.details = details; } @Override public String toString() { return "Parcel [source=" + source + ", destination=" + destination + ", details=" + details + "]"; } }

🧩 Step 6: Set Up the Chain (Main Class)

package com.vinod.design.chain; public class ParcelService { public static void main(String[] args) { // Create handler objects Chain bangalore = new Bangalore(); Chain bombay = new Bombay(); Chain delhi = new Delhi(); // Build the chain: Bangalore → Bombay → Delhi bangalore.setNextDestination(bombay); bombay.setNextDestination(delhi); // Create test parcels Parcel parcel1 = new Parcel(); parcel1.setDestination("Bombay"); parcel1.setDetails("Parcel to Bombay"); Parcel parcel2 = new Parcel(); parcel2.setDestination("Delhi"); parcel2.setDetails("Parcel to Delhi"); // Send parcels through the chain bangalore.processParcel(parcel1); bangalore.processParcel(parcel2); } }

🧾 Output

Processing parcel in Bombay: Parcel [source=null, destination=Bombay, details=Parcel to Bombay] Processing parcel in Delhi: Parcel [source=null, destination=Delhi, details=Parcel to Delhi]

🧠 How It Works (Step-by-Step Flow)

StepActionExplanation
1️⃣ParcelService sets up the chainBangalore → Bombay → Delhi
2️⃣A request (parcel) starts at BangaloreBangalore checks if destination matches
3️⃣If not, forwards to next handlerThe parcel moves along the chain
4️⃣Handler matches parcel destinationProcesses it and stops the chain
5️⃣If no handler matchesRequest is ignored or logged

πŸ” Benefits of Chain of Responsibility

  • Loose coupling — Sender doesn’t need to know which handler will process the request

  • Extensibility — Add new handlers easily without changing existing code

  • Flexible processing — Handlers can process partially or pass along

  • Separation of concerns — Each handler focuses on one specific job


⚠️ Things to Watch Out For

  • 🚫 The chain must be properly linked (missing setNextDestination() can break flow).

  • ⚙️ Handlers should ideally stop propagation once a request is processed.

  • 🧩 Avoid creating circular chains — can cause infinite loops.


🧩 UML-Like Text Diagram

+-------------+ +------------+ +-----------+ Request --> | Bangalore | --> | Bombay | --> | Delhi | +-------------+ +------------+ +-----------+ | | | | | | [Can process?] [Can process?] [Can process?] | | | v v v [Yes → Handle] [Yes → Handle] [Yes → Handle] | | | +------ No ---------+------ No ---------+

πŸ’¬ Real-World Applications

Use CaseExample
Web request filteringServlet Filters in Java EE (FilterChain)
Logging frameworkDifferent log handlers (Console, File, DB)
Event handling systemsUI event bubbling or middleware
Authorization pipelinesRequest passes through validators or access checkers

java.util.concurrent.TimeUnit Example

A TimeUnit represents time durations at a given unit of granularity and provides utility methods to convert across units, and to perform timing and delay operations in these units. Here is one example to do the following

Days to Hours

Hours to Minutes

Minutes to Seconds

Seconds to Milliseconds

Milliseconds to Micro seconds

Example

package com.pretech;
import java.util.concurrent.TimeUnit;
public class TimeUtilTest {
	public static void main(String[] args) {
		try {
			// Days to hours
			System.out.println("Total Hours" + TimeUnit.DAYS.toHours(1));
			// Hours to minutes
			System.out.println("Total Minutes" + TimeUnit.HOURS.toMinutes(24));
			// Minutes to Seconds
			System.out.println("Total Seconds"
					+ TimeUnit.MINUTES.toSeconds(1440));
			// Seconds to Mill seconds
			System.out.println("Total Milli seconds"
					+ TimeUnit.SECONDS.toMillis(86400));
			// Milli seconds to micro seconds
			System.out.println("Total Micro seconds "
					+ TimeUnit.MILLISECONDS.toMicros(86400000));
			// TimeUnit to sleep
			System.out.println("Before sleep ");
			TimeUnit.SECONDS.sleep(5);
			System.out.println("After sleep ");
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

Output



Total Hours24
Total Minutes1440
Total Seconds86400
Total Milli seconds86400000
Total Micro seconds 86400000000
Before sleep
After sleep


Camel Exception clause example

Camel provides Exception clause to handle exceptions , came RouteBuilder onException method will helps to handle exceptions. In this example we will move files from source directory to destination and in case RuntimeException occured we will move the messages to exceptionDirectory.

package com.vinod.test;

import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.main.Main;

public class OnExceptionTest {
    public static void main(String... args) throws Exception {
        Main main = new Main();
        main.enableHangupSupport();
        main.addRouteBuilder(new TestExceptionRoute());
        main.run(args);
    }
}

class TestExceptionRoute extends RouteBuilder {
    @Override
    public void configure() throws Exception {
        onException(RuntimeException.class).to("file:exceptionDirectory");
        from("file:source").process(new Processor() {
            public void process(Exchange exchange) throws Exception {
                throw new RuntimeException();
            }
        }).to("file:destination");
    }

}
 

Download source code 

https://github.com/kkvinodkumaran/camel

 

 

 

Camel DeadLetterChannel errorhandler Example

Camel supports the Dead Letter Channel from EIP patterns to handle errors. Camel will move the Exchange data in case any exceptions to Dead Letter channel. In this example we will see Route Builder error handler is moving the exchange data to dead letter channel. For testing this While moving the files from source to destination we are throwing exception.
package com.vinod.test;
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.main.Main;
public class ErrorHandlerTest {
    public static void main(String... args) throws Exception {
        Main main = new Main();
        main.enableHangupSupport();
        main.addRouteBuilder(new TestRoute());
        main.run(args);
    }
}

class TestRoute extends RouteBuilder {
    @Override
    public void configure() throws Exception {
        errorHandler(deadLetterChannel("file:deadletter"));
        from("file:source").process(new Processor() {
            public void process(Exchange exchange) throws Exception {
                throw new RuntimeException();
            }
        }).to("file:destination");
    }
}

Camel Data transformation using Custom Expression


Camel provides custom expression class to control the data transformation. Here is one example which will convert the text file to html format.

Example

package com.vinod.test;

import org.apache.camel.Exchange;
import org.apache.camel.Expression;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.main.Main;

public class CamelDatatransformation {
    public static void main(String... args) throws Exception {
        Main main = new Main();
        main.enableHangupSupport();
        main.addRouteBuilder(new MyRoute());
        main.run(args);
    }
}

class MyRoute extends RouteBuilder {
    @Override
    public void configure() throws Exception {
        from("file:source").transform(new Expression() {
            public <T> T evaluate(Exchange exchange, Class<T> type) {
                String body = exchange.getIn().getBody(String.class);
                body = body.replaceAll("\n", "<br/>");
                body = "<html><body>" + body + "</body></html>";
                return (T) body;
            }
        }).to("file:destination");
    }
}

Run it and place one input text file in source folder and see the destination folder

Done!!!

How to Iterate a String in Java

package pretechtest;
public class StringTest {
	public static void main(String[] args) {
		String str = "Hello pretech";
		for (int i = 0; i <= str.length()-1; i++) {
			System.out.println(str.charAt(i));
		}
	}
}

Output



H
e
l
l
o
p
r
e
t
e
c
h


Spring Boot

Spring Boot Examples

Factory Method Pattern Example

🏭 Factory Method Pattern in Java — Step-by-Step Guide

The Factory Method Pattern is one of the key Creational Design Patterns in Java.
It provides an interface for creating objects, allowing subclasses or factory logic to decide which class to instantiate at runtime.

Rather than creating objects using the new keyword directly, the Factory hides the instantiation logic inside a dedicated class — improving flexibility, maintainability, and decoupling.


🎯 Real-World Analogy

Think of a person registration system.
Based on user input (M or F), we want to create a Male or Female object.

Instead of writing:

Person p = new Male();

everywhere in the code, we delegate that logic to a Factory.
So the client simply calls:

Person p = PersonFactory.getPerson("M");

and the factory takes care of the rest.


⚙️ Pattern Intent

  • Define an interface for object creation.

  • Let subclasses or logic decide which concrete class to instantiate.

  • Decouple object creation from object usage.


🧩 Step 1: Create an Interface (Person.java)

package com.vinod.design.factory; /** * Common interface for all Person types. */ public interface Person { void say(); }

🧩 Step 2: Create Concrete Implementations

πŸ‘¨ Male.java

package com.vinod.design.factory; public class Male implements Person { @Override public void say() { System.out.println("I am male"); } }

πŸ‘© Female.java

package com.vinod.design.factory; public class Female implements Person { @Override public void say() { System.out.println("I am female"); } }

🧩 Step 3: Create the Factory Class

package com.vinod.design.factory; /** * Factory class that decides which Person object to create. */ public class PersonFactory { /** * Creates and returns a Person instance based on the input gender. * * @param gender "M" for Male, "F" for Female * @return Person instance or null if input is invalid */ public Person getPerson(String gender) { if (gender == null) { return null; } if (gender.equalsIgnoreCase("M")) { return new Male(); } else if (gender.equalsIgnoreCase("F")) { return new Female(); } return null; // can also throw IllegalArgumentException } }

🧩 Step 4: Test the Factory

package com.vinod.design.factory; public class FactoryTest { public static void main(String[] args) { PersonFactory factory = new PersonFactory(); // Create Male instance Person p1 = factory.getPerson("M"); p1.say(); // Create Female instance Person p2 = factory.getPerson("F"); p2.say(); // Invalid input test Person p3 = factory.getPerson("X"); if (p3 == null) { System.out.println("Invalid gender input. No object created."); } } }

🧾 Output

I am male I am female Invalid gender input. No object created.

🧠 How It Works (Step-by-Step)

StepActionDescription
1️⃣Client requests object creationCalls factory.getPerson("M")
2️⃣Factory checks inputEvaluates input value (“M” / “F”)
3️⃣Factory creates instanceUses new Male() or new Female()
4️⃣Factory returns resultReturns Person reference to caller
5️⃣Client uses interfaceCalls say() without knowing the concrete type

πŸ” Advantages of Factory Method Pattern

BenefitDescription
✅ EncapsulationObject creation logic hidden from client
✅ Loose CouplingClient depends only on interface, not implementation
✅ ExtensibilityNew types can be added without changing client code
✅ ReusabilityCommon creation logic centralized in the factory
✅ SimplicityCleaner and more readable code

⚠️ When to Use Factory Method

  • When you don’t know the exact type of object to create at compile time.

  • When you want to isolate the creation logic.

  • When the object creation process is complex or conditional.

  • When you want to return subclasses or implementations dynamically.


🧩 UML-Like Text Diagram

+-------------------+ | Person | <-- Interface |-------------------| | + say() | +---------+---------+ | +------------+------------+ | | +-------------+ +-------------+ | Male | | Female | |-------------| |-------------| | + say() | | + say() | +-------------+ +-------------+ ^ | +----------------+ | PersonFactory | |----------------| | + getPerson() | +----------------+ ^ | +----------------+ | FactoryTest | +----------------+

🧭 Real-World Examples in Java

Use CaseDescription
Calendar.getInstance()Returns different Calendar subclasses based on locale/timezone
NumberFormat.getInstance()Returns appropriate number format (e.g., for locale)
JDBC DriverManager.getConnection()Creates a connection for a given database type
ExecutorService.newFixedThreadPool()Factory method returning different thread pool types

✅ Summary

ConceptDescription
Pattern TypeCreational
PurposeDelegate object creation to a factory
Core ComponentsInterface, Concrete Classes, Factory Class
Key BenefitEncapsulates creation logic, enhances flexibility
Real ExamplePersonFactory creating Male or Female objects

Strategy Pattern Example

🎯 Strategy Design Pattern in Java — Step-by-Step with Example

The Strategy Pattern is a behavioral design pattern that enables selecting an algorithm’s behavior at runtime.

It defines a family of algorithms, encapsulates each one, and makes them interchangeable.
This helps you avoid hardcoding logic and promotes flexibility and reusability.


🧠 Concept

  • Define a common strategy interface (for a family of related algorithms).

  • Create multiple concrete strategy classes, each implementing that interface.

  • Use a context class that maintains a reference to a strategy and delegates execution to it.

This lets you change an algorithm’s behavior dynamically at runtime without modifying existing code.


πŸš— Real-World Analogy

Think of a navigation app (like Google Maps)
You can choose different travel strategies:

  • πŸš— Car route (fastest by road)

  • πŸš† Train route

  • 🚢 Walking route

All these share the same interface: calculateRoute(),
but their internal logic differs.

You can switch the strategy anytime, depending on user preference or situation — that’s Strategy Pattern in action.


⚙️ Step-by-Step Implementation


🧩 Step 1: Define the Strategy Interface

package com.vinod.design.strategy; /** * Strategy interface defining a common operation. */ public interface PaymentStrategy { void pay(int amount); }

This interface represents the common behavior (payment operation) shared by all strategies.


🧩 Step 2: Create Concrete Strategies

Each class implements a different way to perform the payment.

package com.vinod.design.strategy; public class CreditCardPayment implements PaymentStrategy { @Override public void pay(int amount) { System.out.println("Paid " + amount + " using Credit Card."); } }
package com.vinod.design.strategy; public class PayPalPayment implements PaymentStrategy { @Override public void pay(int amount) { System.out.println("Paid " + amount + " using PayPal."); } }
package com.vinod.design.strategy; public class UpiPayment implements PaymentStrategy { @Override public void pay(int amount) { System.out.println("Paid " + amount + " using UPI."); } }

🧩 Step 3: Create the Context Class

The context uses a strategy object to execute the algorithm.
You can change the strategy dynamically by calling setPaymentStrategy().

package com.vinod.design.strategy; /** * Context class that uses a PaymentStrategy. */ public class PaymentContext { private PaymentStrategy paymentStrategy; // Constructor injection (optional) public PaymentContext(PaymentStrategy paymentStrategy) { this.paymentStrategy = paymentStrategy; } // Setter injection (allows dynamic strategy change) public void setPaymentStrategy(PaymentStrategy paymentStrategy) { this.paymentStrategy = paymentStrategy; } // Delegates behavior to the selected strategy public void executePayment(int amount) { if (paymentStrategy == null) { throw new IllegalStateException("Payment strategy not set!"); } paymentStrategy.pay(amount); } }

🧩 Step 4: Test the Strategy Pattern

package com.vinod.design.strategy; public class StrategyPatternTest { public static void main(String[] args) { // Pay using Credit Card PaymentContext context = new PaymentContext(new CreditCardPayment()); context.executePayment(500); // Change strategy at runtime to PayPal context.setPaymentStrategy(new PayPalPayment()); context.executePayment(1200); // Switch again to UPI context.setPaymentStrategy(new UpiPayment()); context.executePayment(250); } }

🧾 Output

Paid 500 using Credit Card. Paid 1200 using PayPal. Paid 250 using UPI.

🧠 How It Works (Step-by-Step)

StepActionExplanation
1️⃣Client creates PaymentContextHolds a reference to a PaymentStrategy
2️⃣Client sets strategy (e.g., CreditCardPayment)Decides algorithm to use
3️⃣Context delegates callCalls strategy.pay(amount) internally
4️⃣Strategy executes its algorithmPerforms the desired operation
5️⃣Strategy can be switched dynamicallyClient can call setPaymentStrategy() anytime

🧩 UML-Like Text Diagram

+---------------------+ | PaymentStrategy | <--- Interface |---------------------| | + pay(amount):void | +---------+-----------+ | +-------------------+------------------+ | | | +----------------+ +----------------+ +----------------+ | CreditCardPayment | | PayPalPayment | | UpiPayment | |------------------| |----------------| |----------------| | + pay() | | + pay() | | + pay() | +------------------+ +----------------+ +----------------+ ^ | +----------------------+ | PaymentContext | |----------------------| | - strategy:PaymentStrategy | | + setPaymentStrategy() | | + executePayment() | +----------------------+

⚡ Benefits of Strategy Pattern

BenefitDescription
Open/Closed PrincipleYou can add new strategies without modifying existing code
Runtime FlexibilityChange algorithms dynamically
Code ReusabilityCommon logic stays in context; algorithms are modular
Clean DesignRemoves complex if-else or switch conditions
TestabilityEach strategy can be unit tested independently

Model Context Protocol (MCP) — Complete Guide for Backend Engineers

  Model Context Protocol (MCP) — Complete Guide for Backend Engineers Build Tools, Resources, and AI-Driven Services Using LangChain Moder...

Featured Posts