Showing posts with label Software Design. Show all posts
Showing posts with label Software Design. Show all posts

Template Method Design Pattern in Java with Real-World Examples

Template Method Design Pattern in Java: Complete Guide with Real-World Examples

Have you ever noticed that different payment methods follow almost the same process?

For example, whether a customer makes a payment using UPI, a debit card, or a wallet, the application generally performs these steps:

  1. Validate the payment request.
  2. Check payment conditions.
  3. Execute the payment.
  4. Save the transaction details.
  5. Send a notification.

The overall payment process remains almost the same, but the actual payment execution changes depending on the payment method.

Now imagine writing this complete workflow separately for every payment method. You would probably end up with duplicated code.

The Template Method Design Pattern in Java solves exactly this problem. It defines the overall algorithm in a parent class and allows subclasses to customize individual steps.

In this guide, we will understand the pattern using a tea-and-coffee example and a real-world payment processing scenario, implement complete executable Java code, explore a Spring Boot use case, compare it with Strategy, and prepare for common Java design-pattern interview questions.

1. What Is the Template Method Design Pattern?

The Template Method pattern is a behavioral design pattern that defines the skeleton of an algorithm in a superclass. Some steps are implemented in the parent class, while other steps are left to subclasses.

The important point is that the parent class controls the execution order.

In simple words: The parent class decides what happens and in which order. The child class decides how certain steps happen.

Real-world example: Preparing tea and coffee

Think about preparing tea and coffee. Both follow a common process:

  1. Boil water.
  2. Add the main ingredient.
  3. Add milk or other ingredients.
  4. Serve the drink.

However, the ingredients are different. For tea, we add tea leaves. For coffee, we add coffee powder. The overall preparation process remains the same, but certain steps change.

This is the basic idea behind the Template Method Pattern.

Key participants

  • Abstract Class: Defines the template method and common steps.
  • Template Method: Defines the fixed sequence of operations.
  • Concrete Class: Implements the steps that vary.
  • Hook Method: An optional method with a default implementation that subclasses can override.

2. Important Components of the Pattern

The pattern has four key building blocks.

1. Abstract Class

Defines the template method and common steps. It may declare abstract methods for steps that subclasses must provide and concrete methods for steps that are shared.

2. Template Method

Defines the fixed sequence of operations. It usually calls common methods, abstract methods, and optional hook methods in a specific order. Making it final prevents subclasses from changing the sequence.

3. Concrete Class

Implements the steps that vary between different variants of the algorithm. For example, UPIPayment, CardPayment, and WalletPayment provide their own executePayment() implementations.

4. Hook Method

An optional method that subclasses can override to customize a step. It has a default implementation in the parent class, so subclasses that do not need customization can simply ignore it.

3. Template Method Class Diagram

This original diagram shows how an abstract payment class defines the common workflow, while UPI, card, and wallet payment classes provide their own payment implementation.

Template Method Class Diagram with UPI, Card, and Wallet Subclasses AbstractPayment defines the final processPayment() template method, a private validatePayment() step, an abstract executePayment() method, and an overridable beforePayment() hook. UPIPayment, CardPayment, and WalletPayment extend AbstractPayment and provide their own executePayment() implementations. WalletPayment also overrides the beforePayment() hook. AbstractPayment + processPayment() : void [final] - validatePayment() : void # executePayment() : void [abstract] # beforePayment() : void [hook] UPIPayment executePayment() UPI-specific logic CardPayment executePayment() Card-specific logic WalletPayment executePayment() Wallet-specific logic Three subclasses · One common template All subclasses inherit processPayment()
Figure: The abstract parent class defines the workflow, and concrete subclasses provide their own step implementations.

Important: The abstract class contains the common workflow. Each child class provides its own implementation of executePayment(). The wallet class can additionally override the optional hook method.

4. How the Template Method Works Internally

