False positive METHOD_RETURN_TYPE_CHANGED for covariant return type overrides (original) (raw)
Summary
When a class overrides an interface method with a covariant return type (i.e. a more
specific subtype), japicmp reports METHOD_RETURN_TYPE_CHANGED and treats this as a
binary incompatibility. However, this is a false positive: the Java compiler automatically
generates a bridge method with the original (erased) return type, ensuring full binary
compatibility for existing callers.
Background
This was discovered in the context of apache/maven-resolver#1888
after upgrading to japicmp 0.25.7 (which correctly fixed bridge-method matching via #507).
The class org.eclipse.aether.util.version.GenericVersionScheme implements VersionScheme
and intentionally uses covariant return types:
// Interface VersionScheme declares: Version parseVersion(String version) throws InvalidVersionSpecificationException; VersionRange parseVersionRange(String range) throws InvalidVersionSpecificationException; VersionConstraint parseVersionConstraint(String constraint) throws InvalidVersionSpecificationException;
// GenericVersionScheme overrides with covariant return types: @Override public GenericVersion parseVersion(String version) { ... } // GenericVersion extends Version
@Override public GenericVersionRange parseVersionRange(String range) { ... } // GenericVersionRange extends VersionRange
@Override public GenericVersionConstraint parseVersionConstraint(String c) { ... } // GenericVersionConstraint extends VersionConstraint
The Java compiler generates bridge methods for each override, so the bytecode of
GenericVersionScheme actually contains both:
- GenericVersion parseVersion(String) — the real implementation
- Version parseVersion(String) — compiler-generated bridge, delegates to the real method
japicmp 0.25.6 silently missed this change (it incorrectly matched the old non-bridge method
to the new bridge method).
japicmp 0.25.7 correctly stopped doing that —but now the change is reported as METHOD_RETURN_TYPE_CHANGED and treated as a binary incompatibility.
Why This Is Not a Binary Incompatibility
Java's covariant return type mechanism is designed precisely to be backward-compatible:
- Binary compatibility: Old code compiled against Version parseVersion(String) will
find the bridge method at runtime — NoSuchMethodError cannot occur. - Source compatibility: The Java Language Specification (JLS §8.4.5) explicitly allows
covariant return types in overriding methods.
Intentional design: The bridge method is not an implementation detail to be ignored —
it is a contractual guarantee of the Java compiler for this exact scenario.
The one legitimate edge case where this CAN break is subclassing: if a third-party class
extends the changed class and overrides the method with the old (non-covariant) return type,
that override silently stops being an override of the new method. This is worth detecting,
but it is a different and less severe category than a full binary break.
Proposed Solution
Introduce a new, distinct compatibility change type for this scenario:
METHOD_RETURN_TYPE_COVARIANT_CHANGED
Conditions for this classification (all must hold):
- The new return type is a strict subtype of the old return type.
- A bridge method with the original return type exists in the new class.
- The method is an override of an interface or superclass method.
This new type should be:
- Reported (the change is real and worth knowing about)
- Treated as compatible by default (not binary-incompatible)
Related
PR #508 / Issue #507 — fixed bridge-method matching (prerequisite fix, directly related)
apache/maven-resolver#1888 — real-world trigger for this issue