Bridge Design Pattern in Java

Learn the Bridge pattern in Java with a runnable report example. See how report types and output formats vary independently, and when to use Bridge.

Bridge pattern participants

The Bridge pattern separates an abstraction from its implementation so that each can vary independently. The abstraction defines what an operation does for its caller. It delegates part of that operation to an implementor, which defines how that part is performed. Because the two are connected through an object reference, a new variant on either side does not require a subclass for every possible pairing.

1. How the Bridge Pattern Works

Bridge describes the relationship between two sets of classes. The abstraction defines the operation used by the client and holds a reference to an implementor. The implementor defines the service that completes part of that operation. These names describe pattern roles: an abstraction can be an abstract class, and an implementor can be a Java interface.

Participant Responsibility
Abstraction Defines the operation the caller uses and holds a reference to an implementor.
Refined abstraction Adds one variation of the abstraction’s behavior.
Implementor Defines the service that implementations provide to the abstraction.
Concrete implementor Performs that service in one particular way.
Client Chooses which abstraction and implementor objects to combine.

The client combines one abstraction with one implementor. When the client calls the abstraction, the abstraction performs its own work and calls the implementor through its reference. A new refined abstraction can use an existing implementor, and a new concrete implementor can serve existing abstractions, provided the implementor’s contract still supports their needs.

Generic Bridge participants: a client selects a refined abstraction and concrete implementor; the abstraction holds the implementor

2. Bridge Pattern in Action

An order application needs reports in two places: a console and a web page. It also needs two levels of detail. A short report shows how many orders were completed, while a detailed report includes their total. Report detail and output format are separate choices. If both choices were represented by subclasses alone, the application would need a class for every combination, such as a short console report and a detailed HTML report.

In this example, the report classes choose the content, and renderer classes choose the format. For example, the application can request a detailed report in HTML by constructing it with an HTML renderer:

Report report = new DetailedReport(new HtmlRenderer());
String output = report.generate();

The DetailedReport decides which title and body describe the report. Its generate() method passes those values to the HtmlRenderer through the Renderer interface. The returned output is an HTML fragment containing the detailed report’s content. Changing the renderer changes the format without changing the report class.

Pattern role Example type What it does
Abstraction Report Holds a Renderer and defines generate().
Refined abstractions ShortReport, DetailedReport Choose the title and body sent to the renderer.
Implementor Renderer Defines render(title, body).
Concrete implementors PlainRenderer, HtmlRenderer Format the title and body as plain text or HTML.
Client BridgeDemo Creates each report with a chosen renderer.

The pattern roles map to the example as follows. The report constructor receives a Renderer and stores it for later use. When generate() runs, the report calls render(title, body) on that stored renderer and returns the formatted result to the caller. The report therefore controls what is reported, while the renderer controls how it is represented.

Bridge report example: two Report subclasses delegate to either PlainRenderer or HtmlRenderer through Renderer

3. Bridge Pattern Implementation

The Renderer interface is the contract between the two sides. Both implementations receive the same title and body, and each returns a String. PlainRenderer joins those values on one line for console output. HtmlRenderer wraps them in heading and paragraph elements for a web page. The report classes can call either implementation without knowing its formatting rules.

interface Renderer {
    String render(String title, String body);
}

final class PlainRenderer implements Renderer {
    public String render(String title, String body) {
        return title + " | " + body;
    }
}

final class HtmlRenderer implements Renderer {
    public String render(String title, String body) {
        return "<h1>" + escape(title) + "</h1><p>" + escape(body) + "</p>";
    }

    private String escape(String value) {
        return value.replace("&", "&amp;").replace("<", "&lt;")
            .replace(">", "&gt;").replace("\"", "&quot;")
            .replace("'", "&#39;");
    }
}

HtmlRenderer escapes the title and body before inserting them as HTML text. For example, a title containing < must be shown as text rather than interpreted as the start of a tag. This helper covers text inside the two elements shown here. A production web application should use a template system that escapes each output context, including attributes and URLs, according to that context.

