Detectable binary compatibility problems


Such types may be deleted or otherwise changed, even if incompatibilities are otherwise described here, provided that the affected binaries of that package are updated together. This section describes the effects of changes to the declaration of a class and its members and constructors on pre-existing binaries.

If a class that was not declared abstract is changed to be declared abstract , then pre-existing binaries that attempt to create new instances of that class will throw either an InstantiationError at link time, or if a reflective method is used an InstantiationException at run time; such a change is therefore not recommended for widely distributed classes. Changing a class that is declared abstract to no longer be declared abstract does not break compatibility with pre-existing binaries.

If a class that was not declared final is changed to be declared final , then a VerifyError is thrown if a binary of a pre-existing subclass of this class is loaded, because final classes can have no subclasses; such a change is not recommended for widely distributed classes. Changing a class that is declared final to no longer be declared final does not break compatibility with pre-existing binaries.

Changing a class that is not declared public to be declared public does not break compatibility with pre-existing binaries. If a class that was declared public is changed to not be declared public , then an IllegalAccessError is thrown if a pre-existing binary is linked that needs but no longer has access to the class type; such a change is not recommended for widely distributed classes. A ClassCircularityError is thrown at load time if a class would be a superclass of itself.

Changes to the class hierarchy that could result in such a circularity when newly compiled binaries are loaded with pre-existing binaries are not recommended for widely distributed classes. Changing the direct superclass or the set of direct superinterfaces of a class type will not break compatibility with pre-existing binaries, provided that the total set of superclasses or superinterfaces, respectively, of the class type loses no members.

If a change to the direct superclass or the set of direct superinterfaces results in any class or interface no longer being a superclass or superinterface, respectively, then linkage errors may result if pre-existing binaries are loaded with the binary of the modified class. Such changes are not recommended for widely distributed classes. Suppose that a new version of class Super is then compiled:. This version of class Super is not a subclass of Hyper.

If we then run the existing binaries of Hyper and Test with the new version of Super , then a VerifyError is thrown at link time. The verifier objects because the result of new Super cannot be passed as an argument in place of a formal parameter of type Hyper , because Super is not a subclass of Hyper.

It is instructive to consider what might happen without the verification step: This demonstrates that without the verifier, the Java type system could be defeated by linking inconsistent binary files, even though each was produced by a correct Java compiler.

The lesson is that an implementation that lacks a verifier or fails to use it will not maintain type safety and is, therefore, not a valid implementation. Assuming the following client code is legal:. This is analogous to other situations where a class transformation that is binary compatible for a client might not be source compatible for the same client. Adding or removing a type parameter of a class does not, in itself, have any implications for binary compatibility.

If such a type parameter is used in the type of a field or method, that may have the normal implications of changing the aforementioned type. Renaming a type parameter of a class has no effect with respect to pre-existing binaries. Changing any other bound has no effect on binary compatibility. No incompatibility with pre-existing binaries is caused by adding an instance respectively static member that has the same name and accessibility for fields , or same name and accessibility and signature and return type for methods , as an instance respectively static member of a superclass or subclass.

No error occurs even if the set of classes being linked would encounter a compile-time error. Deleting a class member or constructor that is not declared private may cause a linkage error if the member or constructor is used by a pre-existing binary.

Suppose that a new version of class Super is produced:. Then, recompiling Super and executing this new binary with the original binaries for Test and Hyper produces the output:.

The super keyword can be used to access a method declared in a superclass, bypassing any methods declared in the current class. Identifier is resolved, at compile time, to a method m in the superclass S. If the method m is an instance method, then the method which is invoked at run time is the method with the same signature as m that is a member of the direct superclass of the class containing the expression involving super.

Then, if Super and Hyper are recompiled but not Test , then running the new binaries with the existing binary of Test produces the output:. Changing the declared access of a member or constructor to permit less access may break compatibility with pre-existing binaries, causing a linkage error to be thrown when these binaries are resolved. Less access is permitted if the access modifier is changed from default access to private access; from protected access to default or private access; or from public access to protected , default, or private access.

Changing a member or constructor to permit less access is therefore not recommended for widely distributed classes. Perhaps surprisingly, the binary format is defined so that changing a member or constructor to be more accessible does not cause a linkage error when a subclass already defines a method to have less access.

If the package points defines the class Point:. If the method print in class Point is changed to be public , and then only the Point class is recompiled, and then executed with the previously existing binary for Test , then no linkage error occurs.

This happens even though it is improper, at compile time, for a public method to be overridden by a protected method as shown by the fact that the class Test could not be recompiled using this new Point class unless print in Test were changed to be public. Allowing superclasses to change protected methods to be public without breaking binaries of pre-existing subclasses helps make binaries less fragile.

The alternative, where such a change would cause a linkage error, would create additional binary incompatibilities. Widely distributed programs should not expose any fields to their clients.

Apart from the binary compatibility issues discussed below, this is generally good software engineering practice. Adding a field to a class may break compatibility with pre-existing binaries that are not recompiled. Assume a reference to a field f with qualifying type T. Assume further that f is in fact an instance respectively static field declared in a superclass of T , S , and that the type of f is X.

