japicmp icon indicating copy to clipboard operation
japicmp copied to clipboard

removing a method from an unstable interface breaks compatibility checks for implementing classes.

Open hgschmie opened this issue 4 months ago • 5 comments

Consider this interface:

@Unstable
interface Foo {
    default void one() {
       ... do something
    }

    void two();
}

and an implementing class

class Bar implements Foo {
    void two() {
       ... do something else
    }
}

(@Unstable is an annotation that we ignore for japicmp using @Unstable in the exclude config for the maven plugin).

We shipped this code for a while. It turns out that the "One" default method needs to go away. removing it, results in

WARNING: Incompatibility detected: Requires semantic version level MAJOR: JApiMethod [oldMethod=Bar.one(), newMethod=n.a., returnType=JApiReturnType [oldReturnTypeOptional=void, newReturnTypeOptional=void, changeStatus=REMOVED], getCompatibilityChanges()=[JApiCompatibilityChange{type=METHOD_REMOVED}]]

(or similar).

That was surprising. My assumption was, because the "Foo" interface is unstable and the method is not overridden in the implementing class, removing it would be no problem.

Adding the @Unstable annotation temporarily to class Bar is worse, because now the class "disappears" from the japicmp run and I get different errors.

Is this "works as designed"? How do I mark an interface default method in an unstable interface so that I can actually remove it without breaking japicmp?

hgschmie avatar Aug 01 '25 20:08 hgschmie

Consider this interface:

@Unstable interface Foo { default void one() { ... do something }

void two();

} and an implementing class

class Bar implements Foo { void two() { ... do something else } } (@Unstable is an annotation that we ignore for japicmp using @Unstable in the exclude config for the maven plugin).

We shipped this code for a while. It turns out that the "One" default method needs to go away. removing it, results in

WARNING: Incompatibility detected: Requires semantic version level MAJOR: JApiMethod [oldMethod=Bar.one(), newMethod=n.a., returnType=JApiReturnType [oldReturnTypeOptional=void, newReturnTypeOptional=void, changeStatus=REMOVED], getCompatibilityChanges()=[JApiCompatibilityChange{type=METHOD_REMOVED}]]

(or similar).

That was surprising. My assumption was, because the "Foo" interface is unstable and the method is not overridden in the implementing class, removing it would be no problem.

Adding the @Unstable annotation temporarily to class Bar is worse, because now the class "disappears" from the japicmp run and I get different errors.

Is this "works as designed"? How do I mark an interface default method in an unstable interface so that I can actually remove it without breaking japicmp?

jorgeybea90-rgb avatar Aug 03 '25 19:08 jorgeybea90-rgb

Do I understand it right, that you exclude interfaces with the annotation Unstable but not the implementing classes?

If this is what you are doing, I think it works as expected because the class Bar no longer has the method one. So clients of this class will brake when still using one.

siom79 avatar Aug 03 '25 19:08 siom79

Yes. The point is to convey to users that "the interface Foo is still under development" (The annotations are actually called @Alpha and @Beta) and we reserve the right to change things annotated with those. My expectation for japicmp would be that "if something is marked with such an annotation, then a change would result in a message or a warning, not an error".

Basically toning down the strict "that is not compatible" stance.

As japicmp checks for direct annotations, I would have expected that in the case of "implemented methods of an interface", the check would cascade "up the inheritance tree".

hgschmie avatar Aug 25 '25 20:08 hgschmie

Is there somewhere an overview of which annotations are recognized by japicmp?

kwin avatar Sep 29 '25 14:09 kwin

There is no predefined set of annotations. You can configure what annotation to use:

<parameter>
	<includes>
		<include>@my.AnnotationToInclude</include>
	</includes>
	<excludes>
		<exclude>@my.AnnotationToExclude</exclude>
	</excludes>

siom79 avatar Sep 29 '25 20:09 siom79