How do bridge methods interact with covariant return types when overriding a method?
answer
- covariant return = override returns a subtype
- JVM dispatch ignores return type → mismatch
- bridge with parent's return type forwards to your override
- generic erasure of return type produces the same bridge
- one bridge can both cast args and adapt return
basics
~20 sCovariant returns let an override return a more specific type than the parent method. The JVM can't naturally dispatch on return type, so the compiler adds a bridge method with the parent's return type that calls your override and returns its result.
solid answer
~50 sBridge methods are not only about generics — they also implement covariant return types. A covariant return is when an override declares a narrower return type than the method it overrides, e.g. parent returns Object and the override returns String. The JVM matches overrides on name and parameter types only, not on return type, so an override with a different return type isn't recognized as the same method at the bytecode level. The compiler therefore emits a synthetic bridge with the *parent's* return type that delegates to your real, narrower-returning method and hands back its result (an implicit widening). With generics, the two reasons combine: overriding a generic method that already has covariant returns can produce a bridge that both casts the erased parameter and adjusts the return type. The bridge is again flagged ACC_BRIDGE/ACC_SYNTHETIC and is invisible in source.
code
java · 12 linesinterface Box<T> { T get(); }
class StringBox implements Box<String> {
public String get() { return "hi"; } // you write this
// compiler generates (ACC_BRIDGE, ACC_SYNTHETIC):
// public Object get() { return get(); } // returns String as Object
}
// javap -p StringBox shows BOTH:
// public java.lang.String get();
// public java.lang.Object get(); // the bridgego deeper
Aware that an override can return a more specific type, but typically unaware that a hidden bridge implements it.
Can describe covariant returns and that the compiler does extra work, but may not state that the JVM ignores return type in dispatch.
Explains that override identity is name + parameters (not return type), why that forces a bridge with the parent's return type, and how this combines with generic erasure.
Reasons about the descriptor-level dispatch model, framework/tooling impact of seeing both bridge and real method, annotation-copying history on bridges, and where covariant-return design choices affect API evolution.
## Two independent reasons bridges exist Bridge methods solve two distinct mismatches between what you write and what the JVM dispatches on: 1. **Type erasure of generics** — the parameter types of a generic supertype method get erased, so a specific override has a different parameter signature (covered separately). 2. **Covariant return types** — this question's focus. ## What a covariant return type is Since Java 5, an overriding method may declare a **return type that is a subtype** of the overridden method's return type. Example: ```java class Animal { Animal reproduce() { return new Animal(); } } class Cat extends Animal { @Override Cat reproduce() { return new Cat(); } // returns Cat, narrower than Animal } ``` Returning `Cat` instead of `Animal` is *covariant*: callers expecting an `Animal` are still satisfied, and callers holding a `Cat` get the precise type without a cast. This is purely a source/compiler feature. ## Why the JVM needs help At the bytecode level, a method's identity for override purposes is its **name plus parameter types** — the *return type is part of the descriptor but the JVM does not allow two methods that differ only in return type to be treated as overrides of each other*. The virtual dispatch table is keyed by `name + parameter descriptor`. So `Animal reproduce()` and `Cat reproduce()` have different descriptors (`()LAnimal;` vs `()LCat;`) and the second would NOT override the first as far as the JVM is concerned. A call through an `Animal` reference would look for `()LAnimal;` and miss the `Cat` version. ## The bridge fix The compiler generates, inside `Cat`, a synthetic bridge with the **parent's** return type: ```java // compiler-generated in Cat: ACC_BRIDGE, ACC_SYNTHETIC Animal reproduce() { return reproduce(); // calls the real Cat reproduce(), returns Cat as Animal } ``` This bridge has descriptor `()LAnimal;`, so it genuinely overrides `Animal.reproduce()`. Its body invokes the real narrower-returning `Cat.reproduce()` and returns the result, which widens `Cat` to `Animal` implicitly. Polymorphic dispatch through an `Animal` reference now reaches the bridge, which forwards to your override. ## Combining with generics The two reasons can stack. Consider a generic interface with covariant overriding: ```java interface Box<T> { T get(); } class StringBox implements Box<String> { public String get() { return "hi"; } } ``` After erasure `Box.get()` is `Object get()`. `StringBox.get()` returns `String`. The compiler emits a bridge `Object get()` that calls `String get()` and returns the `String` as `Object` — here the bridge is needed for the erased *return* type. If the generic method also took a type parameter as an argument, the bridge would additionally cast the parameter. So a single bridge can simultaneously cast erased arguments and adapt the return type. ## Recognizing and handling it Same as any bridge: `ACC_BRIDGE`/`ACC_SYNTHETIC`, visible via `javap -p` or `Method.isBridge()`. Frameworks that scan methods (DI containers, mappers, mocking libraries) must filter bridges, because for a covariant override you will see both `Object get()` (bridge) and `String get()` (real). Picking the bridge by accident can lose annotations (annotations are usually copied to bridges since Java 8, but historically were not) or give you the less-specific return type. ## Key takeaways - Covariant return = override returns a subtype of the parent's return type. - The JVM doesn't dispatch on return type, so the compiler adds a bridge with the parent's return type that forwards to your override. - This bridge mechanism predates and is separate from generics, but generic erasure produces the same kind of bridge, and the two can combine in one method.
- Are covariant return types a JVM feature or a compiler feature?A compiler feature. The JVM matches overrides by name and parameter types and won't treat methods differing only in return type as overrides, so javac emits a bridge to make it work.
- Can one bridge method handle both an erased parameter and a covariant return at once?Yes. When overriding a generic method that returns a type parameter and takes a type parameter, the single generated bridge can cast the erased argument and return the narrower result as the erased type.
saying these in an interview costs you the question
- Saying covariant returns are a runtime/JVM feature — they are a compiler feature implemented via bridges.
- Claiming the JVM dispatches overrides on return type — it dispatches on name + parameter types only.
- Confusing covariant returns with covariant parameters (the latter is unsafe and not allowed).
- Assuming bridges only ever exist because of generics.