What Is Project Amber in Java?

In this post, we’re going to delve into some details of the features being targeted in Project Amber, which was introduced in early 2017 by its lead and language architect Brian Goetz. This project aims to add some really cool beans to the Java programming language that improve the developer’s productivity when writing Java code, namely:

As this is still work in progress, other features could be added to the project in the future. Even though these features may seem to have been addressed late in the Java timeline, it’s worth considering that the Java team has historically been rather cautious in introducing new features to evolve the language, as Goetz explains in a talk about the project.

Local-variable type inference

In Java 5 generics were introduced with the possibility for the compiler to infer type arguments during generic method calls, as shown in the below example:

private <T> void foo(T t) {

public void bar() {
    this.foo(1);  // with implicit type, compiler infers type argument
    this.<Integer>foo(1); // with explicit typing

Further enhancements to type inference were done over the next releases, including the diamond operator in Java 7, enhancements in Java 8 along with lambda and stream support, and in Java 9 allowing the diamond operator in anonymous classes when the inferred type is denotable. With this feature (specified in JEP 286), the compiler will be able to infer declaration types of local variables, subject to certain limitations. The main requirement for the compiler is that the initializer needs to be included with the variable declaration. With this enhancement, a statement like:

File inputFile = new File("input.txt");

could be written as:

var inputFile = new File("input.txt");

Like any other form of type inference, the main benefit is avoiding the redundant typing of the variable type when it can be easily known from the right hand side of the assignment. It is important to remember here that the var keyword does not mean that the variable is dynamically typed. It is just a syntax to avoid writing the manifest type of the local variable; the static typing nature of Java remains intact. Strictly speaking, var is a reserved type name that gets desugared to the variable initializer type by the compiler.

Not every form of local variable declaration can use var to infer the declaration or manifest type of the variable. The following cases do not allow the use of var:

  • Local variables without initializers, such as File inputFile;
  • Local variables initialized to null.
  • Initializers that expect a target type, such as a lambda, a method reference or an array initializer.

As such, below are examples where type inference is not allowed:

// not allowed, lambda expression needs an explicit target-type
var func = s -> Integer.valueOf(s);

// not allowed, method reference needs an explicit target-type
var biConsumer = LogProcessor::process;

// not allowed, array initializer needs an explicit target-type
var array = { 1, 2 };

The majority of local variable declarations in typical code could benefit from this feature. For example, in the OpenJDK codebase, only 13% of local variables cannot be re-written using var. Therefore, the cost of broadening local type inference to include cases like the above may be too high compared to the amount of applicable code that can further benefit from it.

This feature is included in the planned Java 10 release, which is expected to be available in March 2018.

Enhanced enums

This feature enhances enums in two aspects. First, it allows declaring a generic enum, which combines the flexibility and type safety of generics with the simplicity and powerful semantics of an enum. Second, it enhances enums so that an enum constant that is declared as generic or overrides behavior via a class body gets its own type information, along with its own state and behavior.

In some use cases, we may need to define enum constants where each is bound to a certain type. A typical example is an enum that contains mappings to Java types, where a generic enum can be used shown in the below JsonType example:

public enum JsonType<T> {
    DOUBLE<>(Double.class), // can use a diamond operator to infer

    final Class<T> mappedClass;

    JsonType(Class<T> mappedClass) {
        this.mappedClass = mappedClass;

    public T convert(Object o) {

In this case, the enum constant STRING has a sharper type JsonType<String>, enum constant LONG is of type JsonType<Long>, and so on. One could further customize each enum constant with additional state and/or methods:

public enum JsonType<T> {

    // LONG is has an anonymous class as type now,
    // we can use a diamond as long as inferred type is denotable
    LONG<>(Long.class) {
        public String desc = "Long JSON type";

        public boolean isLongValue(JSONValue value) {

Since the enum constant LONG has a class body, its type is an anonymous class whose supertype is JsonType<Long>.

This feature is discussed more in JEP 301 which still has a “Candidate” status, so not all risks may have been addressed and is not expected to reach JDK 10.

Enhancements to lambda expressions

These are a couple of additional features added to lambda along with improving type inference for methods involving lambdas as arguments. The first feature is the ability to use an underscore to denote an unused parameter in a lambda:

BiFunction<Integer, String, String> biss = (i, _) -> String.valueOf(i);

As of Java 9, an underscore can no longer be used as an identifier, and with this feature it now carries a special meaning in the context of lambdas.

The second feature introduced in this JEP for lambdas is the ability to shadow variables declared in the enclosing scope of a lambda by re-using the same variable names to declare the lambda parameters:

int i = 0;
// can declare lambda parameter named i, shadowing the local variable i
BiFunction<Integer, String, String> biss = (i, _) -> String.valueOf(i);

Currently it is not allowed to re-use i in the lambda expression because lambdas are lexically scoped and generally do not allow shadowing variables. Last but not least, improving overload resolution for methods invoked with either a lambda or a method reference as argument is optionally targeted in this project. This should fix false compilation errors that may be commonly encountered when writing methods that accept functional interfaces:

m(Predicate<String> ps) { ... }
m(Function<String, String> fss) { ... }

m(s -> false) // error due to ambiguity, although Predicate
              // should have been inferred

class Foo {
    static boolean g(String s) { return false }
    static boolean g(Integer i) { return false }

m(Foo::g) // error due to ambiguity, although boolean g(String s)
          // should have been selected

Pattern matching

The next feature in the Amber project introduces a powerful construct called a pattern. The motivation behind this feature is the commonly used boilerplate code shown below:

String content = null;

if (msg instanceof JsonMessage) {

    content = unmarshalJson((JsonMessage) msg);

} else if (msg instanceof XmlMessage) {

    content = unmarshalXml((XmlMessage) msg);

} else if (msg instanceof PlainTextMessage) {

    content = ((PlainTextMessage) msg).getText();

} ...

Each condition branch checks if the object is of a certain type, then casts it to that type and extracts some information from it. Pattern matching is a generalization of this “test-extract-bind” technique and can be defined as:

  • a predicate that can be applied to a target
  • a set of binding variables that are extracted from the object matching the predicate

Instead of the above, we could apply a type-test pattern on the object msg using a new matches operator. As a first step, this would remove the redundant cast:

String content = null;

if (msg matches JsonMessage json) {

    content = unmarshalJson(json);

} else if (msg matches XmlMessage xml) {

    content = unmarshalXml(xml);

} else if (msg matches PlainTextMessage text) {

    content = text.getText();

} ...

The existing switch statement already makes use of the simplest form of patterns: the constant pattern. Given an object whose type is allowed in a switch statement, we test the object if it matches any of several constant expressions. Now the switch statement would also benefit from type-test patterns:

String content;

switch (msg) {

    case JsonMessage json:      content = unmarshalJson(json); break;

    case XmlMessage xml:        content = unmarshalXml(xml); break;

    case PlainTextMessage text: content = text.getText(); break;


    default:                    content = msg.toString();

This switch could be now be considered a “type switch” but it’s actually a generalized switch that can take other types of patterns; in this example, type-test patterns. In addition to generalizing a switch statement, this feature suggests a further improvement by allowing a switch to be also used as an expression instead of a statement, making the above look even more readable:

String content = switch (msg) {

    case JsonMessage json      -> unmarshalJson(json);

    case XmlMessage xml        -> unmarshalXml(xml);

    case PlainTextMessage text -> text.getText();


    default                    -> msg.toString();

Another kind of a pattern that can be used for matching and that is further proposed in this JEP is a deconstruction pattern. It matches an expression against a certain type, and extracts variables based on the signature of an existing constructor in the matched type:

// Using a deconstruction pattern with nested type patterns
if (item matches Book(String isbn, int year)) {
    // do something with isbn and published

What’s really nice about this is that the component String isbn is itself a type-test pattern that matches the isbn property of the Book object against a String and extracts it into the isbn variable, and the same for the int year pattern. This means patterns may be nested within each other. Another example is nesting a constant pattern in a deconstruction pattern:

// Using a deconstruction pattern with a nested constant pattern
if (item matches Book("978-0321349606")) {

Finally, within a future work scope of this JEP are sealed types, which allow defining types whose subtypes can be limited by the programmer. With this feature, a type could be marked as “sealed” to mean that the subtypes are restricted to a known set. It is similar to a final type, but instead gives the programmer a way to restrict the hierarchy of child types. For example, in an bookstore application we may only need to handle certain items like books, DVDs, etc. In this case we could seal our BookstoreItem parent class. This can be very useful because it removes the burden of handling default cases whenever we are switching over the possible subtypes.

Data classes

A lot of times all that a class is responsible of is holding data. And with the current way to writing such classes, we usually end up writing too much boilerplate to customize methods based on the state of the object, e.g. by overriding equals(), hashCode(), toString(), etc. Furthermore, there is nothing in the language that just tells the compiler or the reader that this class is a simple data holder class.

In order to define such semantics, this feature seeks to introduce data classes of the following form (syntax and semantics still under discussion; for a comprehensive discussion see this document on the OpenJDK site):

__data class Point(int x, int y) { }

which would be translated into the following at compile time:

final class Point extends java.lang.DataClass {
    final int x;
    final int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;

    // destructuring pattern for Point(int x, int y)
    // state-based equals, hashCode, and toString
    // public read accessors for x and y

The general idea is to have the compiler generate all that boilerplate for the programmer (similar to what is done with enums), and have the programmer still able to override methods like equals() or implementing interfaces. At this point, some design decisions are in progress, for example it is undecided whether immutability will be enforced or if mutability could be allowed, which would definitely impact the implementation of this feature along with the thread-safety of such classes.


Project Amber aims to bring features that can make writing Java code more readable and concise, and target specific use cases such as using generic enums or data classes. Local variable type inference enables the programmer to defer thinking about the variable type to whenever it is initialized. Enhanced enums allows a more flexible approach to solving specific problems with less code. Lambda leftovers improves lambda support with a couple of small changes. Pattern matching provides a powerful construct to writing conditional logic and reduces boilerplate coding. And finally data classes allow the programmer to segregate plain data carriers from other classes. So far, only local variable type inference is planned in the Java 10 release, but with the accelerated release timeline of Java, the others can be rolled out as soon as they are ready.

3 thoughts on “What Is Project Amber in Java?

  1. chaotic3quilibrium (Jim O'Flaherty, Jr.)

    Fascinating! It appears Java is now stealing a number of awesome features Scala has had for +8 years! I’m very happy to see it.

  2. Pingback: Java Weekly Newsletter Issue 12 | Java Development Journal

  3. Pingback: Java Annotated Monthly – January 2018 | IntelliJ IDEA Blog

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s