Let's understand the execution sequence.

  1. The client creates an object of a concrete payment class.
  2. The client calls processPayment().
  3. The parent class validates the payment request.
  4. The parent class calls beforePayment().
  5. The child class implementation of executePayment() is invoked.
  6. The parent class saves the transaction and sends a notification.

The important point is that the client never assembles the workflow itself. The parent class owns the sequence, and the subclass only supplies the pieces that differ.

What changes after introducing a template method?

The common steps live in one place. Each concrete subclass contains only the implementation of the specific steps that vary. Adding a new payment method becomes a matter of adding a new subclass rather than duplicating the whole workflow.

5. Execution Flow Diagram

This diagram represents the common sequence followed by UPI, card, and wallet payments. Only the payment execution implementation changes.

Template Method Payment Execution Flow The client calls processPayment(), which validates the request, runs the beforePayment() hook, executes the subclass-specific payment step, saves the transaction, and sends a notification. The parent class controls the execution order. Client calls processPayment() 1. Validate payment request 2. Run beforePayment() hook 3. Execute payment (subclass) UPIPayment UPI execution CardPayment Card execution WalletPayment Wallet execution 4. Save transaction 5. Send notification
Figure: The template method runs the workflow in a fixed order, delegating the payment execution step to the subclass.

The parent class controls the sequence before and after payment execution. The child class only supplies the payment-specific behavior.

6. Java Implementation – Tea and Coffee Example

Before implementing a payment system, let's create a simple example using tea and coffee. This example makes the pattern easy to see before applying it to a real workflow.

Complete executable Java code

Create a file named BeverageDemo.java and copy the following code.

abstract class Beverage {

    // Template Method
    public final void prepareBeverage() {
        boilWater();
        addMainIngredient();
        addCondiments();
        serve();
    }

    private void boilWater() {
        System.out.println("Boiling water...");
    }

    protected abstract void addMainIngredient();

    protected abstract void addCondiments();

    private void serve() {
        System.out.println("Serving beverage...");
    }
}

class Tea extends Beverage {

    @Override
    protected void addMainIngredient() {
        System.out.println("Adding tea leaves...");
    }

    @Override
    protected void addCondiments() {
        System.out.println("Adding milk and sugar...");
    }
}

class Coffee extends Beverage {

    @Override
    protected void addMainIngredient() {
        System.out.println("Adding coffee powder...");
    }

    @Override
    protected void addCondiments() {
        System.out.println("Adding milk and sugar...");
    }
}

public class BeverageDemo {

    public static void main(String[] args) {

        Beverage tea = new Tea();

        System.out.println("Preparing Tea:");
        tea.prepareBeverage();

        System.out.println();

        Beverage coffee = new Coffee();

        System.out.println("Preparing Coffee:");
        coffee.prepareBeverage();
    }
}

Expected output

Preparing Tea:
Boiling water...
Adding tea leaves...
Adding milk and sugar...
Serving beverage...

Preparing Coffee:
Boiling water...
Adding coffee powder...
Adding milk and sugar...
Serving beverage...

Understand this carefully: Both classes execute the same prepareBeverage() method. However, the implementation of addMainIngredient() and addCondiments() comes from the respective child class.

Also notice the final keyword. It prevents subclasses from overriding the template method and changing the overall sequence.

7. Real-World Example – Payment Processing System

Now let's understand a practical example from a fintech application.

Suppose you are developing a payment gateway that supports three payment methods:

  • UPI payment
  • Debit or credit card payment
  • Wallet payment

All three methods need common processing steps, but each payment method has its own execution logic. Without a proper design, developers may write the same validation, transaction-saving, and notification code in multiple classes.

Let's solve this problem using the Template Method Pattern.

Complete executable Java 8 code

For simplicity, we will simulate the payment providers using console messages. In a real application, the payment execution methods would communicate with external payment APIs or internal payment services.

Create a file named PaymentDemo.java.

abstract class AbstractPayment {

    private final double amount;