If a new field of type X with the same name as f is added to a subclass of S that is a superclass of T or T itself, then a linkage error may occur. Such a linkage error will occur only if, in addition to the above, either one of the following conditions hold:.

The new field is less accessible than the old one. The new field is a static respectively instance field. In particular, no linkage error will occur in the case where a class could no longer be recompiled because a field access previously referenced a field of a superclass with an incompatible type.

The previously compiled class with such a reference will continue to reference the field declared in a superclass. Suppose a new version of class Super is produced:.

Then, recompiling Hyper and Super , and executing the resulting new binaries with the old binary of Test produces the output:. The field h of Hyper is output by the original binary of Test. While this may seem surprising at first, it serves to reduce the number of incompatibilities that occur at run time. In an ideal world, all source files that needed recompilation would be recompiled whenever any one of them changed, eliminating such surprises.

But such a mass recompilation is often impractical or impossible, especially in the Internet. And, as was previously noted, such recompilation would sometimes require further changes to the source code. If the resulting binary is used with the existing binaries for Hyper and Test , then the output is still:.

Deleting a field from a class will break compatibility with any pre-existing binaries that reference this field, and a NoSuchFieldError will be thrown when such a reference from a pre-existing binary is linked.

Only private fields may be safely deleted from a widely distributed class. If a field that was not declared final is changed to be declared final , then it can break compatibility with pre-existing binaries that attempt to assign new values to the field. Changing A Variable To Be final. If Super is recompiled but not Test , then running the new binary with the existing binary of Test results in a IllegalAccessError.

Deleting the keyword final or changing the value to which a field is initialized does not break compatibility with existing binaries. Suppose that a new version of class Flags is produced:. If Flags is recompiled but not Test , then running the new binary with the existing binary of Test produces the output:. This behavior would not change if Flags were changed to be an interface, as in the modified example:.

The best way to avoid problems with "inconstant constants" in widely-distributed code is to declare as compile-time constants only values which truly are unlikely ever to change. Other than for true mathematical constants, we recommend that source code make very sparing use of class variables that are declared static and final. If the read-only nature of final is required, a better choice is to declare a private static variable and a suitable accessor method to get its value.

We also recommend, as a general rule, that only truly constant values be declared in interfaces. We note, but do not recommend, that if a field of primitive type of an interface may change, its value may be expressed idiomatically as in:.

Similar idioms exist for the other primitive types. If a field that is not declared private was not declared static and is changed to be declared static , or vice versa, then a linkage error, specifically an IncompatibleClassChangeError , will result if the field is used by a pre-existing binary which expected a field of the other kind.

Such changes are not recommended in code that has been widely distributed. Adding or deleting a transient modifier of a field does not break compatibility with pre-existing binaries.

Adding a method or constructor declaration to a class will not break compatibility with any pre-existing binaries, even in the case where a type could no longer be recompiled because an invocation previously referenced a method or constructor of a superclass with an incompatible type.

The previously compiled class with such a reference will continue to reference the method or constructor declared in a superclass. Assume a reference to a method m with qualifying type T. Assume further that m is in fact an instance respectively static method declared in a superclass of T , S. If a new method of type X with the same signature and return type as m is added to a subclass of S that is a superclass of T or T itself, then a linkage error may occur.

The new method is less accessible than the old one. The new method is a static respectively instance method. Deleting a method or constructor from a class may break compatibility with any pre-existing binary that referenced this method or constructor; a NoSuchMethodError may be thrown when such a reference from a pre-existing binary is linked.

Such an error will occur only if no method with a matching signature and return type is declared in a superclass. Adding one or more constructor declarations to the source code of such a class will prevent this default constructor from being supplied automatically, effectively deleting a constructor, unless one of the new constructors also has no parameters, thus replacing the default constructor.

The automatically supplied constructor with no parameters is given the same access modifier as the class of its declaration, so any replacement should have as much or more access if compatibility with pre-existing binaries is to be preserved. Adding or removing a type parameter of a method or constructor does not, in itself, have any implications for binary compatibility. If such a type parameter is used in the type of the method or constructor, that may have the normal implications of changing the aforementioned type.

Renaming a type parameter of a method or constructor has no effect with respect to pre-existing binaries. If the type parameter is used as the type of a field, the effect is as if the field was removed and a field with the same name, whose type is the new erasure of the type variable, was added.

If the type parameter is used as the type of any formal parameter of a method, but not as the return type, the effect is as if that method were removed, and replaced with a new method that is identical except for the types of the aforementioned formal parameters, which now have the new erasure of the type parameter as their type. If the type parameter is used as a return type of a method, but not as the type of any formal parameter of the method, the effect is as if that method were removed, and replaced with a new method that is identical except for the return type, which is now the new erasure of the type parameter.

