What are bridge methods and why are they needed
This happens due to type erasure — when the generic type is erased, method signatures change.
🟢 Junior Level
Bridge method — a special method that the Java compiler creates automatically to preserve polymorphism when working with generics.
This happens due to type erasure — when the generic type is erased, method signatures change.
public class Node<T> {
private T data;
public void setData(T data) { this.data = data; }
public T getData() { return data; }
}
public class StringNode extends Node<String> {
@Override
public void setData(String data) { super.setData(data); }
@Override
public String getData() { return super.getData(); }
}
After type erasure, Node<String>.setData(String) becomes setData(Object), but StringNode.setData(String) stays setData(String). These are different methods!
To make polymorphism work, the compiler creates a bridge method:
// Automatically added to StringNode
public void setData(Object data) {
setData((String) data); // calls the original method
}
🟡 Middle Level
Why bridge methods are needed
Problem:
// Before type erasure:
Node<String> node = new StringNode();
node.setData("Hello"); // should call StringNode.setData(String)
// After type erasure:
Node node = new StringNode();
node.setData("Hello"); // Node.setData(Object) — but StringNode only has setData(String)!
Solution — bridge method:
class StringNode extends Node {
// Original method
public void setData(String data) { /* ... */ }
// Bridge method (created by compiler)
public void setData(Object data) {
setData((String) data); // delegates to original
}
}
How to see bridge methods
Via reflection:
for (Method m : StringNode.class.getDeclaredMethods()) {
System.out.println(m.getName() + " " +
Arrays.toString(m.getParameterTypes()) +
" bridge=" + m.isBridge());
}
// Output:
// setData [class java.lang.String] bridge=false
// setData [class java.lang.Object] bridge=true
Via javap:
javap -p StringNode.class
Common mistakes
- Not understanding why a method is called: ```java StringNode node = new StringNode(); node.setData(new Object()); // ClassCastException!
// Bridge method is called -> cast to String -> exception
2. **Annotations on bridge methods:**
```java
public class StringNode extends Node<String> {
@Override
@MyAnnotation // ❌ won't appear on bridge method
public void setData(String data) { }
}
🔴 Senior Level
Internal Implementation
JLS Specification:
JLS 15.12.4.5:
- When overriding a method with a different signature (after erasure)
- The compiler creates a bridge method with the parent class's signature
- Bridge method delegates to the original method with a cast
Desugaring:
// Source code
public class Node<T> {
public T getData() { return data; }
public void setData(T data) { this.data = data; }
}
public class StringNode extends Node<String> {
@Override
public String getData() { return super.getData(); }
@Override
public void setData(String data) { super.setData(data); }
}
// After compilation:
class Node {
public Object getData() { return data; }
public void setData(Object data) { this.data = data; }
}
class StringNode extends Node {
public String getData() { return (String) super.getData(); }
// Bridge method for getData
public Object getData() { return getData(); }
public void setData(String data) { super.setData(data); }
// Bridge method for setData
public void setData(Object data) { setData((String) data); }
}
Architectural Trade-offs
Bridge methods:
| Pros | Cons |
|---|---|
| Preserve polymorphism | Additional methods |
| Transparent to developer | Confusion in stack trace |
| Allow covariant return types | Annotations not inherited |
Edge Cases
1. Covariant return type:
public class Parent {
public Number getValue() { return 0; }
}
public class Child extends Parent {
@Override
public Integer getValue() { return 0; }
}
// Bridge method in Child:
// public Number getValue() { return getValue(); } // delegate to Integer version
// Bridge returns Number, calls Integer getValue().
// Auto-boxing is handled separately — Integer is already an object.
2. Multiple inheritance:
// Java does NOT allow implementing two interfaces with the same signature
// but different return types. The compiler rejects this at source code level.
// Bridge methods are generated only for covariant returns.
3. Generic methods:
public class Util {
public static <T> T identity(T t) { return t; }
}
// When called with different types — one method after erasure
Util.identity("Hello"); // T = String
Util.identity(42); // T = Integer
// Both call identity(Object)
4. Annotation workaround:
// Annotations don't appear on bridge method
// Solution: use @Inherited meta-annotation or reflection
// that checks isBridge() and finds the original method.
Performance
Bridge method overhead:
- One additional call (delegate)
- JIT inlines bridge methods
- Overhead: ~0-1 ns (practically zero)
In production: negligible
Production Experience
JDK examples:
// Collections — bridge methods for covariant returns
public class ArrayList<E> extends AbstractList<E> {
@Override
public Object clone() { return super.clone(); }
// Bridge: public Object clone() from Object
}
// Enum — bridge for compareTo
public abstract class Enum<E extends Enum<E>> implements Comparable<E> {
public final int compareTo(E o) { return ordinal - o.ordinal; }
// Bridge: public int compareTo(Object o) { return compareTo((E) o); }
}
Reflection with bridge methods:
Method[] methods = StringNode.class.getDeclaredMethods();
for (Method m : methods) {
if (m.isBridge()) {
System.out.println("Bridge: " + m);
// Find original method
Method original = findOriginal(m);
System.out.println("Original: " + original);
}
}
Serialization:
// Bridge methods are not serialized separately
// Only the class is serialized, methods are restored
Best Practices
// ✅ Don't worry about bridge methods — they are transparent
// ✅ Use @Override for clarity
@Override
public String getData() { return super.getData(); }
// ⚠️ Annotations don't appear on bridge method
// ✅ If annotations are needed — add to both methods
// ❌ Don't call bridge methods directly
// ❌ Don't rely on existence of bridge methods
🎯 Interview Cheat Sheet
Must know:
- Bridge method — synthetic method created by the compiler to preserve polymorphism
- Reason: type erasure changes method signatures, breaking override
- Bridge method delegates to the original method with a cast of parameters
- Can be seen via reflection:
method.isBridge()or viajavap -p - Annotations do NOT appear on bridge methods — need to add to both methods
- Overhead negligible: JIT inlines bridge methods (~0-1 ns)
Frequent follow-up questions:
- Why are bridge methods needed? — Preserve polymorphism after type erasure (parent method signature != child)
- How to see bridge methods? —
javap -p ClassNameormethod.isBridge()via reflection - Can bridge methods cause ClassCastException? — Yes, if called with the wrong type
- Do annotations work on bridge methods? — No, only on the original method
Red flags (DO NOT say):
- ❌ “Bridge methods are developer-written methods” — They are created by the compiler automatically
- ❌ “Bridge methods add significant overhead” — JIT inlines them, overhead ~0-1 ns
- ❌ “Annotations automatically appear on bridge methods” — No, meta-annotation or manual handling needed
- ❌ “Bridge methods are a Java bug” — This is intentional design for compatibility with type erasure
Related topics:
- [[11. What are Generics in Java]]
- [[13. What is type erasure]]
- [[22. Can you overload methods that differ only in generic parameters]]
- [[24. How do Generics work with inheritance]]