Java is about to change, as the new proposal will support type classes

Preface

Following pipeline programming, lambda expressions record、 After sealing classes, pattern matching, and other features, Java seems to want to go further in the functional programming paradigm. At the 2025 JVM Language Summit, Java architect Brian Goetz proposed a new proposal for Java to support type classes, aiming to enhance Java's extensibility. In the foreseeable future, this feature, like the value type (Project Valhalla), will serve as a nuclear level update to the Java language, significantly enhancing its competitiveness.

Initial acquaintance type class

You may not have been uncovered to the term 'type class', so let's break down the concept of' type class' step by step.

Firstly, imagine the existing interfaces in Java. The interface defines what an object can do. For example, the Comparable interface defines how an object can be compared to another object of the same type, while the List interface defines how to add or remove elements from a list. When you have a String object, it can call the length() method because it is an instance of a string. Java's existing interfaces are very good at describing the behavior of instances.

However, sometimes what we want to describe is the behavior of the type itself, rather than the behavior of a specific object. There are still some things that Java interfaces cannot do, which are the problems that type classes want to solve.

Type classes are a powerful abstraction mechanism that allows you to add new behavior to any closed data type without using subtypes (i.e. without modifying the inheritance hierarchy of existing classes).

What is a "type class" in Java?

In the new design of Java, a "type class" is essentially a regular interface.

It defines a certain behavior that a certain type should possess, usually represented by generics (such as<T>) to indicate which type it is applicable to.

But the key is that it does not require direct implementation by that type.

This sounds a bit abstract, let's use an analogy to understand it:

Imagine Comparable and Comparator.

The Comparable interface is usually implemented by the class itself (such as String implementations Comparable<String>), which defines "how a compares with b".

The comparator interface is different. A comparator<Integer>object can compare two Integer values, but the Integer class itself does not directly implement the comparator interface. A comparator is an independent "comparator" provided by external code to the scene that needs to be compared (for example, the Collections. sort method can accept a comparator).

Java's "type class" is like a comparator, an interface that describes the behavior of a type, rather than letting the type itself implement it.

What is' Witness'?

If the type class is an interface, then the 'witness' is a regular instance of that type class.

You can imagine it as a "credential" or "proof" that proves a certain type indeed possesses the behavior defined by the type class.

For example, an instance of comparator<Integer>is a witness to the comparability of Integer type.

Why does Java require type classes? What problems did it solve?

The introduction of type classes in Java is mainly aimed at enhancing the language's growability and extensibility, and addressing several limitations of existing interfaces.

Add new behavior to existing types (i.e. expression problem)

Imagine you have a String class (string) that is built-in in Java and you cannot modify its source code. Now you want to add a new behavior to String, such as making it support some new formatting operation (Formattable).

Using traditional interfaces, you must have the String class implement Formattable. But you cannot modify the code of the String class!

With a type class, you can define a Formattable type class externally and create a witness for Formattable<String>. This witness proves that the String type supports this formatting behavior without modifying the code of the String itself.

Abstract 'behavior of the type itself' rather than 'instance behavior'

Suppose you want to calculate the total sum of a list of numbers. If the list is empty, the total sum is 0. If the list contains a String, then the sum is an 'empty string' (the 'zero value' of string concatenation).

You cannot ask any element in an empty list, 'What is your zero value?' because there are no elements at all. What you need is that the zero value of type Integer is 0, and the zero value of type String is' '.

Traditional interfaces define instance methods (such as myString. length()), which require an instance to call. But sometimes we need the behavior of a static type itself (for example, the Monoid<T>type class can define a zero() method to obtain the zero value of the type).

Provide multiple behavioral instances for the same type

Assuming you have a short type. It can be widened conversion to int, long, or even float.

If using the traditional interface ConvertibleTo<T>, Java specifies that a class can only implement one ConvertibleTo<T>(such as ConvertibleTo<Int>), and cannot simultaneously implement ConvertibleTo<Long>and ConvertibleTo<Float>.

Type classes do not have this restriction. You can provide multiple "witnesses" for the short type, such as ConversionWitness<Short, Int>, ConversionWitness<Short, Long>, etc., each of which can be converted into different types of "credentials" as a short.

Avoid naming conflicts

Sometimes the method names in an interface may conflict with the method names already present in the class that implements it.

The indirectness introduced through type classes can better isolate the generic naming of interface methods from the naming in specific classes, reducing the possibility of conflicts.

How will Java implement the publication and retrieval of these 'witnesses'?

This is the core of the type class mechanism:

Publishing Witnesses

You can declare a type to have a certain behavior by marking a public static final field (like a static constant) as a witness.

You can also mark a static method as a witness. These methods can derive new testimonies based on existing ones. For example, if you have a witness of Monoid<T>(know how to "add" type T), you can derive a witness of Monoid<Optional<T>(know how to "add" Optional<T>).

Finding Witnesses

The compiler will automatically perform a 'proof search' process during code compilation to find suitable witnesses.

To ensure that the results are clear and predictable, only classes directly related to the type parameters of the witness you are looking for will be considered for providing a witness (for example, when searching for witnesses for Monoid<Box<String>, only Monoid, Box, and String types will be considered, and unrelated Integer or other classes will not be asked).

When there are multiple potential witnesses, there will be a set of conflict resolution rules, similar to the existing method coverage rules in Java.

Due to these witnesses being determined at compile time and considered as' symbolic constants', it is highly advantageous for the Just In Time (JIT) compiler of the Java Virtual Machine (JVM) to actively inline and optimize, and even simplify complex operations into single machine instructions, thereby improving performance.

The practical benefits and application scenarios brought by type classes:

Implicit Widening Conversions

In the future, users can define new numerical types (such as float16) and provide witnesses to automatically expand their conversions like built-in int to long or float to double, without the need to hard code a bunch of conversion tables in the Java language specification.

Operator Overloading

Allow overloading operators for value types (especially numeric types), such as allowing float16 types to use the+operator for addition instead of writing it as a. add (b).

But there will be very strict restrictions to prevent misuse: new operators cannot be introduced, only existing operators in Java can be overloaded; Limited to value types, especially numerical types; And a complete set of algebraic structural operations (such as addition, subtraction, multiplication, division) must be implemented to ensure that the mathematical meaning of the operators remains unchanged.

Collection Literals

You can create a collection using simpler syntax, such as [a, b, c] directly representing a list.

The compiler will look for a witness of the SequenceBuildable type at compile time, which will tell the compiler how to build the corresponding collection from these elements. This means that any collection type that provides a corresponding witness can use this concise literal syntax, no longer limited to the few built-in collections in Java.

Differentiated Creation Expressions

Allow a witness to provide a default instance for certain value types (usually those with a 'natural default value', such as 0 for numeric types).

For example, when creating an array, if the type has default instances, the language can automatically fill these valid default values instead of always filling in 0 or null as it does now.

Compared to Scala, Scala 3 has already provided similar type class functionality through given instances and using clauses. The approach of Java is similar to that of Scala's given instances, but Java's design will be more rigorous and predictable in witness lookup and conflict resolution to support optimization and clarity.

In summary, Java's type class mechanism defines behavior through ordinary interfaces (type classes), with ordinary instances of interfaces serving as credentials (witnesses), and combined with the compiler's intelligent lookup mechanism, aims to make Java more flexible and extensible, able to handle behaviors that can currently be achieved through built-in features or clumsy patterns in a more elegant way. This will enable user code to better interact and expand with new language features.

Will JDK17 replace JDK8?

learn more

Java gzip output image stream

learn more

Why is it required to use Static to modify ThreadLocal variables?

learn more