If the type parameter is used as a return type of a method and as the type of one or more formal parameters of the method, the effect is as if that method were removed, and replaced with a new method that is identical except for the return type, which is now the new erasure of the type parameter, and except for the types of the aforementioned formal parameters, which now have the new erasure of the type parameter as their types.

Changing the name of a formal parameter of a method or constructor does not impact pre-existing binaries.

Changing a method that is declared abstract to no longer be declared abstract does not break compatibility with pre-existing binaries. Changing a method that is not declared abstract to be declared abstract will break compatibility with pre-existing binaries that previously invoked the method, causing an AbstractMethodError.

Changing A Method To Be abstract. If Super is recompiled but not Test , then running the new binary with the existing binary of Test results in an AbstractMethodError , because class Test has no implementation of the method out , and is therefore is or should be abstract.

Changing a method that is declared final to no longer be declared final does not break compatibility with pre-existing binaries. Changing an instance method that is not declared final to be declared final may break compatibility with existing binaries that depend on the ability to override the method. Changing A Method To Be final. If Super is recompiled but not Test , then running the new binary with the existing binary of Test results in a VerifyError because the class Test improperly tries to override the instance method out.

Changing a class static method that is not declared final to be declared final does not break compatibility with existing binaries, because the method could not have been overridden.

Adding or deleting a native modifier of a method does not break compatibility with pre-existing binaries. The impact of changes to types on pre-existing native methods that are not recompiled is beyond the scope of this specification and should be provided with the description of an implementation. Implementations are encouraged, but not required, to implement native methods in a way that limits such impact.

If a method that is not declared private is also declared static that is, a class method and is changed to not be declared static that is, to an instance method , or vice versa, then compatibility with pre-existing binaries may be broken, resulting in a linkage time error, namely an IncompatibleClassChangeError , if these methods are used by the pre-existing binaries.

Adding or deleting a synchronized modifier of a method does not break compatibility with pre-existing binaries. Changes to the throws clause of methods or constructors do not break compatibility with pre-existing binaries; these clauses are checked only at compile time. Changes to the body of a method or constructor do not break compatibility with pre-existing binaries. The keyword final on a method does not mean that the method can be safely inlined; it means only that the method cannot be overridden.

It is still possible that a new version of that method will be provided at link-time. Furthermore, the structure of the original program must be preserved for purposes of reflection. Therefore, we note that a Java compiler cannot expand a method inline at compile time.

In general we suggest that implementations use late-bound run-time code generation and optimization. Adding new methods or constructors that overload existing methods or constructors does not break compatibility with pre-existing binaries.

The signature to be used for each invocation was determined when these existing binaries were compiled; therefore newly added methods or constructors will not be used, even if their signatures are both applicable and more specific than the signature originally chosen. If Super is recompiled but not Test , then running the new binary with the existing binary of Test still produces the output:.

However, if Test is then recompiled, using this new Super , the output is then:. If an instance method is added to a subclass and it overrides a method in a superclass, then the subclass method will be found by method invocations in pre-existing binaries, and these binaries are not impacted.

If a class method is added to a class, then this method will not be found unless the qualifying type of the reference is the subclass type. Adding or reordering constants in an enum type will not break compatibility with pre-existing binaries.

If a pre-existing binary attempts to access an enum constant that no longer exists, the client will fail at run time with a NoSuchFieldError. Therefore such a change is not recommended for widely distributed enums. In all other respects, the binary compatibility rules for enums are identical to those for classes.

This section describes the impact of changes to the declaration of an interface and its members on pre-existing binaries. Changing an interface that is not declared public to be declared public does not break compatibility with pre-existing binaries. If an interface that is declared public is changed to not be declared public , then an IllegalAccessError is thrown if a pre-existing binary is linked that needs but no longer has access to the interface type, so such a change is not recommended for widely distributed interfaces.

Multiple interfaces allow your systems to evolve over time, without breaking existing components or requiring massive re-compiles, because a released interface is never changed. Instead, new functionality is added to a system by creating new interfaces. If you decide to use the Version Compatibility feature, you may find the following rules helpful in determining when to use the different options:. When you begin working on a new version of an existing component, you may decide that the only way to make necessary enhancements is to break backward compatibility.

In this case, set No Compatibility the first time you compile your project. Change the Project Name on the General tab of the Project Properties dialog box, so that the incompatible component will have a different type library name. This ensures that the objects the component provides will have unique programmatic IDs.

After compiling once with No Compatibility, switch to Project Compatibility to simplify your development tasks. Project Compatibility is discussed in "Project Compatibility: Switch to Binary Compatibility mode when you begin work on the second version of any component, if you want applications compiled using the earlier version to continue to work using the new version. If you enhance any of the interfaces in a component, Visual Basic will change their interface IDs.

The technique of evolving component software by adding interfaces depends on interface invariance. That is, an interface once defined is never changed — including the interface ID. The Version Compatibility box, located on the Component tab of the Project Properties dialog box, contains three options: The appropriate use of these options is described below.

When to Use Version Compatibility Options If you decide to use the Version Compatibility feature, you may find the following rules helpful in determining when to use the different options: