Revapi Java Extension

Revapi extension to check API of java classes in jar archives.

The Java extension somewhat obviously provides API checking capability for Java libraries. It contains a large number of checks that check different aspects of changes in the API, but it, too, is extensible. That is useful in cases where Java methods are annotated using annotations like JPA or JAXB. One can imagine new checks on methods and fields annotated by those annotations to perform additional analysis for DB schema changes or XML serialization changes.

Overview

The Revapi java extension performs a thorough class and method signature analysis and detects quite a large number of (potential) problems and changes of the code in the two versions of the API.

Note
There are over 80 kinds of detected changes (of which some don’t represent an actual problem with the API but merely a compatible change (a change nevertheless)).

You can find below all the detected differences, but as a sneak peak consider the following detected changes:

  • checked exception added/removed to/from method signature - both of these are source incompatible but binary compatible changes,

  • formal type parameter added to class/method signature - this is again source incompatible but binary compatible,

  • non-final method replaced by a final method in superclass - huh, binary compatible, source potentially incompatible, potentially producing VerifyError at linkage time. What is this actually? V1 of the library contained a class C with method m(). Class C inherited from class B. V2 changed this such that C still inherits from B but method m() is now final and declared in B. This change is OK unless client code inherits from C and overrides the method in question.

  • serialVersionUid unchanged - reported when there’s been a change in the class that would change the automatically generated serialization version, but the manually assigned serialVersionUid field remained with the same value in the new version of the library.

  • non-public part of API - a class that is non-public was detected to be reachable from the public API. I.e. there exists a non-public type that is used in a public capacity - as a return type or a parameter of an accessible method - this is checked recursively. E.g. in the example below, the Internal class is flagged:

//------------------------ API.jar

public class API {
  public Model getModel() {
    return null;
  }
}

//------------------------ Dependency.jar

public class Model {
  protected Internal getState() {
    return null;
  }
}

/*package private*/ class Internal {
}

This is because the api class API returns the Model class from its public method and therefore makes the Model class formally part of the API even though it comes from a dependency. The Model class then is non-final and returns an Internal instance from its method. Thus, it is conceivable that a user of the API.jar might want to extend and use the Model class at which point he’d be unable to use the correct type of the getState() result.

List of API Differences

When Revapi reports the found API problems it needs to pinpoint the element it is "talking about". Therefore it assigns a one-line representation to each API element to identify it. It tries to follow the conventions of Java syntax but needs to go beyond it to express additional information.

First, let’s go through some simple examples.

class com.example.MyClass

This somewhat obviously refers to a class called MyClass in the com.example package.

class com.example.MyGenericClass<T extends java.lang.Number>

This again refers to the class but you can see that Revapi also includes the type parameters in the class name. Note that the bounds of the type parameters are specified using fully qualified class names.

interface com.example.MyInterface

Here we see that different kinds of classes are distinguished. Revapi uses @interface for annotation types, enum for enumerations, interface for interfaces and class for ordinary classes.

missing-class com.example.Whoops

What’s this? Here, Revapi tries to convey that the class in question wasn’t found anywhere on the classpath.

method java.lang.String com.example.MyClass::myMethod(int, long)

If you are familiar with the method reference syntax in Java 8, you may guess that the above refers to a method called myMethod declared in the com.example.MyClass class that returns a java.lang.String and accepts an int as its first argument and a long as its second argument. As with classes, the type mentioned in the method identifier may include the type parameters. The method identifier also includes the throws declaration if present on the method.

parameter java.lang.String com.example.MyClass::myMethod(int, ===long===)

If Revapi needs to "talk" about a single parameter of a method, it encloses the parameter in ===. The above therefore identifies the second parameter of the method myMethod.

field com.example.MyClass.myField

Quite intuitively, the above identifies the field called myField in the class com.example.MyClass.

method void com.example.MySuperClass::superMethod() @ com.example.MyClass

This is again a little bit cryptic. The above identifies the method superMethod declared in com.example.MySuperClass as seen in com.example.MyClass. In another words, Revapi distinguishes between a declared method and inherited method. This is because a public method in a private class can be made "accessible" in the API if the private class is inherited by some public class in the API.

Types
class the.fully.qualified.class.name.including.Any<Type extends Params>

where class can be class, interface, enum or @interface depending on the kind of class in question, or in case the type is missing in the API:

missing-class fully.qualified.class.name.including.Any<Type extends Params>
Methods

Declared method:

method ReturnType fully.qualified.class.name.of.the.declaring.class::methodName(All, The, Params) throws Any, Exceptions

Inherited method:

method ReturnType fully.qualified.class.name.of.the.declaring.class::methodName(All, The, Params) throws Any, Exceptions @ the.class.that.inherits.the.Method

Revapi distinguishes between declared and inherited methods because a public method in a private class can be made "accessible" in the API if the private class is inherited by some public class in the API.

Method Parameters

This is where the syntax differs most from java. The === highlight the parameter in question among the others. Note also that the parameter names are not available because more often than not they’re not available in the bytecode.

Parameter on a declared method:

parameter ReturnType class.name.of.declaring.class::methodName(===ParameterType===, Other, Params)

Parameter on an inherited method:

parameter ReturnType class.name.of.declaring.class::methodName(===ParameterType===, Other, Params) @ class.name.of.inheriting.class

Because method parameter is tied to the method, the distinction between a declared and inherited method is exhibited in the identifier of the method parameter, too.

Fields

Declared field:

field fully.qualified.class.name.of.declaring.class.fieldName

Inherited field:

field fully.qualified.class.name.of.declaring.class.fieldName @ class.that.inherits.the.Field

As with methods, the distinction between a declared and inherited field is quite important.

Match Parameters

In addition to a human-readable representation of the element outlined above, the differences can also be matched by their additional parameters. Each of the below differences lists the parameters that can be matched against.

For example the java.class.noLongerImplementsInterface difference defines the match parameter interface. This can be used when matching the difference for example in the revapi.ignore extension:

{
  "regex": true,
  "code": "java\\.class\\.noLongerImplementsInterface",
  "old": "class my\\..*",
  "interface": "my\\.Interface"
}

The above will instruct Revapi to not report the removal of my.Interface from the list of implemented interfaces on any class in the my package (or any other subpackage).

All types of differences report the elementKind parameter that can have 1 of the following values:

class

The element is an ordinary class

interface

The element is an interface

enum

The element is an enum class

@interface

The element is an annotation class

constructor

The element is a constructor

enumConstant

The element is an enum constant

field

The element is a field

method

The method is a method

parameter

The element is a method parameter

annotation

The element is an annotation of another element

Additionally, if the analyzer is configured to report example use chains for given difference (see the java extension configuration), there will be 1 or 2 more parameters with the example use chains present: exampleUseChainInOldApi and/or exampleUseChainInNewApi.

Detected Differences

All the differences detected by the extension are defined in the Code enumeration. Below, you can find a short discussion of each type of the difference.

Missing API Class

Code

java.missing.oldClass or java.missing.newClass

Binary severity

potentially breaking

Source severity

potentially breaking

Semantic severity

NA

By default, Revapi will report any type that should belong to the API but cannot be found neither in the API libraries themselves or in the supporting libraries. It can also be configured to ignore such missing classes or to abort the API check altogether. If it is configured to report them (which is the default), one of the above codes will be emitted depending on whether the missing class is found in the old version of the API or the new one.

The missing class behavior can be configured by the revapi.java.missing-classes configuration property with the possible values of ignore, report and error, e.g.:

{
    "revapi" : {
        "java" : {
            "missing-classes" : "ignore"
        }
    }
}
Match parameters

