Question 25 · Section 20

What are bridge methods and why are they needed

This happens due to type erasure — when the generic type is erased, method signatures change.

Language versions: English Russian Ukrainian

🟢 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

  1. 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 via javap -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 ClassName or method.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]]