Report stores the selected renderer in a final field. Its constructor calls Objects.requireNonNull() so a missing renderer fails when the report is created, rather than failing later in generate(). Each subclass supplies a title and body to the renderer. Neither subclass decides whether those values become plain text or HTML.

abstract class Report {
    protected final Renderer renderer;

    Report(Renderer renderer) {
        this.renderer = Objects.requireNonNull(renderer);
    }

    abstract String generate();
}

final class ShortReport extends Report {
    ShortReport(Renderer renderer) { super(renderer); }

    String generate() {
        return renderer.render("Orders", "2 orders completed");
    }
}

final class DetailedReport extends Report {
    DetailedReport(Renderer renderer) { super(renderer); }

    String generate() {
        return renderer.render("Orders", "2 orders completed, total 4500 cents");
    }
}

These excerpts omit the package declaration, the java.util.Objects import, and the entry point. The complete runnable file is linked after the conclusion.

4. Demo

The client can select either report with either renderer. Run BridgeDemo from the repository root to see all four combinations:

javac -d out src/main/java/com/howtodoinjava/patterns/structural/bridge/BridgeDemo.java
java -cp out com.howtodoinjava.patterns.structural.bridge.BridgeDemo
public class BridgeDemo {
    public static void main(String[] args) {
        System.out.println(new ShortReport(new PlainRenderer()).generate());
        System.out.println(new ShortReport(new HtmlRenderer()).generate());
        System.out.println(new DetailedReport(new PlainRenderer()).generate());
        System.out.println(new DetailedReport(new HtmlRenderer()).generate());
    }
}
Orders | 2 orders completed
<h1>Orders</h1><p>2 orders completed</p>
Orders | 2 orders completed, total 4500 cents
<h1>Orders</h1><p>2 orders completed, total 4500 cents</p>

The first line is the short report in plain text. The second line contains the same order count in HTML, because only the renderer changed. The third and fourth lines repeat that format change for the detailed report, which adds the total of 4500 cents. The fourth combination needs no DetailedHtmlReport class: DetailedReport already supplies the content, and HtmlRenderer already supplies the format.

5. Similar Patterns

Pattern Choose it when
Bridge Two aspects of the design need separate sets of variants, as with report content and format.
Adapter Existing code has an interface the caller cannot use directly; an adapter translates between them.
Strategy The main choice is which algorithm or behavior performs one task.

All three patterns can use composition, in which one object holds a reference to another. Choose Bridge when the application needs independent variants on both sides of that relationship. Adapter makes an existing interface usable through another interface; Strategy selects among interchangeable behaviors for a task. If there is only one report and one output format, introducing both Bridge hierarchies adds little value.

6. FAQs

6.1. Does the abstraction have to be a Java interface?

No. Report is an abstract class because it stores the renderer and shares constructor logic. The pattern names describe responsibilities rather than required Java declarations.

6.2. Can the renderer change while a report object is in use?

Yes, if the report class allows its renderer to be replaced after construction. This example keeps the renderer field final, so the pairing is chosen at construction. The pattern does not require either choice.

6.3. Does adding a report type always leave renderers untouched?

Only while the render(title, body) contract is sufficient. A report that needs tables or images may require a richer contract and changes to existing renderers. Bridge separates the two kinds of variation; it does not remove their shared agreement.

7. Conclusion

Bridge is a good choice when both the operation offered to callers and the service it delegates to need separate variants. The two sides remain connected by the implementor’s contract, so expand that contract carefully if a new report needs more than a title and body.

Happy Learning!

The complete Bridge example on GitHub contains the runnable source and README.

Leave a Comment

  1. You said that “Decouple an abstraction from its implementation so that the two can vary independently.”
    However, in your example, both implementation and abstraction have the same methods in which situations do you need to add more functionality to an interface?
    Because I’m still confused about the pattern

  2. After reading ur article i understood properly bridge pattern. I am using it everday for controller,service,repository pattern each of them evolve separately. One of the best example explained clearly.

  3. In FileDownloaderAbstractionImpl.java

    Isn’t it better to replace
    ‘private FileDownloadImplementor provider = null;’
    by
    ‘private final FileDownloadImplementor provider;’
    ?

Comments are closed.

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.