Back to Design Pattern Course

Design Pattern Course

Bridge is a Structural Design Pattern.

Bridge separates an abstraction from its implementation so the two can vary independently — meaning you can change or extend either side without the other needing to change. Without Bridge, evolving both dimensions simultaneously requires a combinatorial explosion of subclasses. A classic textbook illustration: if you have two shapes (Circle, Square) and two rendering APIs (OpenGL, DirectX), you'd need four classes without Bridge — and every new shape or rendering API multiplies the count further. With Bridge, you have two independent hierarchies connected by composition, and you can add a new shape or a new renderer without touching the other hierarchy.

The "classic example of Bridge is Drivers (e.g., JDBC)" commonly cited in textbooks holds up: your application code (the abstraction) calls Connection, Statement, ResultSet without knowing whether it's talking to MySQL, PostgreSQL, or Oracle. The concrete JDBC driver implementation can be swapped at runtime via DriverManager without application code changing at all — the Bridge (the JDBC API) decouples the two.

public interface MessageSender { void sendMessage(); }
public abstract class Message { // Composition: the abstraction holds a reference to the implementor protected MessageSender messageSender; Message(MessageSender messageSender) { this.messageSender = messageSender; } abstract void send(); }
public class EmailMessage extends Message { EmailMessage(MessageSender messageSender) { super(messageSender); } @Override void send() { messageSender.sendMessage(); } }
public class TextMessage extends Message { TextMessage(MessageSender messageSender) { super(messageSender); } @Override void send() { messageSender.sendMessage(); } }
public class EmailMessageSender implements MessageSender { @Override public void sendMessage() { System.out.println("Email Message Sent"); } }
public class TextMessageSender implements MessageSender { @Override public void sendMessage() { System.out.println("Text Message Sent"); } }
public class MessageTest { public static void main(String[] args) { MessageSender textSender = new TextMessageSender(); Message textMessage = new TextMessage(textSender); textMessage.send(); MessageSender emailSender = new EmailMessageSender(); Message emailMessage = new EmailMessage(emailSender); emailMessage.send(); } }

The Bridge is the messageSender field in Message — that reference to the MessageSender interface is the "bridge" that connects the message-type hierarchy (EmailMessage, TextMessage) to the transport-implementation hierarchy (EmailMessageSender, TextMessageSender). You can add a new message type (e.g., PushNotificationMessage) or a new transport (e.g., SlackMessageSender) without touching the other side.

Bridge relies on composition over inheritance as its core mechanism. A common mistake when first learning Bridge is confusing it with the Strategy pattern — they look structurally similar (both hold a reference to a pluggable interface). The distinction is intent: Strategy varies the algorithm/behaviour within a single object (sorting strategies, payment processing strategies); Bridge varies the implementation platform of an entire abstraction, and both the abstraction and implementation hierarchies are expected to grow independently.

Benefits

  • Decouples abstraction from implementation so both can evolve independently.
  • Eliminates the M×N class explosion you'd otherwise get when two independent dimensions both need to vary.
  • Supports the Open/Closed Principle: new abstractions and new implementations can be added without modifying existing classes.
  • Implementation can be swapped at runtime (injected through the constructor or a setter).

Limitations

  • Adds indirection and upfront design complexity — for simple cases where there's only one implementation, the extra hierarchy isn't justified.
  • Identifying the "two dimensions" that should be bridged requires good domain understanding; poorly chosen split points make the design more confusing, not less.

Bridge Pattern Examples in the JDK

  • JDBC — the clearest real-world Java example. java.sql.Connection, java.sql.Statement, java.sql.ResultSet are the abstraction; vendor JDBC drivers (com.mysql.cj.jdbc.Driver, org.postgresql.Driver, etc.) are the implementation. Your application never changes when you change database vendors.
  • AWT — java.awt provides the abstraction layer; the platform-specific peer objects (mapped to the native OS windowing system) are the implementation. AWT calls the right native windowing implementation for Windows, macOS, or Linux without your Swing/AWT code knowing which it is.