package

the package of the class

classSimpleName

the simple name of the class

elementKind

See Match Parameters for possible values

Element No Longer Deprecated

Code

java.element.noLongerDeprecated

Binary severity

equivalent

Source severity

equivalent

Semantic severity

NA

An element (class, method or field) is marked as deprecated in the old version of the API but not in the new version. This represents no danger in terms of API breakage and is reported only because it is useful to know for the library users to know about such cases.

Match parameters

annotationType

exactly @java.lang.Deprecated.

elementKind

See Match Parameters for possible values

Element Now Deprecated

Code

java.element.nowDeprecated

Binary severity

equivalent

Source severity

equivalent

Semantic severity

NA

An element (class, method or field) is marked as deprecated in the new version of the API but not in the old version. This represents no danger in terms of API breakage and is reported only because it is useful to know for the library users to know about such cases.

Match parameters

annotationType

exactly @java.lang.Deprecated.

elementKind

See Match Parameters for possible values

Class Visibility Increased

Code

java.class.visibilityIncreased

Binary severity

equivalent

Source severity

equivalent

Semantic severity

NA

The class is more visible in the new version of the API than it used to be in the old version. This is no API breakage and is reported for completeness sake. The visibility is ordered as follows: private < package private < protected < public.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

oldVisibility

The visibility of the type as it was in the old version.

newVisibility

The visibility of the type in the new version.

elementKind

See Match Parameters for possible values

Class Visibility Reduced

Code

java.class.visibilityReduced

Binary severity

breaking

Source severity

breaking

Semantic severity

NA

Reducing the visibility of an API class is a breaking change. It means that classes that could inherit or use the class might no longer be able to. Thus a library user might face compilation errors at compile time or linkage errors at runtime when trying to use the new version of the library.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

oldVisibility

The visibility of the type as it was in the old version.

newVisibility

The visibility of the type in the new version.

elementKind

See Match Parameters for possible values

Class Kind Changed

Code

java.class.kindChanged

Binary severity

breaking

Source severity

breaking

Semantic severity

NA

There are 4 kinds of java classes: class, interface, annotation type, enum. This difference is reported when a class changes from one to the other. This is of course incompatible change and will break the library users at both compile time and at runtime.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

oldKind

The kind of the type as it was in the old version.

newKind

The kind of the type in the new version.

elementKind

See Match Parameters for possible values

Class No Longer Final

Code

java.class.noLongerFinal

Binary severity

equivalent

Source severity

equivalent

Semantic severity

NA

A class that used to be final is now not. This is no API breakage and is reported for completeness sake.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

oldModifiers

The sorted modifiers on the class in the old version.

newModifiers

The sorted modifiers on the class in the new version.

elementKind

See Match Parameters for possible values

Note
Modifiers are sorted according to the Java style guidelines in the following order: public protected private abstract static final transient volatile default synchronized native strictfp.

Class Now Final

Code

java.class.nowFinal

Binary severity

breaking

Source severity

breaking

Semantic severity

NA

A class became final in the new version of the library. This is a breaking change because any library user that extended the class will no longer be compatible with the new version of the library, in which the class cannot be extended.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

oldModifiers

The sorted modifiers on the class in the old version.

newModifiers

The sorted modifiers on the class in the new version.

elementKind

See Match Parameters for possible values

Note
See Class No Longer Final for the ordering of the modifiers.

Class No Longer Abstract

Code

java.class.noLongerAbstract

Binary severity

equivalent

Source severity

equivalent

Semantic severity

NA

A class that used to be abstract is now not. This is no API breakage and is reported for completeness sake.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

oldModifiers

The sorted modifiers on the class in the old version.

newModifiers

The sorted modifiers on the class in the new version.

elementKind

See Match Parameters for possible values

Note
See Class No Longer Final for the ordering of the modifiers.

Class Now Abstract

Code

java.class.nowAbstract

Binary severity

breaking

Source severity

breaking

Semantic severity

NA

A concrete class became abstract in the new version of the library. This is a breaking change because it is no longer possible to create instances of such class.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

oldModifiers

The sorted modifiers on the class in the old version.

newModifiers

The sorted modifiers on the class in the new version.

elementKind

See Match Parameters for possible values

Note
See Class No Longer Final for the ordering of the modifiers.

Class Added

Code

java.class.added

Binary severity

non breaking

Source severity

non breaking

Semantic severity

NA

A new class appeared in the new version of the API. This is a non-breaking change reported for completeness sake.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

elementKind

See Match Parameters for possible values

Class Removed

Code

java.class.removed

Binary severity

breaking

Source severity

breaking

Semantic severity

NA

A class present in the old version of the library is no longer present. This is of course a breaking change because the users of the API will no longer be able to use that class in any capacity.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

elementKind

See Match Parameters for possible values

External Class Exposed In API

Code

java.class.externalClassExposedInAPI

Binary severity

non-breaking

Source severity

non-breaking

Semantic severity

potentially breaking

This is reported for classes from dependencies that are exposed in the API (for example as a return value). This is generally discouraged practice because it makes updating the dependency version a more complex task (you want a bugfix but you don’t want the changed API to leak to your users).

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

elementKind

See Match Parameters for possible values

External Class No Longer Exposed In API

Code

java.class.externalClassNoLongerExposedInAPI

Binary severity

non-breaking

Source severity

non-breaking

Semantic severity

NA

An opposite of External Class Exposed In API. This is non-breaking, because the class is still accessible on the classpath so users that used to rely on it can still access it. The class is just no longer exposed in the API (which will usually cause other differences to be reported, too).

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

elementKind

See Match Parameters for possible values

Class No Longer Implements Interface

Code

java.class.noLongerImplementsInterface

Binary severity

breaking

Source severity

breaking

Semantic severity

NA

This is a breaking change because it is no longer possible to cast the class to the no longer implemented interface.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

interface

The fully qualified name of the interface that is no longer implemented.

elementKind

See Match Parameters for possible values

Class Now Implements Interface

Code

java.class.nowImplementsInterface

Binary severity

non breaking

Source severity

non breaking

Semantic severity

NA

No API breakage reported for the completeness sake.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

interface

The fully qualified name of the interface that is now implemented.

elementKind

See Match Parameters for possible values

Final Class Inherits From New Class

Code

java.class.finalClassInheritsFromNewClass

Binary severity

equivalent

Source severity

equivalent

Semantic severity

NA

A final class inherits from a new class. This represents no API breakage and is reported for completeness sake. Inheriting from a new class may introduce new methods or fields to the class but cannot remove any (method changes are reported separately).

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

superClass

The fully qualified name of the new super class.

elementKind

See Match Parameters for possible values

Non-final Class Inherits From New Class

Code

java.class.nonFinalClassInheritsFromNewClass

Binary severity

potentially breaking

Source severity

potentially breaking

Semantic severity

NA

While this change is usually OK, it might cause trouble to the users of the API if the newly inherited class contains final methods. If the users of the library happen to define methods of the same name in the class that inherits from the checked one, they will get compilation or linkage errors.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

superClass

The fully qualified name of the new super class.

elementKind

See Match Parameters for possible values

Class Now Checked Exception

Code

java.class.nowCheckedException

Binary severity

non breaking

Source severity

breaking

Semantic severity

NA

A class newly inherits from java.lang.Exception. This is a source incompatibility because such exceptions need to be declared in the throws declarations of the methods.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

elementKind

See Match Parameters for possible values

Class No Longer Inherits From Class

Code

java.class.noLongerInheritsFromClass

Binary severity

breaking

Source severity

breaking

Semantic severity

NA

