Skip to content

API Tools reports compatible method pull-up through non-API superclass as breaking #2499

Description

@estepper

API Tools can incorrectly require a major version bump when a public API method remains available through inheritance from a non-API superclass.

Example:

// baseline
public class B extends A implements I
{
  public void m()
  {
  }
}
// current
public interface J extends I
{
  void m();
}

// non-exported package
public class X extends A implements J
{
  public void m()
  {
  }
}

public class B extends X
{
}

For consumers of B, the public method remains available:

B b = ...;
b.m();

X itself is not API, but its public method is inherited by B.

API Tools currently reports two independent breaking changes for this hierarchy:

  • EXPANDED_SUPERINTERFACES_SET_BREAKING, because implementation lookup does not consider the non-API superclass;
  • REMOVED | METHOD, because method pull-up detection does not consider the non-API superclass and therefore misses METHOD_MOVED_UP.

Both checks use superclass lists filtered by API visibility although they need to inspect the actual Java superclass hierarchy.

This is particularly problematic because the resulting package-level version error is not accompanied by a filterable culprit problem that could be suppressed to eliminate the version error. As a result, affected refactorings cannot be handled with a targeted API problem filter and effectively force an unnecessary major version increase.

The fix includes non-API superclasses for these implementation lookups.

For METHOD_MOVED_UP, the matching inherited method is additionally checked for compatibility so the broader lookup does not hide real breaking changes such as:

  • public → protected visibility narrowing;
  • instance → static changes;
  • concrete → abstract changes;
  • non-final → final changes where extending is unrestricted.

For expanded-superinterface checks, a matching method inherited from a non-API superclass is only considered an implementation if it is public, non-static, concrete, and non-synthetic. This prevents an abstract matching method in an internal superclass from incorrectly suppressing EXPANDED_SUPERINTERFACES_SET_BREAKING.

Regression coverage includes the compatible pull-up scenario, the incompatible moved-method counterexamples, and the abstract internal-superclass counterexample for the expanded-superinterface check.

Validation:

  • ClassDeltaTests: 163 passed
  • VersionTest: 20 passed
  • ClassCompatibilityHierarchyTests: 22 passed
  • complete org.eclipse.pde.api.tools.tests suite: 1,027 passed, no failures or errors

Activity

  1. HannesWell commented on Sep 22, 2026

    @HannesWell
    Member

    API Tools can incorrectly require a major version bump when a public API method remains available through inheritance from a non-API superclass.

    First, while moving a method to a super-class is a source compatible change, I'm in doubt it's a binary-compatible change. IIRC in callers the resolved class defining the method is recoded in the byte code. So moving it up, would break these existing classes and it would make sense to consider it a breaking change. Please check if moving methods up is also binary compatible.

    Second, when you say from a non-API superclass., I again think it's fine to consider it a breaking change if the method is declared in that non-API superclass, since it has effectively vanished from the API and a caller should not (or cannot) access its definition (probably only if it's redeclared in an API class).
    But from your example I assume the declaring interface/class is even further up in the hierarchy and API again?

  2. estepper commented on Sep 23, 2026

    @estepper
    ContributorAuthor

    Hi Hannes,

    Good point. I checked the JLS: it explicitly lists moving a method up the class hierarchy as binary compatible. An already compiled call such as B.m() remains resolvable; the JVM also searches the superclass hierarchy.

    I don’t think X being non-exported changes that: client code still refers to B, not X, and B inherits the public method. Of course, changes such as narrowing visibility or switching between instance and static methods must still be treated as breaking, which is why we validate the inherited method before classifying it as a compatible pull-up.

    Do you see a specific reason for API Tools to treat this more strictly than JVM binary compatibility?

    Cheers
    Eike

  3. estepper commented on Sep 23, 2026

    @estepper
    ContributorAuthor

    I checked the compatibility concern in more detail.

    The original hierarchy change is binary-compatible: B.m() remains resolvable through the new superclass X, and both changing the superclass hierarchy without losing former supertypes and moving a method up the hierarchy are covered by JLS 13.4.4 / 13.4.6.

    While reviewing the broader superclass lookup, I did find one concrete case where the initial fix was too permissive: an exact-signature abstract method in a non-API superclass could have been mistaken for a valid interface implementation.

    I tightened checkSuperInterfaces() accordingly. A matching superclass method is now only accepted if it is public, non-static, concrete, and non-synthetic. A regression test covers the abstract-superclass case.

    The full API Tools test suite passes: 1,027 tests, no failures or errors.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions