Intent
Decouple an abstraction from its implementation so the two can vary independently. Instead of a deep inheritance hierarchy (N×M subclasses), use composition — the abstraction holds a reference to the implementation (the bridge).
The class explosion problem: 3 notification types (Order, Marketing, Alert) × 3 channels (Email, SMS, Push) = 9 subclasses without Bridge. Add WhatsApp channel = 3 more subclasses, 12 total. With Bridge: 3 notification classes + 3 channel classes = 6. Adding WhatsApp = 1 new channel class. They vary independently.
JDBC is the canonical Bridge: java.sql.Connection = abstraction. Each database driver (PgConnection, MySQLConnection) = implementation. Your code uses the Connection API; the driver handles SQL dialect.
JavaNotification Bridge — Type × Channel Independence + JDBC Bridge
// IMPLEMENTATION INTERFACE (the "implementor" side of the bridge)
public interface NotificationChannel {
void send(String recipient, String title, String content);
String getChannelName();
boolean supportsRichContent();
}
// Concrete implementations (vary independently)
@Component
public class EmailChannel implements NotificationChannel {
private final JavaMailSender mail;
@Override
public void send(String to, String subject, String html) {
MimeMessage msg = mail.createMimeMessage();
new MimeMessageHelper(msg, true).setTo(to);
mail.send(msg);
}
public String getChannelName() { return "EMAIL"; }
public boolean supportsRichContent() { return true; }
}
@Component
public class SmsChannel implements NotificationChannel {
private final TwilioClient twilio;
@Override
public void send(String phone, String title, String text) {
twilio.messages().create(phone, Message.creator(phone, fromNumber, title + ": " + text));
}
public String getChannelName() { return "SMS"; }
public boolean supportsRichContent() { return false; }
}
// ABSTRACTION (uses the channel bridge via composition)
public abstract class Notification {
protected final NotificationChannel channel; // THE BRIDGE — composition, not inheritance
protected Notification(NotificationChannel channel) { this.channel = channel; }
public abstract void send(String recipient, String message);
}
// Refined Abstractions (vary independently of channel)
public class UrgentNotification extends Notification {
public UrgentNotification(NotificationChannel ch) { super(ch); }
@Override
public void send(String recipient, String message) {
channel.send(recipient, "🚨 URGENT", message);
}
}
public class ScheduledNotification extends Notification {
private final LocalDateTime at;
public ScheduledNotification(NotificationChannel ch, LocalDateTime at) { super(ch); this.at = at; }
@Override
public void send(String recipient, String message) {
scheduler.schedule(() -> channel.send(recipient, "Reminder", message), at);
}
}
// Usage: mix any abstraction with any channel — no subclass explosion
new UrgentNotification(emailChannel).send("admin@co.com", "Payment failed!");
new UrgentNotification(smsChannel).send("+91-9876543210", "Server down!");
new ScheduledNotification(pushChannel, tomorrow).send(deviceToken, "Daily report ready");
// 3 channels × 3 abstraction types = 6 classes (not 9)
// Adding WhatsApp: 1 new class. Zero changes to notification abstractions.
// JDBC — the canonical Bridge example
// Abstraction: java.sql.Connection
// Implementation: org.postgresql.jdbc.PgConnection (PostgreSQL driver)
// com.mysql.cj.jdbc.ConnectionImpl (MySQL driver)
Connection conn = DriverManager.getConnection("jdbc:postgresql://localhost/orders");
// conn is actually PgConnection, but your code sees only Connection
conn.prepareStatement("SELECT * FROM orders"); // same API for ALL databases
🔩 Where Bridge Is Used in Java Frameworks
JDBC DriverManager
Connection, Statement, ResultSet are the abstractions. Each database driver provides concrete implementations. Your SQL code works with the abstraction, never touching PostgreSQL or MySQL specifics.
Spring DataSource abstraction
DataSource (abstraction) can be backed by HikariCP, DBCP2, Tomcat pool, or embedded H2. Spring code calls dataSource.getConnection(); the underlying pool implementation is the bridge.
Logging Bridge — SLF4J
SLF4J API is the abstraction. Logback, Log4j2, JUL are implementations. The bridge is the SLF4JServiceProvider binding. Application uses SLF4J API; the implementation handles actual file writing.
Spring PlatformTransactionManager
Abstraction for transaction management. Concrete impls: JpaTransactionManager, DataSourceTransactionManager, JtaTransactionManager. @Transactional works with all of them via the bridge interface.
🎯 Interview Questions — Bridge
Bridge vs Strategy — both use composition. What's the difference?
Bridge: Structural pattern. Separates abstraction from implementation so BOTH can vary independently. The abstraction class IS-A abstraction; it HAS-A implementation via composition (the bridge). The focus is on structural decomposition to avoid class explosion. Example: Notification (abstraction hierarchy) + Channel (implementation hierarchy) — both have their own class trees.
Strategy: Behavioral pattern. Defines a family of interchangeable algorithms that are swapped at runtime. The context object has ONE strategy that defines HOW to do something. Focus is on behavioral variation. Example: PaymentService has a DiscountStrategy that can be swapped.
Key: Bridge has two independent class hierarchies. Strategy has one context with one interchangeable piece. Bridge is about structure; Strategy is about behavior.
How does JDBC implement Bridge? Why can you switch databases without code changes?
java.sql.Connection, Statement, ResultSet are the abstraction interfaces (defined in java.sql package in JDK). Each DB driver JAR provides concrete implementations: org.postgresql.jdbc.PgConnection, com.mysql.cj.jdbc.ConnectionImpl, etc. DriverManager.getConnection(url) loads the appropriate driver class via JDBC URL prefix and returns the concrete implementation — but your code only sees Connection.
To switch from PostgreSQL to MySQL: change JDBC URL + swap driver JAR. Zero application code changes. This is the Bridge benefit — abstraction layer insulates your code from implementation specifics.