The checked class no longer inherits from a super class that it used to. This means that it can no longer be cast to that super class nor can the methods declared in the super class be called using the instance of the checked class.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

superClass

The fully qualified name of the superclass that is no longer inherited.

elementKind

See Match Parameters for possible values

Class Is Non-Public Part of API

Code

java.class.nonPublicPartOfAPI

Binary severity

non breaking

Source severity

non breaking

Semantic severity

breaking

While this is non-breaking from the pure API compatibility point of view, it is a very strange design decision. This means that a class that is not publicly accessible (i.e. is private or package private) is used in a public capacity (i.e. return type of a method, type of a method parameter, type of an accessible field, implemented interface).

By default, Revapi even outputs the "usage chain" from some public API element to the non-public class.

The check is done only on a new version of the API. It serves little purpose to report this on the old which has already been released, while the new one might still not be out in the wild. It does make sense though to accept that some parts of the API use non-public classes, because such API has already existed. Therefore, you can configure Revapi to only report new violations but let the old ones be. The default behavior is to always report non-public class in the API even if it existed in the old version (the reportUnchanged attribute below defaults to true) but you can override it as such:

<revapi.java>
  <checks>
    <nonPublicPartOfAPI>
      <reportUnchanged>false</reportUnchanged>
    </nonPublicPartOfAPI>
  </checks>
</revapi.java>

or

{
  "extension": "revapi.java",
  "configuration": {
    "checks": {
      "nonPublicPartOfAPI": {
        "reportUnchanged": false
      }
    }
  }
}
Match parameters

package

the package of the class

classSimpleName

the simple name of the class

elementKind

See Match Parameters for possible values

Note
This is NOT reported on a non-accessible class that is used solely as a super class of another API classes or that is only implemented by other API classes. An implementation of a private interface or inheriting from a non-public super class is a valid design decision.

Type Parameters of The Super Type Changed

Code

java.class.superTypeTypeParametersChanged

Binary severity

potentially breaking

Source severity

potentially breaking

Semantic severity

NA

The checked class inherits from a generic class. The type parameters used on the generic super class changed between old and new version. Because of type erasure, this might not cause any binary incompatibility (but it can) and it can potentially break the compilation, too.

This is generally a quite dangerous thing to do, because it can change the erased signatures of the methods or fields inherited from the super class (which would be the cause of the binary and source incompatibilities).

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

oldSuperType

The old signature of the super type.

newSuperType

The new signature of the super type.

elementKind

See Match Parameters for possible values

Annotation Added

Code

java.annotation.added

Binary severity

equivalent

Source severity

equivalent

Semantic severity

potentially breaking

An element is newly annotated by given annotation. This poses no risk during compilation or at linkage time but may cause semantic differences between the versions because of the way the annotations can be used (code generation, processing, reflection, etc.).

Match parameters

annotationType

The fully qualified name of the annotation type, preceded by @ (e.g. @java.lang.annotation.Target).

elementKind

See Match Parameters for possible values

Annotation Removed

Code

java.annotation.removed

Binary severity

equivalent

Source severity

equivalent

Semantic severity

potentially breaking

An element is no longer annotated by given annotation. This poses no risk during compilation or at linkage time but may cause semantic differences between the versions because of the way the annotations can be used (code generation, processing, reflection, etc.).

Match parameters

annotationType

The fully qualified name of the annotation type, preceded by @ (e.g. @java.lang.annotation.Target).

elementKind

See Match Parameters for possible values

Annotation Attribute Value Changed

Code

java.annotation.attributeValueChanged

Binary severity

equivalent

Source severity

equivalent

Semantic severity

potentially breaking

An attribute of some annotation on some element changed its value. This poses no risk during compilation or at linkage time but may cause semantic differences between the versions because of the way the annotations can be used (code generation, processing, reflection, etc.).

Match parameters

attribute

The name of the attribute that changed value

annotationType

The fully qualified name of the annotation type, preceded by @ (e.g. @java.lang.annotation.Target).

oldValue

The old value of the attribute.

newValue

The new value of the attribute.

elementKind

See Match Parameters for possible values

Annotation Attribute Added

Code

java.annotation.attributeAdded

Binary severity

equivalent

Source severity

equivalent

Semantic severity

potentially breaking

An annotation on some element newly specifies an explicit value of an attribute. This poses no risk during compilation or at linkage time but may cause semantic differences between the versions because of the way the annotations can be used (code generation, processing, reflection, etc.).

Match parameters

attribute

The name of the attribute that was added

annotationType

The fully qualified name of the annotation type, preceded by @ (e.g. @java.lang.annotation.Target).

value

The value of the attribute.

elementKind

See Match Parameters for possible values

Annotation Attribute Removed

Code

java.annotation.attributeRemoved

Binary severity

equivalent

Source severity

equivalent

Semantic severity

potentially breaking

An annotation on some element no longer specifies an explicit value of an attribute. This poses no risk during compilation or at linkage time but may cause semantic differences between the versions because of the way the annotations can be used (code generation, processing, reflection, etc.).

Match parameters

attribute

The name of the attribute that was removed,

value

The value of the attribute that was removed,

annotationType

The fully qualified name of the annotation type, preceded by @ (e.g. @java.lang.annotation.Target).

elementKind

See Match Parameters for possible values

Annotation No Longer Inherited

Code

java.annotation.noLongerInherited

Binary severity

non breaking

Source severity

non breaking

Semantic severity

potentially breaking

An annotation type used to be annotated with the @Inherited annotation but is no more. This poses no risk during compilation or at linkage time but may cause semantic differences between the versions because of the way the annotations can be used (code generation, processing, reflection, etc.).

Match parameters

annotationType

exactly @java.lang.annotation.Inherited.

elementKind

See Match Parameters for possible values

Annotation Now Inherited

Code

java.annotation.nowInherited

Binary severity

non breaking

Source severity

non breaking

Semantic severity

potentially breaking

An annotation type is now annotated with the @Inherited annotation. This poses no risk during compilation or at linkage time but may cause semantic differences between the versions because of the way the annotations can be used (code generation, processing, reflection, etc.).

Match parameters

annotationType

exactly @java.lang.annotation.Inherited.

elementKind

See Match Parameters for possible values

Static Field Added

Code

java.field.addedStaticField

Binary severity

non breaking

Source severity

non breaking

Semantic severity

NA

No API breakage, provided for completeness sake. Note that this si reported only for publicly accessible fields.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

fieldName

the name of the field

elementKind

See Match Parameters for possible values

Field Added

Code

java.field.added

Binary severity

non breaking

Source severity

non breaking

Semantic severity

NA

No API breakage, provided for completeness sake. Note that this si reported only for publicly accessible fields.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

fieldName

the name of the field

elementKind

See Match Parameters for possible values

Field Removed

Code

java.field.removed

Binary severity

breaking

Source severity

breaking

Semantic severity

NA

The field was removed from the class. This is an API breakage because the field can no longer be accessed. Note that this si reported only for publicly accessible fields.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

fieldName

the name of the field

elementKind

See Match Parameters for possible values

Constant Field Removed

Code

java.field.removedWithConstant

Binary severity

non-breaking

Source severity

breaking

Semantic severity

potentially breaking

The field with a constant value was removed from the class. This is source incompatible because the field can no longer be accessed. It is binary compatible though because the field is actually never used because all its uses are inlined. Note that this si reported only for publicly accessible fields.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

fieldName

the name of the field

elementKind

See Match Parameters for possible values

Field Moved To Superclass

Code

java.field.movedToSuperclass

Binary severity

equivalent

Source severity

equivalent

Semantic severity

NA