    public AbstractPayment(double amount) {
        this.amount = amount;
    }

    // Template Method
    public final void processPayment() {

        validatePayment();

        beforePayment();

        executePayment();

        saveTransaction();

        sendNotification();

        System.out.println("Payment completed successfully.");
    }

    private void validatePayment() {

        if (amount <= 0) {
            throw new IllegalArgumentException(
                "Payment amount must be greater than zero"
            );
        }

        System.out.println("Payment validation successful.");
    }

    // Optional hook method
    protected void beforePayment() {
        System.out.println("Checking payment conditions...");
    }

    // Subclasses implement their own payment logic
    protected abstract void executePayment();

    private void saveTransaction() {
        System.out.println("Saving transaction: Rs. " + amount);
    }

    private void sendNotification() {
        System.out.println("Sending payment notification...");
    }

    protected double getAmount() {
        return amount;
    }
}

class UPIPayment extends AbstractPayment {

    public UPIPayment(double amount) {
        super(amount);
    }

    @Override
    protected void executePayment() {
        System.out.println(
            "Processing UPI payment of Rs. " + getAmount()
        );
    }
}

class CardPayment extends AbstractPayment {

    public CardPayment(double amount) {
        super(amount);
    }

    @Override
    protected void executePayment() {
        System.out.println(
            "Processing Card payment of Rs. " + getAmount()
        );
    }
}

class WalletPayment extends AbstractPayment {

    public WalletPayment(double amount) {
        super(amount);
    }

    @Override
    protected void beforePayment() {
        System.out.println("Checking wallet balance...");
    }

    @Override
    protected void executePayment() {
        System.out.println(
            "Processing Wallet payment of Rs. " + getAmount()
        );
    }
}

public class PaymentDemo {

    public static void main(String[] args) {

        System.out.println("----- UPI PAYMENT -----");

        AbstractPayment upiPayment =
            new UPIPayment(1000);

        upiPayment.processPayment();

        System.out.println();

        System.out.println("----- CARD PAYMENT -----");

        AbstractPayment cardPayment =
            new CardPayment(2000);

        cardPayment.processPayment();

        System.out.println();

        System.out.println("----- WALLET PAYMENT -----");

        AbstractPayment walletPayment =
            new WalletPayment(500);

        walletPayment.processPayment();
    }
}

Let's understand the important code

1. Why is processPayment() final?

The processPayment() method is the template method. It defines the payment workflow. Making it final prevents subclasses from overriding the method and changing the required execution sequence.

2. Why is executePayment() abstract?

Because each payment method has a different implementation. UPI, card, and wallet payments may communicate with different services or providers.

3. Why is beforePayment() a hook method?

Not every payment method needs the same additional operation. The parent class provides a default implementation, and a child class can override it when necessary. In our example, the wallet payment checks its balance before executing the payment.

4. Why are saveTransaction() and sendNotification() common methods?

Because the basic transaction-saving and notification workflow is shared by all payment types in this simplified example. Therefore, we avoid duplicating these operations in every child class.

8. Payment Workflow Diagram

This original diagram shows the shared payment template and the three concrete payment implementations that plug into it.

Payment Template Method Workflow A common payment template defines the processPayment() sequence of validate, execute, save, and notify. UPI, card, and wallet payments plug into the execute step with their own implementations, while sharing the transaction and notification workflow. Common Payment Template processPayment() Validate → Execute → Save → Notify Same sequence for every payment type UPI Payment UPI provider API Custom execution Card Payment Card provider API Custom execution Wallet Payment Wallet service Custom execution Different implementations, same overall algorithm Common transaction and notification workflow
Figure: The shared payment template defines the workflow; each payment type only supplies its execution step.

9. Expected Output

----- UPI PAYMENT -----
Payment validation successful.
Checking payment conditions...
Processing UPI payment of Rs. 1000.0
Saving transaction: Rs. 1000.0
Sending payment notification...
Payment completed successfully.

