Kinds of breaking changes¶
This catalog lists every breaking change kind Roseau reports.
Detection rules
Roseau's detection rules aim to be as accurate as possible, avoiding false positives and false negatives. Many edge cases involving complex interactions are not covered on this page. While it describes the most common cases, the full detection rules can be reviewed in the source code, with examples in the corresponding test cases.
Conventions¶
Breaking changes are expressed at the API level, not the code level. Roseau works with the API surface it extracted, not the raw source code or bytecode is analyzes. For example, a public type that becomes package-private is treated the same as a type that is removed from the codebase: it is reported as TYPE_REMOVED. From Roseau's perspective the type isn't API anymore. Likewise, changes to declarations that are not part of the API surface are ignored and not checked for breaking changes. This includes, for instance, protected members of effectively-final types.
In the following, a breaking change on an executable applies to both methods and constructors.
Each entry shows the smallest library change that triggers the kind, and a minimal example of client code affected by it. One library edit can trigger multiple kinds, so a real diff may report several related findings for the same change.
Compatibility icons used throughout this page:
Binary — breaks already compiled clients (.class files linked against the old API)
Source — breaks clients on recompilation (source code that compiled against the old API)
An absent icon means the change does not affect compatibility in that category.
Jump to: Type-Related · Class-Related · Annotation-Related · Field-Related · Executable-Related · Method-Related · Formal Type Parameters
Summary¶
| Kind | Trigger | ||
|---|---|---|---|
TYPE_REMOVED |
A visible type is removed from the API | ||
TYPE_NOW_PROTECTED |
A public nested type becomes protected | ||
TYPE_KIND_CHANGED |
A type changes its kind (e.g., changing a class to an enum or a record) | ||
TYPE_SUPERTYPE_REMOVED |
A visible supertype is removed from a type's hierarchy | ||
TYPE_NEW_ABSTRACT_METHOD |
An abstract class or interface gains a new abstract method | ||
CLASS_NOW_ABSTRACT |
A concrete class becomes abstract | ||
CLASS_NOW_FINAL |
A class becomes effectively final | ||
CLASS_NOW_CHECKED_EXCEPTION |
An unchecked exception becomes checked | ||
CLASS_NOW_STATIC |
A nested inner class becomes static | ||
CLASS_NO_LONGER_STATIC |
A nested static class becomes inner | ||
ANNOTATION_TARGET_REMOVED |
An annotation loses one or more legal targets | ||
ANNOTATION_NEW_METHOD_WITHOUT_DEFAULT |
An annotation gains an element with no default value | ||
ANNOTATION_NO_LONGER_REPEATABLE |
An annotation stops being repeatable | ||
ANNOTATION_METHOD_NO_LONGER_DEFAULT |
An annotation element loses its default value | ||
FIELD_REMOVED |
A visible field is removed from the API | ||
FIELD_NOW_PROTECTED |
A public field becomes protected | ||
FIELD_TYPE_ERASURE_CHANGED |
A field type change alters the erased field descriptor | ||
FIELD_TYPE_CHANGED_INCOMPATIBLE |
A field type change breaks reads or writes | ||
FIELD_NOW_FINAL |
A writable field becomes final | ||
FIELD_NOW_STATIC |
An instance field becomes static | ||
FIELD_NO_LONGER_STATIC |
A static field becomes an instance field | ||
EXECUTABLE_REMOVED |
An executable is removed from the API | ||
EXECUTABLE_NOW_PROTECTED |
A public executable becomes protected | ||
EXECUTABLE_NOW_THROWS_CHECKED_EXCEPTION |
An executable adds or widens a checked exception | ||
EXECUTABLE_NO_LONGER_THROWS_CHECKED_EXCEPTION |
An executable removes or narrows a checked exception | ||
EXECUTABLE_PARAMETER_GENERICS_CHANGED |
Parameter generic arguments change while erasure stays the same | ||
METHOD_RETURN_TYPE_ERASURE_CHANGED |
A return type change alters the erased method signature | ||
METHOD_RETURN_TYPE_CHANGED_INCOMPATIBLE |
A return type change breaks callers or overriders | ||
METHOD_NOW_ABSTRACT |
A concrete method becomes abstract | ||
METHOD_NOW_FINAL |
An overridable method becomes final | ||
METHOD_NOW_STATIC |
An instance method becomes static | ||
METHOD_OVERRIDABLE_NOW_STATIC |
An overridable or interface method becomes static | ||
METHOD_NO_LONGER_STATIC |
A static method becomes an instance method | ||
FORMAL_TYPE_PARAMETER_ADDED |
A formal type parameter is added to a declaration | ||
FORMAL_TYPE_PARAMETER_REMOVED |
A formal type parameter is removed from a declaration | ||
FORMAL_TYPE_PARAMETER_CHANGED |
A declaration's formal type parameter changes bounds in an incompatible way |
Type-Related¶
TYPE_REMOVED¶
An exported type disappears from the API. This includes a public top-level type becoming package-private or a nested type no longer visible.
Library
Client
TYPE_NOW_PROTECTED¶
A public nested type becomes protected. Code outside the allowed inheritance context can no longer reference it.
Library
Client
TYPE_KIND_CHANGED¶
A type changes kind — for example from a class to an interface, an enum, or a record. The rules governing how the type can be instantiated, extended, or implemented change fundamentally, breaking clients that relied on the original kind.
Library
Client
TYPE_SUPERTYPE_REMOVED¶
An exported supertype is removed from a type's hierarchy. Roseau reports the closest exported supertype that no longer appears in the new hierarchy. Upcasts and API contracts based on the removed supertype stop working.
Library
Client
TYPE_NEW_ABSTRACT_METHOD¶
An abstract class or interface gains a new abstract method. Existing source-level implementations must provide it.
Library
Client
Why source-only
Compiled classes that already implement the interface continue to link and load — the JVM does not verify method completeness when loading a class. Only recompilation of implementing classes forces the new method to be added.
Class-Related¶
CLASS_NOW_ABSTRACT¶
A concrete class becomes abstract. Clients that instantiate it directly can no longer compile or link.
Library
Client
CLASS_NOW_FINAL¶
A class that was not effectively final becomes effectively final, preventing subclassing. This fires for explicit final, for sealed (without non-sealed), and for any change that removes all constructors accessible to subclasses.
Library
Client
CLASS_NOW_CHECKED_EXCEPTION¶
An exception class that extended RuntimeException (directly or indirectly) now extends Exception (directly or indirectly). Callers must catch or declare it.
Library
Client
CLASS_NOW_STATIC¶
A nested inner class becomes static. Source code using the qualified instantiation form outer.new B() no longer compiles. Compiled code also breaks: an inner class constructor carries a hidden enclosing-instance parameter that disappears when the class becomes static, so compiled call sites fail to link.
Library
Client
CLASS_NO_LONGER_STATIC¶
A nested static class becomes inner. Clients now need an enclosing instance to create it, breaking both the source form and compiled code.
Library
Client
Annotation-Related¶
ANNOTATION_TARGET_REMOVED¶
An annotation loses one or more valid placement targets. Existing uses at those targets no longer compile.
Library
@java.lang.annotation.Target({
- java.lang.annotation.ElementType.FIELD,
- java.lang.annotation.ElementType.LOCAL_VARIABLE
+ java.lang.annotation.ElementType.FIELD
})
public @interface A {}
Client
ANNOTATION_NEW_METHOD_WITHOUT_DEFAULT¶
An annotation gains a new element with no default value. Existing annotation uses must now supply a value for the new element.
Library
Client
ANNOTATION_NO_LONGER_REPEATABLE¶
An annotation loses @Repeatable. Sites that use the annotation more than once no longer compile.
Library
Client
ANNOTATION_METHOD_NO_LONGER_DEFAULT¶
An existing annotation element loses its default value. Annotation uses that relied on the default must now supply a value explicitly.
Library
Client
Field-Related¶
FIELD_REMOVED¶
A visible field disappears from the API and can no longer be accessed in client code.
Library
Client
FIELD_NOW_PROTECTED¶
A public field becomes protected. Code outside subclasses or the package loses access.
Library
Client
FIELD_TYPE_ERASURE_CHANGED¶
A field's type changes in a way that alters its erased descriptor. Recompilation can still succeed (for example via autoboxing), but compiled bytecode that references the old field descriptor fails at link time. Fields that are compile-time constants (public static final fields of a primitive type or String initialized to a constant expression) are excluded: the Java compiler inlines their value at every use site and emits no field-access instruction, so the field descriptor is irrelevant to compiled clients.
Library
Client
Why binary-only
Java source allows reading an Integer field into an int variable via unboxing, so recompilation succeeds. Compiled bytecode carries the old field descriptor (I vs Ljava/lang/Integer;) and fails when the JVM resolves the field.
FIELD_TYPE_CHANGED_INCOMPATIBLE¶
A field's type changes in a way that breaks source-level reads or writes, even though the erased descriptor is unchanged.
Library
Client
FIELD_NOW_FINAL¶
A writable field becomes final. Client code that assigns to the field can no longer compile or link.
Library
Client
FIELD_NOW_STATIC¶
An instance field becomes static. Compiled getfield instructions are incompatible with a static field and fail at link time.
Library
Client
Why binary-only
Java source allows accessing a static field through an instance reference (with a warning), so recompilation usually succeeds. Compiled bytecode uses getfield, which is incompatible with a static field.
FIELD_NO_LONGER_STATIC¶
A static field becomes an instance field. Static access no longer compiles, and compiled getstatic instructions fail at link time.
Library
Client
Executable-Related¶
EXECUTABLE_REMOVED¶
A visible method or constructor disappears from the API.
Library
Client
EXECUTABLE_NOW_PROTECTED¶
A public method or constructor becomes protected. Callers outside the allowed inheritance context lose access.
Library
Client
EXECUTABLE_NOW_THROWS_CHECKED_EXCEPTION¶
A method or constructor adds a checked exception that is not already covered by an existing entry in the throws clause. Callers must catch or declare it.
Library
Client
EXECUTABLE_NO_LONGER_THROWS_CHECKED_EXCEPTION¶
A checked exception is removed or narrowed in a way that breaks overriders or callers.
- For non-final methods: every old checked exception must still be covered by the same or a broader exception in the new signature. Narrowing to a subtype (e.g.,
IOException → FileNotFoundException) counts as a breaking removal because subclasses that declared the old exception can no longer do so. - For final methods or constructors: narrowing to a subtype is allowed; only complete removal breaks callers.
Library
Client
Removing an exception is a source break
Removing a checked exception from a non-final method is a source break for two reasons. First, subclasses that override the method may declare the same exception in their throws clause — once the parent removes it, those overrides no longer compile. Second, callers that catch the exception are also broken: Java makes it a compile error (not just a warning) to have a catch block for a checked exception that the callee can no longer throw.
EXECUTABLE_PARAMETER_GENERICS_CHANGED¶
Parameter generic arguments change while the erased signature stays the same.
- For overridable methods, any change to generic arguments is a source break: subclasses overriding with the old signature would have mismatched parameter types.
- For effectively final methods or constructors, Roseau applies a variance check: the new parameter type must be a supertype of the old to be compatible.
Library
public class A {
- public final void m(java.util.List<String> xs) {}
+ public final void m(java.util.List<Integer> xs) {}
}
Client
Method-Related¶
METHOD_RETURN_TYPE_ERASURE_CHANGED¶
A method's return type changes in a way that alters its erased signature. Recompilation can still succeed (for example via autoboxing), but compiled bytecode linked against the old method descriptor fails at link time.
Library
Client
METHOD_RETURN_TYPE_CHANGED_INCOMPATIBLE¶
A method's return type changes in a way that breaks callers or overriders at the source level, even when erasure is unchanged.
Callers break when the new return type cannot be used in place of the old one — for instance, when a caller assigns the return value to a variable typed with the old return type. Overriders break when the new return type is not a supertype of the old return type. Java's covariant override rule requires the overriding method's return type to be a subtype of the overridden method's; if the parent's return type narrows, subclasses that still declare the old (broader) type violate this rule.
Library
Client
METHOD_NOW_ABSTRACT¶
A concrete method becomes abstract. Subclasses or anonymous classes that do not implement it can no longer be compiled or instantiated.
Library
Client
METHOD_NOW_FINAL¶
An overridable method becomes final. Existing overrides in subclasses no longer compile, and compiled override call sites fail at link time.
Library
Client
METHOD_NOW_STATIC¶
An instance method becomes static. Compiled invokevirtual instructions are incompatible with a static method and fail at link time.
Library
Client
Why binary-only
Java source allows calling a static method through an instance reference (with a warning), so recompilation usually succeeds. Compiled bytecode uses invokevirtual, which is incompatible with a static method. See METHOD_OVERRIDABLE_NOW_STATIC for the additional source-breaking case when the method was overridable.
METHOD_OVERRIDABLE_NOW_STATIC¶
An overridable method — or any interface method — becomes static. Subclasses or implementors that override it can no longer do so. Roseau reports this alongside METHOD_NOW_STATIC when the old method could be overridden.
Library
Client
METHOD_NO_LONGER_STATIC¶
A static method becomes an instance method. Static calls no longer compile, and compiled invokestatic instructions fail at link time.
Library
Client
Formal Type Parameters¶
FORMAL_TYPE_PARAMETER_ADDED¶
A type, method, or constructor gains an extra formal type parameter, changing its generic arity. Existing source-level uses with the old arity no longer compile.
Library
Client
FORMAL_TYPE_PARAMETER_REMOVED¶
A type, method, or constructor loses a formal type parameter, changing its generic arity. Existing source-level uses with the old arity no longer compile.
Library
Client
FORMAL_TYPE_PARAMETER_CHANGED¶
Generic bounds change incompatibly. The check depends on what is being changed:
- For types and non-overridable executables (constructors, or methods on effectively-final types): each new bound must be a supertype of some old bound. Tightening a bound — replacing it with a more specific type — narrows the set of valid type arguments and breaks existing uses.
- For overridable methods: bounds must be exactly equal after accounting for any type parameter renames. Any change to bounds is a break, because a subclass overriding the method with the old bounds would no longer match the new signature.
Previously valid type arguments may no longer satisfy the new bounds.
Library
Client