The field was moved to a super class. From the point of view of the field user this represents no noticeable change.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

fieldName

the name of the field

oldClass

The class the field used to be declared in.

newClass

The class the field is now declared in.

elementKind

See Match Parameters for possible values

Inherited Field Now Declared On Class

Code

java.field.inheritedNowDeclared

Binary severity

equivalent

Source severity

equivalent

Semantic severity

NA

A field that was previously inherited is now declared on the class. From the point of view of the field user this represents no noticeable change. If the field was also removed from the super class, it will be reported separately.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

fieldName

the name of the field

oldClass

The class the field used to be declared in.

newClass

The class the field is now declared in.

elementKind

See Match Parameters for possible values

Constant Field Removed

Code

java.field.removed

Binary severity

non breaking

Source severity

breaking

Semantic severity

potentially breaking

An accessible static final field (i.e. a constant) was removed from the class. This breaks compilation but actually causes no problem at runtime (i.e. when the new API is swapped for the old API without recompiling the users of the API). This is because the constants are inlined during compilation. Because the value is no longer declared or used in the API but the user of the API still can operate with the value, this is also reported as potentially breaking the semantics.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

fieldName

the name of the field

elementKind

See Match Parameters for possible values

Constant Field Changed Value

Code

java.field.constantValueChanged

Binary severity

non breaking

Source severity

non breaking

Semantic severity

breaking

A constant field changed its value. At compilation time, the new value is used, but at runtime (i.e. when the new API is swapped for the old API without recompiling the users of the API) the users of the API will still use the old value, because the constant values are inlined. This is therefore reported as breaking the semantics.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

fieldName

the name of the field

oldValue

The old value of the constant field.

newValue

The new value of the constant field.

elementKind

See Match Parameters for possible values

Field Now Constant

Code

java.field.nowConstant

Binary severity

equivalent

Source severity

equivalent

Semantic severity

potentially breaking

This is no API breakage but has consequences for the user code. As a constant, the field will now be inlined in the user code, which is something that didn’t happen before. You may want to re-evaluate that decision.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

fieldName

the name of the field

value

The constant value of the field.

elementKind

See Match Parameters for possible values

Field No Longer Constant

Code

java.field.noLongerConstant

Binary severity

equivalent

Source severity

equivalent

Semantic severity

breaking

When compiling an API user against the new version of the API, the value of the field is taken. When swapping the new version of the API for the old version of the API without recompiling the old value coming from the inlined constant value from the old version of the API is used. I.e. the code works and therefore this is neither a source nor binary incompatibility, but it is marked as a semantic incompatibility, because the behavior described above is most probably NOT what the API author had in mind when making the change.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

fieldName

the name of the field

value

The constant value the field used to have.

elementKind

See Match Parameters for possible values

Field Now Final

Code

java.field.nowFinal

Binary severity

potentially breaking

Source severity

potentially breaking

Semantic severity

NA

A field that could previously be assigned to is now final and cannot be changed. This is therefore both source and binary incompatibility.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

fieldName

the name of the field

elementKind

See Match Parameters for possible values

Field No Longer Final

Code

java.field.noLongerFinal

Binary severity

non breaking

Source severity

non breaking

Semantic severity

NA

This is no API breakage and is reported for completeness' sake.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

fieldName

the name of the field

elementKind

See Match Parameters for possible values

Field No Longer Static

Code

java.field.noLongerStatic

Binary severity

breaking

Source severity

breaking

Semantic severity

NA

A static field has become an instance field. Accessing the field is no longer possible through its class and therefore this is both source and binary incompatibility.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

fieldName

the name of the field

elementKind

See Match Parameters for possible values

Field Now Static

Code

java.field.nowStatic

Binary severity

breaking

Source severity

non breaking

Semantic severity

NA

According to the Java specification, the Java runtime will throw IncompatibleClassChangeError when an instance field has become static and the new version of API is used against the user code compiled against the old version of API. When recompiling the user code against the new version, everything works fine.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

fieldName

the name of the field

elementKind

See Match Parameters for possible values

Field Type Changed

Code

java.field.typeChanged

Binary severity

breaking

Source severity

breaking

Semantic severity

NA

The field has a different type than it used to in the old version of the API. This is incompatible change.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

fieldName

the name of the field

oldType

The fully qualified name of the old field type.

newType

The fully qualified name of the new field type.

elementKind

See Match Parameters for possible values

Field serialVersionUID Unchanged

Code

java.field.serialVersionUIDUnchanged

Binary severity

equivalent

Source severity

equivalent

Semantic severity

potentially breaking

This is reported on the serialVersionUID fields of classes that didn’t change between the versions even though the default UIDs would be different for the two versions of the the class. While this doesn’t break the compilation nor does it break binary compatibility, it possibly may cause semantic problems because serialization may misbehave. This depends on if and how the readObject and writeObject methods on the class are implemented, which is beyond the scope of this check.

The behavior of this check can be configured using the revapi.java.checks.serialVersionUID.changeDetection configuration attribute. The default value of this attribute is structural meaning that the difference is reported only for classes that somehow change structurally between versions (i.e. a field is added/deleted or its type changed). When specifying jvm as the change detection algorithm the default serialVersionUID is computed for both old and new version of the class. This is more "sensitive" than the structural change because it also considers any changes to methods or static fields of the class (but it nevertheless is the algorithm that the JVM itself uses for computing the default serialization UID of a class).

Example of the configuration in both XML and JSON:

<revapi.java>
  <checks>
    <serialVersionUID>
      <changeDetection>structural</changeDetection>
    </serialVersionUID>
  </checks>
</revapi.java>

or

{
  "extension": "revapi.java",
  "configuration": {
    "checks": {
      "serialVersionUID": {
        "changeDetection": "jvm"
      }
    }
  }
}
Match parameters

package

the package of the class

classSimpleName

the simple name of the class

fieldName

the name of the field

elementKind

See Match Parameters for possible values

Field Visibility Increased

Code

java.field.visibilityIncreased

Binary severity

equivalent

Source severity

equivalent

Semantic severity

NA

No API breakage, reported for completeness' sake.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

fieldName

the name of the field

oldVisibility

The visibility of the type as it was in the old version.

newVisibility

The visibility of the type in the new version.

elementKind

See Match Parameters for possible values

Field Visibility Reduced

Code

java.field.visibilityReduced

Binary severity

breaking

Source severity

breaking

Semantic severity

NA

Field’s visibility was reduced, which means that code that used to be able to access it might no longer be able to.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

fieldName

the name of the field

oldVisibility

The visibility of the type as it was in the old version.

newVisibility

The visibility of the type in the new version.

elementKind

See Match Parameters for possible values

Enum Constant Order Changed

Code

java.field.enumConstantOrderChanged

Binary severity

non breaking

Source severity

non breaking

Semantic severity

potentially breaking

The constants of an enumeration were re-ordered. This can lead to problems in user code that uses the Enum.ordinal() method to determine the order of an enum constant and relies on a specific value.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

fieldName

the name of the field

oldOrdinal

The old ordinal number of the enum value.

newOrdinal

The new ordinal number of the enum value.

elementKind

See Match Parameters for possible values

Default Value Added To Method

Code

java.method.defaultValueAdded

Binary severity

non breaking

Source severity

non breaking

Semantic severity

NA

This is only relevant on annotation types, of which the attributes are represented by method declarations. Declaring a default value to an annotation attribute is not an API breakage and is only reported for completeness' sake.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

methodName

the name of the method added

value

the default value of the annotation attribute represented by the method

elementKind

See Match Parameters for possible values

Method’s Default Value Changed

Code

java.method.defaultValueChanged

Binary severity

non breaking

Source severity

non breaking

Semantic severity

potentially breaking

This is only relevant on annotation types, of which the attributes are represented by method declarations. Changing a default value is both source and binary compatible but might cause a semantic incompatibility (depending on how the annotation is used). Elements annotated using this annotation that didn’t provide an explicit value for this attribute will suddenly be understood to have the new default value of the attribute when used with the new version of the API. This might or might not be a problem.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

methodName

the name of the method added

oldValue

the old default value of the annotation attribute represented by the method

newValue

the new default value of the annotation attribute represented by the method

elementKind

See Match Parameters for possible values

Default Value Removed From Method

Code

java.method.defaultValueRemoved

Binary severity

non breaking

Source severity

breaking

Semantic severity

breaking

An annotation attribute no longer declares a default value. This is source incompatible change because elements annotated without explicitly specifying the value for the attribute will no longer compile. This also breaks semantics because annotation processor that relies on the new version of the annotation type will break with a user library that was compiled against the old version of the API (and therefore didn’t have to declare the default value of the attribute).

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

methodName

the name of the method added

value

the removed default value of the annotation attribute represented by the method

elementKind

See Match Parameters for possible values

Method Added To Interface

Code

java.method.addedToInterface

Binary severity

non breaking

Source severity

breaking

Semantic severity

potentially breaking

This is a source-incompatible change because all implementations that were written against the old version of the interface will not have the implementation of the new method and therefore will not compile.

On contrary, this is binary compatible, because no code that used the old version of the interface could have called the method through the interface. The linker doesn’t check for missing method implementations so the linkage also goes without a problem.

There might be semantic problems though. It might break in situations where the interface serves the purpose of an SPI - a library declares an SPI interface to be implemented by users and then uses these SPI implementations inside the library. If the new version of the library assumes that the SPI implementations provide the impl of the new method and it is provided with the SPI implementation of the old version of the interface, things will break with NoSuchMethodError when the caller tries to call the SPI method backed by the old SPI implementation.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

methodName

the name of the method added

elementKind

See Match Parameters for possible values

Default Method Added To Interface

Code

java.method.defaultMethodAddedToInterface

Binary severity

non breaking

Source severity

non breaking

Semantic severity

NA

This represents no API breakage and is included for completeness' sake.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

methodName

the name of the method added

elementKind

See Match Parameters for possible values

Static Method Added To Interface

Code

java.method.staticMethodAddedToInterface

Binary severity

non breaking

Source severity

non breaking

Semantic severity

NA

This represents no API breakage and is included for completeness' sake.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

methodName

the name of the method added

elementKind

See Match Parameters for possible values

Method With No Default Value Added To Annotation Type

Code

java.method.attributeWithNoDefaultAddedToAnnotationType

Binary severity

non breaking

Source severity

breaking

Semantic severity

breaking

While technically a variant of Method Added To Interface, this is similar in consequences to Default Value Removed From Method. This is not binary incompatible, there can be no code compiled against the previous version of the API that would try to access or use the new attribute in any way. This is source incompatible though, because any code that declares annotations according to the old version of the API will fail to compile against the new version of the API because it will not define explicit value for the new attribute. This also breaks semantics because any element annotated without such attribute won’t be possible to process using a processor that depends on the new version of the API and therefore assumes an explicit value for the annotation attribute.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

methodName

the name of the method added

elementKind

See Match Parameters for possible values

Method With Default Value Added To Annotation Type

Code

java.method.attributeWithDefaultAddedToAnnotationType

Binary severity

non breaking

Source severity

non breaking

Semantic severity

NA

This does not break compatibility and is reported for completeness' sake.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

methodName

the name of the method added

elementKind

See Match Parameters for possible values

Abstract Method Added

Code

java.method.abstractMethodAdded

Binary severity

breaking

Source severity

breaking

Semantic severity

NA

Abstract method added to a class. All the code compiled against the old version of the API will not provide a concrete implementation of it and will therefore break.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

methodName

the name of the method added

elementKind

See Match Parameters for possible values

Method Added

Code

java.method.added

Binary severity

non breaking

Source severity

non breaking

Semantic severity

NA

A new concrete method added to a concrete class. This is always safe.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

methodName

the name of the method added

elementKind

See Match Parameters for possible values

Final Method Added To Non-final Class

Code

java.method.finalMethodAddedToNonFinalClass

Binary severity

potentially breaking

Source severity

potentially breaking

Semantic severity

NA

This will break user code if the a subclass of the checked class declared a method that happens to have a same signature as the newly introduced final method.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

methodName

the name of the method added

elementKind

See Match Parameters for possible values

Inherited Method Moved To Class

Code

java.method.inheritedMovedToClass

Binary severity

equivalent

Source severity

equivalent

Semantic severity

NA

A method that was inherited in the old version is now declared in the class (or interface). This is a compatible change. Note that if the super class is part of the API, the removal of the method from that class will be reported separately.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

methodName

the name of the method added

oldClass

the class that the method was originally declared in

newClass

the class that now declares the method

elementKind

See Match Parameters for possible values

Method Removed

Code

java.method.removed

Binary severity

breaking

Source severity

breaking

Semantic severity

NA

Removing a method from a class is an incompatible change.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

methodName

the name of the method added

elementKind

See Match Parameters for possible values

Method Moved To Superclass

Code

java.method.movedToSuperClass

Binary severity

equivalent

Source severity

equivalent

Semantic severity

NA

A method that was declared in the class in the old version is now declared in one of its super types. If such move should represent a compatibility breakage it is reported differently, like for example Method Replaced By Abstract In Superclass. Otherwise this is a compatible change and is reported for completeness' sake.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

methodName

the name of the method added

oldClass

the class that the method was originally declared in

newClass

the class that now declares the method

elementKind

See Match Parameters for possible values

Attribute Removed From Annotation Type

Code

java.method.attributeRemovedFromAnnotationType

Binary severity

breaking

Source severity

breaking

Semantic severity

NA

This is identical to Method Removed but specialized for annotation types.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

methodName

the name of the method added

elementKind

See Match Parameters for possible values

Method No Longer Final

Code

java.method.noLongerFinal

Binary severity

non breaking

Source severity

non breaking

Semantic severity

NA

No API breakage, reported for completeness' sake.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

methodName

the name of the method added

oldModifiers

the modifiers on the method in the old version

newModifiers

the modifiers on the method in the new version

elementKind

See Match Parameters for possible values

Method Now Final

Code

java.method.nowFinal

Binary severity

breaking

Source severity

breaking

Semantic severity

NA

Any subclasses that overrode the method will break both at compile time and at runtime.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

methodName

the name of the method added

oldModifiers

the modifiers on the method in the old version

newModifiers

the modifiers on the method in the new version

elementKind

See Match Parameters for possible values

Method Now Final In Final Class

Code

java.method.nowFinalInFinalClass

Binary severity

equivalent

Source severity

equivalent

Semantic severity

NA

The class is final and cannot be subclassed. Therefore making its method final makes no difference.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

methodName

the name of the method added

oldModifiers

the modifiers on the method in the old version

newModifiers

the modifiers on the method in the new version

elementKind

See Match Parameters for possible values

Method No Longer Static

Code

java.method.noLongerStatic

Binary severity

breaking

Source severity

breaking

Semantic severity

NA

When a method becomes a member method, it no longer can be called from the static context. This breaks both binary and source compatibility.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

methodName

the name of the method added

oldModifiers

the modifiers on the method in the old version

newModifiers

the modifiers on the method in the new version

elementKind

See Match Parameters for possible values

Method Now Static

Code

java.method.nowStatic

Binary severity

breaking

Source severity

non breaking

Semantic severity

NA

A static method can be called in the same way as member method, so on the source level, this change is compatible. It is not binary compatible though because static methods are called using a different bytecode instruction.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

methodName

the name of the method added

oldModifiers

the modifiers on the method in the old version

newModifiers

the modifiers on the method in the new version

elementKind

See Match Parameters for possible values

Method Now Abstract

Code

java.method.nowAbstract

Binary severity

breaking

Source severity

breaking

Semantic severity

NA

If a method becomes abstract, all the inheriting classes will have to implement it even though they didn’t have to before.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

methodName

the name of the method added

oldModifiers

the modifiers on the method in the old version

newModifiers

the modifiers on the method in the new version

elementKind

See Match Parameters for possible values

Method No Longer Abstract

Code

java.method.noLongerAbstract

Binary severity

non breaking

Source severity

non breaking

Semantic severity

NA

This is a compatible change reported for the completeness' sake.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

methodName

the name of the method added

oldModifiers

the modifiers on the method in the old version

newModifiers

the modifiers on the method in the new version

elementKind

See Match Parameters for possible values

Method Visibility Increased

Code

java.method.visibilityIncreased

Binary severity

equivalent

Source severity

equivalent

Semantic severity

NA

No API breakage, reported for completeness' sake.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

methodName

the name of the method added

oldVisibility

The visibility of the type as it was in the old version.

newVisibility

The visibility of the type in the new version.

elementKind

See Match Parameters for possible values

Method Visibility Reduced

Code

java.method.visibilityIncreased

Binary severity

breaking

Source severity

breaking

Semantic severity

NA

A method might no longer be visible to code that used to call it. This is a breaking change.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

methodName

the name of the method added

oldVisibility

The visibility of the type as it was in the old version.

newVisibility

The visibility of the type in the new version.

elementKind

See Match Parameters for possible values

Method Return Type Changed

Code

java.method.returnTypeChanged

Binary severity

breaking

Source severity

potentially breaking

Semantic severity

NA

While changing the return type always breaks at runtime (i.e. when swapping the new API for the old API without recompiling the user code), it might be OK at compile time due to implicit conversions of primitive types.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

methodName

the name of the method added

oldType

the old return type

newType

the new return type

elementKind

See Match Parameters for possible values

Method Return Type Changed Covariantly

Code

java.method.returnTypeChangedCovariantly

Binary severity

breaking

Source severity

non breaking

Semantic severity

NA

Covariant return types represent no problem on source level because the new return type can always be cast to the old return type and therefore the users of the old version of the API can work with the new type. This a binary incompatibility though because the method signature changes and users of the old version of the API would get nasty NoSuchMethodError errors at link time.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

methodName

the name of the method added

oldType

the old return type

newType

the new return type

elementKind

See Match Parameters for possible values

Type Parameters of The Return Type Changed

Code

java.method.returnTypeTypeParametersChanged

Binary severity

non breaking

Source severity

breaking

Semantic severity

NA

If the return type of the method is a generic type and its type parameters change between old and new version of the API it is a source incompatible change. It is binary compatible because of type erasure.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

methodName

the name of the method added

oldType

the old return type

newType

the new return type

elementKind

See Match Parameters for possible values

Number of Method Parameters Changed

Code

java.method.numberOfParametersChanged

Binary severity

breaking

Source severity

breaking

Semantic severity

NA

Obviously, this is a breaking change - you can no longer call the method with the same parameters.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

methodName

the name of the method added

elementKind

See Match Parameters for possible values

Method Parameter Type Changed

Code

java.method.parameterTypeChanged

Binary severity

breaking

Source severity

potentially breaking

Semantic severity

NA

This is a binary incompatibility but may be source compatible if the changed types are primitive and the new one is strictly bigger than the old one and the old one is implicitly convertible to it.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

methodName

the name of the method added

parameterIndex

the index of the method parameter

oldType

the old type of the parameter

newType

the new type of the parameter

elementKind

See Match Parameters for possible values

Method Parameter Type Parameter Changed

Code

java.method.parameterTypeParameterChanged

Binary severity

non breaking

Source severity

potentially breaking

Semantic severity

NA

This problem is reported on method parameters with a generic type if one or more of the type parameters of the generic type changed. This is binary compatible because of the type erasure but it can break source compatibility.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

methodName

the name of the method added

parameterIndex

the index of the method parameter

oldType

the old type of the parameter

newType

the new type of the parameter

elementKind

See Match Parameters for possible values

Method Now Throws a Checked Exception

Code

java.method.exception.checkedAdded

Binary severity

non breaking

Source severity

breaking

Semantic severity

NA

A method now throws a checked exception. This binary compatible but is not source compatible because the code using the new version of the method will have to be modified to handle the checked exception.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

methodName

the name of the method added

exception

the fully qualified name of the added exception type

elementKind

See Match Parameters for possible values

Method Now Throws a Runtime Exception

Code

java.method.exception.runtimeAdded

Binary severity

non breaking

Source severity

non breaking

Semantic severity

potentially breaking

This is both source and binary compatible but represents a danger for the semantics of the user code that might want to catch the newly thrown exception.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

methodName

the name of the method added

exception

the fully qualified name of the added exception type

elementKind

See Match Parameters for possible values

Method No Longer Throws a Checked Exception

Code

java.method.exception.checkedRemoved

Binary severity

non breaking

Source severity

breaking

Semantic severity

NA

A method no longer throws a checked exception. This binary compatible but is not source compatible because the code using the new version of the method fail to compile - the checked exception can no longer be thrown from the method and therefore the catch clauses for it will be invalid.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

methodName

the name of the method added

exception

the fully qualified name of the removed exception type

elementKind

See Match Parameters for possible values

Method No Longer Throws a Runtime Exception

Code

java.method.exception.checkedRemoved

Binary severity

non breaking

Source severity

non breaking

Semantic severity

NA

This is a compatible change added for completeness' sake.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

methodName

the name of the method added

exception

the fully qualified name of the removed exception type

elementKind

See Match Parameters for possible values

Method Now Default

Code

java.method.nowDefault

Binary severity

equivalent

Source severity

equivalent

Semantic severity

NA

The method is now a default method. This is completely transparent.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

methodName

the name of the method added

oldModifiers

the modifiers on the method in the old version

newModifiers

the modifiers on the method in the new version

elementKind

See Match Parameters for possible values

Method No Longer Default

Code

java.method.noLongerDefault

Binary severity

breaking

Source severity

breaking

Semantic severity

NA

The method is no longer default. This means that any implementations of the inteface with the method will now have to supply an implementation for it.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

methodName

the name of the method added

oldModifiers

the modifiers on the method in the old version

newModifiers

the modifiers on the method in the new version

elementKind

See Match Parameters for possible values

Element Now Parameterized

Code

java.generics.elementNowParameterized

Binary severity

non breaking

Source severity

non breaking

Semantic severity

potentially breaking

In and of itself, this is a compatible change but may cause semantic confusion if the user code compiled against the old API wasn’t honoring the new semantics introduced with the generic type parameter (e.g. old code was using raw List and the new version of the API parameterized the list to List<E>. The old code used to insert variety of types into the list but the new version of the API suggests it is not possible. Everything will still work correctly, but new user code might start assuming uniform types in the list).

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

elementKind

See Match Parameters for possible values

Optionally

methodName

the name of the method if it is the newly parameterized element

Formal Type Parameter Added

Code

java.generics.formalTypeParameterAdded

Binary severity

non breaking

Source severity

breaking

Semantic severity

NA

This is not a binary incompatibility due to type erasure but it is a source incompatible change. Classes declared against the old version of the API will no longer compile with the new version because they will be missing the definition of the formal type parameter.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

elementKind

See Match Parameters for possible values

Optionally

methodName

the name of the method if it is the newly parameterized element

Formal Type Parameter Removed

Code

java.generics.formalTypeParameterRemoved

Binary severity

non breaking

Source severity

breaking

Semantic severity

NA

This is not a binary incompatibility due to type erasure but it is a source incompatible change. Classes declared against the old version of the API will no longer compile with the new version because they will be declaring a type parameter that is no longer required.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

elementKind

See Match Parameters for possible values

Optionally

methodName

the name of the method if it is the newly parameterized element

Formal Type Parameter Changed

Code

java.generics.formalTypeParameterChanged

Binary severity

non breaking

Source severity

breaking

Semantic severity

NA

The constraints on the formal type parameter have changed. This is again source incompatible because the user code declared against the old version of the API will use wrong constraints.

Match parameters

package

the package of the class

classSimpleName

the simple name of the class

oldTypeParameter

The old type parameter that changed.

newTypeParameter

The new type parameter.

elementKind

See Match Parameters for possible values

Optionally

methodName

the name of the method if it is the newly parameterized element

Class/Package Filtering

Given Java’s lacking modularity (prior to Java9), authors are often forced to make classes that are essentially an implementation detail of a library public. Such classes are often included in packages like impl or internal to hint at their non-public nature.

While Revapi offers the basic filtering capabilities using the basic filter, it is often simpler to exclude classes or packages using this class / package filters specifically made for this purpose (if for nothing else then for the simpler configuration).

By default, all classes are included. If the include filter is specified, only the classes matching the include filter are included in the API check. The exclude filter can take out classes that would otherwise be included.

It is not possible include class that matches a class exclude filter (i.e. if include and exclude filter match the same class, the class is not included) but it is possible to include an inner classes while its parent class is excluded.

It is not possible include classes in package that matches a package exclude filter (i.e. if include and exclude filter match the same package, the classes are not included).

Sample Configuration

{
  "revapi": {
    "java": {
      "missing-classes": {
        "behavior": "report",
        "ignoreMissingAnnotations": false
      },
      "reportUsesFor": ["java.missing.newClass", "java.class.nonPublicPartOfAPI"],
      "filter": {
        "classes": {
          "regex": true,
          "include": ["com\\.acme\\.api\\..*"]
          "exclude": ["com\\.acme\\.api\\.WeRegretWePlacedThisInApi"]
        },
        "packages": {
          "regex": true,
          "include": ["com\\.acme\\.api\\..*"]
          "exclude": ["com\\.acme\\.internal"]
        }
      }
    }
  }
}

Properties

missing-classes.behavior

This tells the checker what to do when a class is found in the API that is not found in any of the provided jars. The possible values are: error, ignore or report (the default). If report is chosen then the missing class is reported as a problem with either java.missing.oldClass code or java.missing.newClass depending on whether the class was found missing in the old or new version of the API.

missing-classes.ignoreMissingAnnotations

If true (the default is false) the usage chain of annotation classes will not be tracked. The end result of this is that annotation classes will not be considered part of the API and thus missing annotation classes will not be reported.

reportUsesFor

Revapi is able to track usage chains of the classes it finds in the API. For some (or all) problems you may want to know how and where a certain class is used to be able to determine if that’s something wrong or not. This configuration property can either be a string all-differences, signifying that an example use chain will be reported for every found difference or a list of difference codes for which the example use chain should be produced. By default the usage chains are reported for the following problem codes: java.missing.newClass, java.missing.oldClass, java.class.nonPublicPartOfAPI, java.class.externalClassNoLongerExposedInAPI, java.class.externalClassExposedInAPI.

filter.classes.regex

Specifies whether to consider the strings in exclude and include lists as regular expressions or not. The default value is false, meaning the strings are not considered as regular expressions.

filter.classes.exclude

List of strings representing FQCNs or regular expressions matching FQCNs. Classes with the matching fully qualified names (and the members of those classes) will be excluded from the API check.

filter.classes.include

List of strings representing FQCNs or regular expressions matching FQCNs. Only classes with the matching fully qualified names (and their members) will be included in the API check.

filter.packages.regex

Specifies whether to consider the strings in exclude and include lists as regular expressions or not. The default value is false, meaning the strings are not considered as regular expressions.

filter.packages.exclude

List of strings representing full package names or regular expressions matching them. Classes with the matching package names (and the members of those classes) will be excluded from the API check.

filter.packages.include

List of strings representing full package names or regular expressions matching them. Classes with the matching package names (and the members of those classes) will be included in the API check.

Notes

Contrary to intuition, java packages are not hierarchical, even though they have hierarchical names. In Java parlance, package com.acme.foo is a sub-package of com.acme but that does not mean that it’s enclosing element is com.acme. No, each package is a top level element that does not have any parent elements and which only contains classes, not other packages.

Therefore if you want to exclude a package and all its sub-packages, you need to use a regular expression "com\\.acme(\\..+)?" which will match the com.acme package and any of its sub-packages.

Filtering based on annotations

Extension: revapi.java.filter.annotated

The annotated element filter is able to include or exclude java elements based on the presence of certain annotation on them. This also works for annotations with certain attributes having certain values, because the match is done on the "standard" annotation textual notation.

If no include filter is specified, all elements are included, otherwise only the elements annotated with one of the included annotations are API checked.

The exclude filter narrows down the included elements - i.e. if an element is to be included according to the include filter (or lack thereof), the exclude filter is consulted whether that really should be the case.

If an element is included, all its child elements are included, too. I.e. if a class is included, all its methods, fields and inner classes are included in the analysis, too (if you need to exclude some child element that would be included by default, you can either annotate it with some annotation and configure this filter to exclude such elements, or you can use some other Revapi filter, like configurable element filter).

Note
Currently, this filter does NOT take into account annotations placed on the packages.

Sample Configuration

[
  {
    "extension": "revapi.java.filter.annotated",
    "configuration": {
      "regex": true,
      "include": ["@my\\.annotations\\.Public.*"],
      "exclude": ["@my\\.annotations\\.Beta"]
    }
  }
]
<analysisConfiguration>
  <revapi.java.filter.annotated>
    <regex>true</regex>
    <include>
      <item>@my\.annotations\.Public.*</item>
    </include>
    <exclude>
      <item>@my\.annotations\.Beta</item>
    </exclude>
  </revapi.java.filter.annotated>
</analysisConfiguration>

Properties

regex

Specifies whether to consider the strings in exclude and include lists as regular expressions or not. The default value is false, meaning the strings are not considered as regular expressions.

exclude

Elements annotated with annotation that matches at least one from this list will be excluded from the API check.

include

Elements annotated with annotation that matches at least one from this list will be included from the API check.

Notation

The string formatting of the annotations with values follows that of Java8. Notably:

  • The full canonical class names are used for every type.

  • If the annotation specifies only the value of the value attribute, the name, value, must not be included in the notation. I.e. @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME), not @java.lang.annotation.Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME).

  • Array attributes are always enclosed in curly braces even if only a single element is present, i.e. @java.lang .annotation.Target({java.lang.annotation.ElementType.METHOD}), not @java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD).

  • Equals sign is always surrounded by a single space from each side.

Examples

Let’s consider the following classes in our API, that we want to filter:

@Public
public class MyAPI {

   public static final int CONSTANT = 42;

   public void method() {
   }

   public static class InnerAPI {
   }