----- CARD PAYMENT -----
Payment validation successful.
Checking payment conditions...
Processing Card payment of Rs. 2000.0
Saving transaction: Rs. 2000.0
Sending payment notification...
Payment completed successfully.

----- WALLET PAYMENT -----
Payment validation successful.
Checking wallet balance...
Processing Wallet payment of Rs. 500.0
Saving transaction: Rs. 500.0
Sending payment notification...
Payment completed successfully.

Notice that all three payments execute the same processPayment() template, but the executePayment() step changes for each payment method. The wallet payment also overrides the beforePayment() hook to check the balance.

How the responsibilities are separated

Component Responsibility
AbstractPayment Defines the workflow and common steps.
processPayment() Fixed template method controlling the execution sequence.
executePayment() Abstract step implemented by each concrete payment class.
beforePayment() Optional hook overridable by subclasses when needed.
UPIPayment / CardPayment / WalletPayment Provide their own execution logic.

10. Template Method in Spring Boot

The Template Method Pattern can be useful in Spring Boot applications when multiple services follow a common sequence of operations but have different implementations for one or more steps.

Some examples include:

  • Payment processing: Common validation, transaction handling, and notification workflow.
  • File processing: Read file, validate records, transform data, and save results.
  • Report generation: Load data, process records, generate a report, and export it.
  • Authentication: Common authentication flow with different authentication mechanisms.
  • Third-party integration: Common request preparation, provider-specific execution, and response handling.

Example: Spring Boot file processing

Suppose your Spring Boot application processes CSV and Excel files. Both file types follow a similar workflow:

  1. Read the file.
  2. Validate its contents.
  3. Transform the data.
  4. Save the records.

You can create an abstract processor that defines this workflow and separate CSV and Excel processors that implement the file-specific steps. This keeps common processing logic in one place.

Is this always the best design?

No. A class hierarchy is not always the right answer. If the workflow itself needs to change frequently, or if you need to select an algorithm at runtime, the Strategy pattern may be a better fit.

Production consideration: In a real Spring Boot application, also consider dependency injection, transaction boundaries, exception handling, retry policies, idempotency, and external API failure handling. A template method alone does not provide these capabilities.

11. Template Method vs Strategy Pattern

Both patterns help reduce duplicated code, but they solve different problems.

Comparison Template Method Strategy
Type Behavioral Behavioral
Approach Inheritance Composition
Algorithm Defines a fixed workflow Encapsulates interchangeable algorithms
Customization Subclasses override steps Different strategy implementations
Execution order Controlled by parent class Controlled by the calling object
Flexibility Suitable for fixed workflows Flexible for runtime selection

Template Method: Every payment follows the same sequence, but the payment execution step changes.

Strategy: The application selects a payment strategy, such as UPI, card, or wallet, and delegates payment execution to that strategy.

Template Method uses inheritance to vary individual steps of an algorithm. Strategy uses composition to replace an entire algorithm.

12. Advantages and Disadvantages of the Template Method Pattern

Advantages

  • Code reusability: Common logic is written once in the parent class.
  • Less code duplication: Subclasses implement only the steps that differ.
  • Consistent workflow: The parent class controls the sequence of operations.
  • Easy maintenance: Changes to common operations can be made in one place.
  • Controlled customization: Subclasses can customize only the permitted steps.

Disadvantages

  • Inheritance dependency: It relies on a parent-child relationship, which creates tight coupling.
  • Limited flexibility: Changing the workflow may require modifying the parent class.
  • More subclasses: Multiple variations can increase the number of classes.
  • Debugging complexity: Execution moves between parent and child methods, which can make debugging harder.
  • Subclass coupling: Child classes depend on the structure and protected methods of the parent class.

The best template method is small, focused, and stable. If the workflow begins to change frequently or needs to be selected at runtime, consider moving to a composition-based design instead.

13. Best Practices

  1. Keep the template method small and easy to understand. The sequence of steps should be readable at a glance.
  2. Use the final keyword when subclasses must not change the algorithm sequence.
  3. Keep common operations in the parent class. Avoid duplicating the same logic in each subclass.
  4. Use abstract methods only for steps that must be implemented by subclasses.
  5. Use hook methods for optional customization. Provide sensible defaults so subclasses can override only when necessary.
  6. Avoid adding too many customization points without a real requirement. More hooks increase complexity without adding value.
  7. Do not use inheritance just to share a few utility methods. Composition is usually a better fit for that case.
  8. Write unit tests for the common workflow and each concrete implementation. This ensures the sequence remains intact as the design evolves.

14. Java Interview Questions and Answers

Q1. What is the Template Method Design Pattern?

Template Method is a behavioral design pattern that defines the skeleton of an algorithm in a superclass and allows subclasses to implement or customize specific steps without changing the overall algorithm structure.

Q2. Why is Template Method called a behavioral design pattern?

Because it focuses on how an algorithm is executed and how responsibilities are distributed between the parent and child classes.

Q3. What is a template method?

A template method is the method that defines the sequence of steps of an algorithm. It usually calls common methods, abstract methods, and optional hook methods.

Q4. Why is the template method often declared final?

To prevent subclasses from overriding the workflow and changing the predefined order of execution. However, declaring it final is a design choice, not an absolute requirement of the pattern.

Q5. What is a hook method?

A hook method is an optional method with a default implementation in the parent class. Subclasses can override it to customize behavior when required.

Q6. What is the difference between an abstract method and a hook method?

An abstract method has no implementation in the abstract class and must be implemented by a concrete subclass. A hook method has a default implementation and can optionally be overridden.

Q7. Can we implement Template Method without an abstract class?

Yes. A concrete parent class can define the template method and allow subclasses to override protected methods. However, an abstract class is commonly used when some algorithm steps must be implemented by subclasses.

Q8. What is the difference between Template Method and Strategy?

Template Method uses inheritance and defines a fixed algorithm structure. Strategy uses composition and allows the application to select an interchangeable algorithm implementation.

Q9. Can a template method call private methods?

Yes. The template method can call private methods for operations that must remain internal to the parent class. Subclasses cannot override those private methods.

Q10. Can a subclass change the execution order?

If the template method is final, the subclass cannot override the workflow. It can only customize the steps that the parent class exposes for customization.

Q11. What is the Hollywood Principle in relation to Template Method?

The Hollywood Principle means "Don't call us, we'll call you." The parent class controls the algorithm and calls the subclass methods when necessary. Subclasses do not control the overall workflow.

Q12. Is Template Method suitable for microservices?

It can be useful inside a microservice when multiple workflows share common steps. For example, different file processors or payment handlers may share a common processing sequence. It does not replace communication or orchestration between microservices.

Q13. Can Template Method be used with Spring Boot?

Yes. An abstract Spring-managed service can define a common workflow, while concrete Spring components implement the specific steps. Dependency injection can be used for supporting services.

Q14. What happens if a subclass does not implement an abstract method?

The subclass must either implement the abstract method or remain abstract. Otherwise, Java compilation fails.

Q15. When should we avoid Template Method?

Avoid it when the steps of the algorithm change frequently, when you need to select algorithms dynamically at runtime, or when inheritance creates unnecessary coupling. Strategy or composition may be a better choice in such cases.

15. Frequently Asked Questions

Is Template Method part of Java?

No. It is a design pattern, not a built-in Java API. You can implement it using Java classes, inheritance, and method overriding.

Is Template Method the same as a Java template class?

No. Java does not have C++-style templates. Template Method is a design pattern implemented through inheritance and polymorphism.

Can we use interfaces for Template Method?

An interface with default methods can provide some similar behavior, but an abstract class is generally more suitable when you need shared state, constructors, protected methods, and abstract steps.

Where is Template Method useful in real projects?

