Quality Outreach Heads-up - JDK 21: Sequenced Collections Incompatibilities

The OpenJDK Quality Group is promoting the testing of FOSS projects with OpenJDK builds as a way to improve the overall quality of the release. This heads-up is part of a regular communication sent to the projects involved. To learn more about the program, and how-to join, please check here.

Heads-Up - JDK 21 - Potential Sequenced Collections Incompatibilities

The Sequenced Collection JEP has been integrated into JDK 21, build 20.

This JEP introduces several new interfaces into the collections framework’s interface hierarchy, and these interfaces introduce new default methods. When such changes are made, they can cause conflicts that result in source or binary incompatibilities. Any conflicts that occur will be in code that implements new collections or that subclasses existing collection classes. Code that simply uses collections implementations will be largely unaffected.

There are several kinds of conflicts that might arise.

The first is a simple method naming conflict, if a method already exists with the same name but with a different return type or access modifier. Another is a clash between different inherited default method implementations arising from covariant overrides. A class might inherit multiple default methods if it implements multiple interfaces from different parts of the collections framework. A third example occurs with type inference. With type inference (e.g., the use of var) the compiler will infer a type for that local variable. It’s possible for other code to use explicitly declared types that must match the inferred type. The change to the interface hierarchy might result in a different inferred type, causing an incompatibility.

The following examples provide additional details and strategies to mitigate potential incompatibilities.

Method Naming Conflict

New interfaces with new methods have been retrofitted into the existing Collections type hierarchy. These new methods might clash with methods on existing classes. For example, suppose you have a class like this:

class MyList<E> implements List<E> {
    * Returns the first element of this list, or an empty
    * Optional if this list is empty.
    public Optional<E> getFirst() {  }

The SequencedCollection interface, which is inherited by List, defines a new method: E getFirst();

Since the return type differs, this will create a source incompatibility. (This shouldn’t be a binary incompatibility, though, since existing binaries will continue to call the old method.) A related kind of conflict can occur over the access modifiers. For example, a package-access method cannot override a method defined in an interface, which must have public access. Unfortunately, the only way to mitigate the source incompatibility is to rename the conflicting method or to rearrange the type hierarchy, for example, to make MyList no longer implement List.

Covariant Override Conflicts

The List and Deque interface both provide covariant overrides of the reversed() method:

List – List<E> reversed();

Deque – Deque<E> reversed();

This works fine as long as a collection implementation implements only one “family” of collection, such as List or Deque but not both. However, in some cases a collection implementation might decide to implement both List and Deque:

class MyDoubleEndedList<E> implements List<E>, Deque<E> {  }

This works fine in JDK 20, but it will fail starting with JDK 21 build 20. The reason is that it inherits conflicting definitions of the reversed() method, one returning List and the other returning Deque. The compiler can’t choose one over the other, so it emits a compile-time error.

The solution is to add a reversed() method to MyDoubleEndedList that returns a type that is both a List and a Deque. This could be a MyDoubleEndedList itself (or a subclass), or it could be another interface defined for the purpose. The returned object should implement a reverse-ordered view of the original collection.

There is an example of this in the JDK itself. The java.util.LinkedList class implements both List and Deque and solved this issue by adding a reversed() method (see source code) that returns an instance of a reverse-ordered view of a LinkedList. The implementation of the reverse-ordered view is somewhat tedious but it isn’t difficult. There is no actual logic involved. Essentially, it overrides and delegates most methods to a reverse-ordered List view or a reverse-ordered Deque view, both of which have implementations that are available via default methods on those interfaces.

Type Inference

The results of type inference may differ, creating conflicts with assumptions made by existing source code. For example, consider this code:

List<Collection<String>> m() {
    var list = List.of(new ArrayDeque<String>(), List.of("foo"));
    return list;

This compiles on JDK 20 but will give a compile-time error on JDK 21 starting with build 20. The reason is that the inferred type of list changes with the addition of the new collection types.

The type of List.of(a, b) is List<T> where T is the common supertype (more formally, the “least upper bound”) of the arguments a and b. In JDK 20 T was Collection<String> and so the type of list was List<Collection<String>>. This agrees with the return type of the method, so there is no error.

In JDK 21 build 20, SequencedCollection interface was retrofitted above both List and Deque, so the new common supertype T became SequencedCollection<String>. The type of list was therefore List<SequencedCollection<String>>. This disagrees with the return type of the method, giving a compile-time error.

There are several ways to fix this, but perhaps the easiest is to use an explicit type declaration for list instead of using var:

List<Collection<String>> list = List.of(new ArrayDeque<String>(), List.of("foo"));

This declares list to be a type that agrees with the method’s return type, preventing a different type from being inferred that disagrees with the method’s return type.

For more information on Sequenced Collection, make sure to check the following resources.