   @Private
   public static class InnerImplementation {
   }
}

@Private
public class Implementation {

    public static final int DETAIL = 43;

    @Public
    public void method() {
    }
}


public class JustAClass {

    public void method() {
    }

    @Private
    public void implMethod() {
    }

    @Public
    public void definitelyAPIMethod() {
    }
}

Given the classes above, the following example configurations will show different ways of setting up the filtering to achieve anticipated results.

Note
The @Public and @Private annotations are not defined by Revapi. They just represent any annotation you may choose to be used in their place for the same purpose.

Leave out only @Private Elements

This one of the simple approaches. You don’t want to sprinkle your code with annotations everywhere - most of your classes are to be considered public but you want to make sure that certain classes are not meant for public consumption. You neither use Java 9, nor you mandate execution in some of the modular classloading frameworks like OSGi or JBoss Modules, so your options are limited in terms of visibility and you might not have other choice but to make even these classes public.

If you configure Revapi like this:

[
  {
    "extension": "revapi.java.filter.annotated",
    "configuration": {
      "exclude": ["@my.annotations.Private"]
    }
  }
]

The API analysis will not consider these elements:

  • MyAPI.InnerImplementation class

  • Implementation class and any of its members

  • JustAClass.implMethod() method

All other elements will be included in the analysis.

Only Consider @Public Elements

This is an approach where you want to have strict control over what is considered public API and what is not. You do this by annotating the elements to be considered part of the public API using the @Public annotation (of your own making).

The Revapi configuration for this might include this snippet:

[
  {
    "extension": "revapi.java.filter.annotated",
    "configuration": {
      "include": ["@my.annotations.Public"]
    }
  }
]

The API analysis will not consider these elements:

  • Implementation class and all its members but the method() method

  • JustAClass class and all its members but the definitelyAPIMethod() method

The following elements will be analyzed:

  • MyAPI class and all its members, including the InnerImplementation class

  • Implementation.method() method

  • JustAClass.definitelyAPIMethod() method

The Implementation class is not included in the API analysis, because it’s not annotated by the @Public annotation. On the other hand, the MyAPI.InnerImplementation class is included in the API analysis, because it is a member of the the MyAPI class, which is annotated with @Public and there is no configuration for exclusion.

Similarly, JustAClass and its members are not included, because they are not annotated by @Public.

The situation with Implementation.method() and JustAClass.definititelyAPIMethod() is actually quite similar. In both, the presence of the @Public annotation overrides the decision about the parent element’s exclusion (this decision is based on the lack of the @Public annotation on the parents).

Doing this might not seem particularly useful but there are scenarios, where it might be. Imagine that over the evolution of your library certain users became reliant on an implementation class that you never meant to be public. Over the time, you marked your certain methods or the whole class as @Private to really discourage users from using them yet you know of the importance of some method in the class that your clients depend on and don’t want to break the clients using it. You thus annotate it @Public even though it is in a non-public class.

Precise Control Using Both @Public and @Private

This is of course a combination of both the approaches above but still is worth its own explanation.

The configuration would look something like this:

[
  {
    "extension": "revapi.java.filter.annotated",
    "configuration": {
      "include": ["@my.annotations.Public"],
      "exclude": ["@my.annotations.Private"]
    }
  }
]

And the following elements will not be included in the analysis:

  • MyAPI.InnerImplementation class (and all of its members, if there were any)

  • Implementation class and all its members but the method() method

  • JustAClass and all its members but the definitelyAPIMethod() method

This is the most "intuitive" result and probably the one the author of the library anticipated when they annotated the methods and classes with the annotations.

Enhancing Java API checks

In this example it will be shown how to extend the Revapi’s java API checking capabilities. To make it actually useful, this example will show how to automatically ignore addition of any new methods on the EJB interfaces. While a new method on an interface is generally an API breakage, because the implementations that were developed against the old version of the interface would no longer be valid, this change is actually OK on EJB interfaces, because these are not supposed to be implemented by "callers" - the implementations are in control of the library that defines the EJBs.

Project setup

First we need to set up our maven project. We will be extending Revapi’s Java extension that offers an SPI for doing so. In the pom.xml, we will specify that we want to use that SPI:

<project>
    <modelVersion>4.0.0</modelVersion>

    <groupId>my.group</groupId>
    <artifactId>my.extension</artifactId>
    <version>1.0.0</version>

    <dependencies>
        <dependency>
            <groupId>org.revapi</groupId>
            <artifactId>revapi-java-spi</artifactId>
            <version>{version}</version>
        </dependency>
    </dependencies>
</project>

Code

To ignore a found difference, we need to implement a difference transform.

package my.extension;

import java.io.Reader;
import java.util.regex.Pattern;

import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.ExecutableElement;

import org.revapi.AnalysisContext;
import org.revapi.Difference;
import org.revapi.DifferenceTransform;
import org.revapi.java.spi.JavaMethodElement;
import org.revapi.java.spi.Util;

public class IgnoreNewMethodsOnEJBInterfaces implements DifferenceTransform<JavaMethodElement> {
    @Override
    public Pattern[] getDifferenceCodePatterns() {
        return new Pattern[] { Pattern.compile("java\\.method\\.addedToInterface") };
    }

    @Override
    public Difference transform(JavaMethodElement oldElement, JavaMethodElement newElement,
        Difference difference) {

        // we know the element will be a JavaMethodElement. This is because we limit the
        // differences passed into this method.
        ExecutableElement method = newElement.getDeclaringElement();

        // ok, so we got a reference to the method that caused the difference. Now we need to
        // check whether the method was added to an EJB interface - we will just check whether
        // the interface was annotated with the @Local or @Remote annotations.
        for (AnnotationMirror annotation : method.getEnclosingElement().getAnnotationMirrors()) {
            // the Util class in the Java SPI provides a number of useful methods to ease the work
            // with the javax.lang.model objects.
            String annotationTypeName = Util.toHumanReadableString(annotation.getAnnotationType());
            if ("javax.ejb.Local".equals(annotationTypeName) ||
                "javax.ejb.Remote".equals(annotationTypeName)) {

                // ok, so we've found out that the type that declared the new method is indeed
                // an EJB interface. By returning null, we tell Revapi to remove this difference.
                return null;
            }
        }

        // ok, this is not an EJB interface, so we leave the difference alone
        return difference;
    }

    @Override
    public void close() throws Exception {
        // no resources to close...
    }

    @Override
    public String[] getConfigurationRootPaths() {
        // no configuration possible
        return null;
    }

    @Override
    public Reader getJSONSchema(String configurationRootPath) {
        // no configuration possible
        return null;
    }

    @Override
    public void initialize(AnalysisContext analysisContext) {
        // nothing needed here
    }
}

In addition to the code itself, the class needs to be registered as an Revapi extension. For that it needs to be made a java service. Create a file called src/main/resources/META-INF/services/org.revapi.DifferenceTransform and add a line to it with the fully qualified name of the above class, i.e my.extension.IgnoreNewMethodsOnEJBInterfaces.

Usage

Once installed into a maven repository (local or some public), our extension becomes useable by Revapi. Both the Revapi standalone and maven plugin support including new extensions by specifying their maven coordinates, see Getting Started for more details on that.

SPI

The Java extension can be augmented through its small SPI that declares a hook interface into the java checking process that represents the Revapi’s API elements as Java’s own model elements. The checking framework then can use the Java plaform’s own rich functionality for examining the classes in the checked libraries (note, that this API is NOT the reflection API, because it actually doesn’t load the library classes into Java runtime).

Back to top

Msb3 Maven skin by Marek Romanowski.