It is useful in payment processing, file processing, report generation, authentication workflows, and third-party integrations where the overall sequence is common but individual steps differ.

Is the Template Method pattern part of the Gang of Four patterns?

Yes. Template Method is one of the behavioral patterns in the Gang of Four design-pattern catalog.

Does Template Method mean all steps must be fixed?

No. The sequence is fixed, but individual steps can be implemented, overridden, or optionally skipped through hooks depending on the design.

16. Quick Revision Checklist

  • Template Method is a behavioral design pattern.
  • It defines the skeleton of an algorithm in a superclass.
  • The template method controls the execution order.
  • Subclasses customize specific steps without changing the sequence.
  • Making the template method final protects the workflow.
  • Hook methods provide optional customization with sensible defaults.
  • Payment processing, file processing, and report generation are typical uses.
  • Template Method uses inheritance; Strategy uses composition.
  • Spring Boot services can use it for common workflows across concrete components.
  • Avoid it when the workflow changes frequently or must be selected at runtime.

Conclusion

The Template Method Design Pattern is useful when multiple classes follow the same algorithm but need different implementations for certain steps. Instead of duplicating the entire workflow, we define the common sequence in a parent class and let subclasses customize only the required operations.

Remember these three important points:

  • The parent class defines the algorithm structure.
  • Child classes customize specific steps.
  • The template method controls the execution order.

Before using this pattern in a real project, check whether your workflow is genuinely common. If the entire algorithm needs to change dynamically, consider the Strategy Pattern instead.

If you are preparing for Java interviews, practise implementing Template Method alongside Strategy, Observer, and Factory Method. Understanding when each pattern is useful matters more than memorizing its definition.

Bridge Design Pattern in Java: Complete Guide with Examples (2026)

Bridge Design Pattern in Java: Complete Guide with Examples

Learn the Bridge design pattern in Java with simple explanations and practical examples. This guide covers how to decouple abstraction from implementation using composition, with before-and-after code for beginners and experts.

I've been writing Java for years, and the Bridge pattern is one I keep coming back to. Not because it's fancy—because it saved me from a class explosion in a real payment system. Here's what I've learned.

Why the Bridge Pattern Still Matters in Java

You've built a class hierarchy. It works. Then requirements change: new platforms, new features, new combinations. Suddenly your class explosion looks like a family tree gone wrong.

The Bridge pattern solves this by splitting one tangled hierarchy into two independent ones. It's not about adding more classes—it's about organizing them so they can grow separately without stepping on each other.

Whether you're supporting multiple databases, payment gateways, or remote controls, Bridge keeps your design flexible without the inheritance nightmare.

What is the Bridge Design Pattern?

"Decouple an abstraction from its implementation so that the two can vary independently."

In plain words: Bridge prefers composition over inheritance. Instead of hardcoding every combination into a single class tree, you push implementation details into a separate object hierarchy and connect them through a reference.

The Two Hierarchies

Role Responsibility Example
Abstraction High-level control logic RemoteControl
Implementation Low-level platform-specific work Device (TV, Radio)
Bridge Design Pattern Structure Diagram Abstraction hierarchy (RemoteControl, AdvancedRemoteControl) connected via composition to Implementation hierarchy (Device interface with Tv and Radio). ABSTRACTION IMPLEMENTATION RemoteControl abstraction + togglePower() AdvancedRemoteControl + mute() extends «interface» Device + enable() / disable() Tv implements Radio implements THE BRIDGE Abstraction hierarchy Implementation hierarchy Composition (bridge)

Bridge pattern structure: RemoteControl and Device hierarchies grow independently through composition.

How to read this diagram (for freshers): Look at the left side — that's the Abstraction family (remotes). Look at the right side — that's the Implementation family (devices). The bold red arrow in the middle is the "bridge." The remote doesn't care which device it is holding — it just calls the methods defined in the Device interface. Add a new device (say, Projector)? Only the right side changes. Add a new remote (say, VoiceRemote)? Only the left side changes. Both sides grow without touching each other — that's the whole point of the pattern.

The abstraction holds a reference to an implementation object. When the abstraction needs work done, it delegates to that object. Since all implementations share a common interface, they're interchangeable.

Violation: The Class Explosion Nightmare

Imagine you're building a drawing app. You have two shape types (Circle, Square) and two rendering methods (Vector, Raster).

Without Bridge, inheritance forces you to create every combination:

// The explosion begins
class VectorCircle { /* ... */ }
class RasterCircle { /* ... */ }
class VectorSquare { /* ... */ }
class RasterSquare { /* ... */ }

// Add a new shape? Four more classes.
// Add a new renderer? Four more classes.

The Problem: Every new shape or renderer multiplies the class count. This is "nested generalizations"—a clear signal you need to split the hierarchy.

Solution: Bridge with Remote Controls and Devices

Let's use the classic remote control example. You have different remotes and different devices. A basic remote should work with a TV or a Radio. An advanced remote should do the same, plus extra features.

Step 1: The Implementation Hierarchy (Devices)

interface Device {
    boolean isEnabled();
    void enable();
    void disable();
    int getVolume();
    void setVolume(int volume);
    int getChannel();
    void setChannel(int channel);
}
class Tv implements Device {
    private boolean on = false;
    private int volume = 30;
    private int channel = 1;

    public boolean isEnabled() { return on; }
    public void enable() { on = true; }
    public void disable() { on = false; }
    public int getVolume() { return volume; }
    public void setVolume(int volume) { this.volume = volume; }
    public int getChannel() { return channel; }
    public void setChannel(int channel) { this.channel = channel; }
}

class Radio implements Device {
    // Similar implementation for radio
}

Step 2: The Abstraction Hierarchy (Remotes)

class RemoteControl {
    protected Device device;

    public RemoteControl(Device device) {
        this.device = device;
    }

    public void togglePower() {
        if (device.isEnabled()) {
            device.disable();
        } else {
            device.enable();
        }
    }

    public void volumeDown() {
        device.setVolume(device.getVolume() - 10);
    }

    public void volumeUp() {
        device.setVolume(device.getVolume() + 10);
    }
}

Step 3: Refined Abstraction (Advanced Remote)

class AdvancedRemoteControl extends RemoteControl {
    public AdvancedRemoteControl(Device device) {
        super(device);
    }

    public void mute() {
        device.setVolume(0);
    }
}

Step 4: Using the Bridge

public class Demo {
    public static void main(String[] args) {
        // Basic remote with TV
        Device tv = new Tv();
        RemoteControl basicRemote = new RemoteControl(tv);
        basicRemote.togglePower();
        basicRemote.volumeUp();

        // Advanced remote with Radio
        Device radio = new Radio();
        AdvancedRemoteControl advancedRemote = new AdvancedRemoteControl(radio);
        advancedRemote.togglePower();
        advancedRemote.mute();
    }
}

What just happened? The RemoteControl hierarchy and the Device hierarchy grew independently. Adding a new device (e.g., Projector) requires zero changes to remotes. Adding a new remote (e.g., VoiceRemote) requires zero changes to devices.

For Beginners: The Power Strip Analogy

Think of a power strip (the abstraction) and different appliances (the implementations). The power strip doesn't care if you plug in a lamp, a phone charger, or a fan. It just provides power. You can add new appliances without rewiring the strip, and you can buy a new power strip without replacing your appliances.

Bridge is the socket. Inheritance would be soldering the appliance directly to the wall.

For Experts: Bridge vs. Adapter (Know the Difference)

This is a common interview trap.

Aspect Bridge Adapter
Intent Decouple abstraction from implementation Make incompatible interfaces work together
Timing Designed upfront Retrofitted to existing code
Structure Two hierarchies, one reference One wrapper around one object
SOLID Directly supports OCP and DIP Supports SRP but is a band-aid

Rule of thumb: Bridge is planned separation. Adapter is emergency compatibility.

Putting It All Together: The Payment Processing Example

A payment system needs to support multiple gateways (Stripe, PayPal) and multiple payment types (Subscription, One-Time). Without Bridge, you'd need StripeSubscription, StripeOneTime, PayPalSubscription, PayPalOneTime—and it only gets worse with each addition.

The Bridge Structure

Abstraction (Payment Type) Implementation (Gateway)
Payment (abstract) PaymentGateway (interface)
SubscriptionPayment StripeGateway
OneTimePayment PayPalGateway
interface PaymentGateway {
    void process(double amount);
}

class StripeGateway implements PaymentGateway {
    public void process(double amount) {
        System.out.println("Stripe: $" + amount);
    }
}

abstract class Payment {
    protected PaymentGateway gateway;

    public Payment(PaymentGateway gateway) {
        this.gateway = gateway;
    }

    public abstract void pay(double amount);
}

class SubscriptionPayment extends Payment {
    public SubscriptionPayment(PaymentGateway gateway) {
        super(gateway);
    }

    public void pay(double amount) {
        gateway.process(amount * 0.9); // 10% discount
    }
}

class OneTimePayment extends Payment {
    public OneTimePayment(PaymentGateway gateway) {
        super(gateway);
    }

    public void pay(double amount) {
        gateway.process(amount);
    }
}

Now adding CryptoGateway or InstallmentPayment means one new class, not a matrix of combinations.

Personal note: I once inherited a codebase with StripeSubscription, StripeOneTime, PayPalSubscription, and PayPalOneTime. Adding Apple Pay would have meant four more classes. That's when I refactored to Bridge—and the next gateway took one class, not four.

Frequently Asked Questions

What is the Bridge design pattern in Java?

Bridge is a structural pattern that splits a large class or closely related classes into two independent hierarchies: abstraction and implementation. They connect through composition, allowing both to evolve without affecting each other.

When should I use the Bridge pattern?

Use Bridge when you have a proliferation of classes from combining two dimensions (e.g., shapes × renderers, payment types × gateways), or when you want to switch implementations at runtime.

How is Bridge different from Strategy?

Both use composition and delegation. Strategy swaps algorithms at runtime within a single class. Bridge separates two entire hierarchies that would otherwise be fused by inheritance. Bridge is structural; Strategy is behavioral.

Does Bridge support SOLID principles?

Directly. It supports Open/Closed (extend either hierarchy without modifying existing code) and Dependency Inversion (abstractions don't depend on concrete implementations).

Is Bridge overkill for small projects?

Yes, often. If you only have one implementation, or the dimensions won't grow, inheritance is simpler. Bridge pays off when you anticipate independent evolution on both sides.

Your Bridge Checklist

Before committing to a class hierarchy, ask:

  • Am I multiplying classes? If you have TypeA_Impl1, TypeA_Impl2, TypeB_Impl1—stop and consider Bridge.
  • Do I have two dimensions? If shapes and renderers both grow independently, split them.
  • Will implementations change? If you might swap databases or payment gateways, design for it now.
  • Am I ready for composition? Bridge requires accepting a reference in the constructor, not hardcoding it.

Start small. Refactor one exploding hierarchy. The clarity compounds.

Conclusion

The Bridge pattern isn't about complexity—it's about controlled growth. When two dimensions of your system need to evolve independently, Bridge gives them room to breathe. It trades a small upfront design decision for long-term freedom from class explosion.

I'll be honest: I don't use Bridge in every project. Small scripts don't need it. But for anything that touches payments, databases, or multiple platforms, it has saved me from rewriting the same logic over and over. Start with the problem, not the pattern. If you find yourself writing the same class name with different suffixes, Bridge is probably the answer.

Related topics: SOLID Principles in Java, Adapter Design Pattern, Strategy Design Pattern, Java Composition vs Inheritance, Structural Design Patterns