Exploring Project Amber’s Key Enhancements: From Java 12 to Java 22

Neha Sardana
7 min readMar 16, 2024

Project Amber is an initiative by the OpenJDK community aimed at exploring and incubating smaller, productivity-oriented Java language features. The purpose of Project Amber is to enhance the Java language with more concise syntax, reduce boilerplate code, and improve the readability and writability of Java code, thus making Java more efficient and pleasant for developers to use.

From Java 12 to 22, Project Amber has significantly evolved the Java language, introducing streamlined syntax and innovative features to enhance developer productivity and modernize Java programming practices.

Project Amber Main Features

Switch Expressions (Preview Feature in Java 12, Final Feature in Java 14)

Switch expressions enhance the traditional switch statement, allowing it to be used as either a statement or an expression. This feature simplifies coding and improves readability, especially when handling multiple outcomes in a more functional style. It also eliminates the need to have a break statements for each switch case.

 String color = "RED";
String fruit = switch (color)
{
case "RED" -> "Apple";
case "YELLOW" -> "Banana";
case "ORANGE" -> "Orange";
default -> "Unknown fruit";
};
System.out.println("Color: " + color + ",Fruit: " +fruit);
int month = 4; // April
String quarter = switch (month) {
case 1, 2, 3 -> "Q1";
case 4, 5, 6 -> {
// Complex logic or calculations can be placed here
yield "Q2";
}
case 7, 8, 9 -> "Q3";
case 10, 11, 12 -> "Q4";
default -> {
System.out.println("Invalid month: " + month);
yield "Unknown";
}
};

System.out.println("Month " + month + " falls in " + quarter + ".");

Text Blocks (Preview Feature in Java 13, Final Feature in Java 15)

A Text Block is delimited by three double quotes (""") at the start and end, and any text contained within these delimiters is considered part of the string, including newlines and any leading whitespace common to all lines of the string (with the exception of the first line if it’s right after the opening delimiter). Text Blocks simplify the declaration of multiline strings, making it easier to work with JSON, XML, or SQL directly in Java code.

String json = """
{
"name": "Java",
"type": "Programming Language"
}
""";
System.out.println(json);

String Templates (Preview Feature Java 21 and Java 22)

The concept of String Templates usually refers to a way of embedding expressions inside string literals, which then get evaluated and interpolated into a resulting string. String templates enhance Java’s current string literals and text blocks by integrating literal text with embedded expressions and template processors, enabling the generation of specialized outcomes.

String name = "World";
String greeting = STR."Hello, \{name}!";

System.out.println(greeting);

Sequenced Collections (Java 21)

Sequenced Collections are proposed additions to the Java Collections Framework that provide a well-defined order for their elements, from the first to the last. These collections offer unified APIs for accessing endpoint elements and support operations for reverse iteration. This concept addresses the need for a consistent approach to handle ordered elements across different types of collections.

interface SequencedCollection<E> extends Collection<E> {
SequencedCollection<E> reversed();
void addFirst(E);
void addLast(E);
E getFirst();
E getLast();
E removeFirst();
E removeLast();
}

Records (Preview Feature in Java 14 , Final Feature in Java 16)

Records provide a succinct syntax to declare classes that are transparent holders for shallowly immutable data. They are ideal for “data carrier” classes, reducing boilerplate code while ensuring a concise representation of data in applications.

record User(String name, int age) {}

User user = new User("Neha", 37);
System.out.println(user);

Sealed Class (Preview Feature in Java 15, Final Feature in Java 17)

Sealed classes and interfaces restrict which other classes or interfaces may extend or implement them. This feature is a boon for domain modeling, where a strict control over the allowed subtypes is necessary.

public sealed class Shape permits Circle, Square {
// Class body
}

final class Circle extends Shape {}
final class Square extends Shape {}

Pattern Matching for instanceof (Preview Feature in Java 14, Final Feature in Java 16)

This feature enhances the instanceof operator to allow pattern matching, which eliminates the need for explicit casting after an instanceof check, leading to cleaner, more readable code.

Object obj = "Hello, Java!";
if (obj instanceof String s) {
System.out.println(s.toUpperCase());
}

Pattern Matching for switch (Preview Feature in Java 17, Final Feature in Java 19)

Extending pattern matching to switch expressions/statements allows developers to conditionally execute blocks of code not just based on the value of an expression, but also on its type, shape, or other properties.

public String formatterPatternSwitch(Object o){
return switch (o){
case Integer i -> String.format("int %d", i);
case Long l -> String.format("long %d", l);
case Double d -> String.format("double %f", d);
case String s -> String.format("String %s",s);
default -> o.toString();
};
}

Record Pattern (Preview Feature in Java 19, Final Feature in Java 21)

Building on records and pattern matching, record patterns would enable deconstructing records in pattern matching expressions, further simplifying data processing.

public record Department(String name, List<Employee> employees) {}
public record Employee(String name, String position) {}

public void main() {
List<Department> departments = List.of(
new Department("IT", List.of(new Employee("Alice", "Developer"),
new Employee("Bob", "Architect"))),
new Department("HR", List.of(new Employee("Charlie", "Recruiter")))
);

departments.stream()
.flatMap(dept -> dept.employees().stream())
.filter(emp -> emp instanceof Employee(String name, String position) && position.equals("Developer"))
.forEach(emp -> System.out.println(emp.name()));
}

Unnamed classes and instance main methods (Preview in Java 21 and Java 22)

Java has been infamously named as verbose because of the long statements for main method. With the introduction of unnamed class and instance main methods allow you to bootstrap the class with minimum syntax. This feature is introduced to reduce the verbosity and make it easier to get started and learn.

  • A class declaration isn’t necessary.
  • The use of the Java keywords public and static is optional.
  • The main method's args arguments are also optional.
  • It’s possible to include multiple main methods.

In case of multiple main methods, the compiler follows the following sequence:

  • A static void main(String[] args) method
  • A static void main() method without any arguments
  • A void main(String[] args) instance method without static keyword
  • A void main() instance method without static keyword and arguments
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
//unnamed class
void main() {
System.out.println("Hello, World!");
}

Unnamed variables and patterns (Preview in Java 21, Final Feature in Java 22)

Unnamed Patterns

The introduction of Record Patterns brought about a new challenge: when deconstructing a record, it’s not always necessary to use all variables from the record. To preserve code readability, Java architects introduced the use of the underscore (_) keyword. This allows developers to substitute unused variables in record patterns with _, making the code more concise.

Object obj = // some object;
switch (obj) {
case String _, Integer _ -> System.out.println("It's either a String or Integer.");
case List _, Set _ -> System.out.println("It's a List or a Set.");
default -> System.out.println("It's something else.");
}

Unnamed Variables

Developers occasionally create variables they don’t plan to utilize, either due to coding style preferences or language requirements that necessitate variable declarations in specific situations. While the intention to not use these variables is clear at the time of writing, this intent isn’t always explicitly documented, leading to the risk that future maintainers might mistakenly use these variables, thus contravening the original intent. By ensuring these variables cannot be used accidentally, code becomes more informative, readable, and less susceptible to mistakes.

Scenarios where you need to use unnamed variables are:

  • A local variable declaration statement in a block.
  • A resource specification of a 'try-with-resources' statement.
  • The header of a basic 'for' statement.
  • The header of an 'enhanced for loop'.
  • An exception parameter of a 'catch' block.
  • A formal parameter of a lambda expression.
//Enhanced for Loop with Unnamed Variable
for (Order _ : orders) {
// Loop body where the order is not needed.
}
//Try-with-Resources with Unnamed Variable
try (var _ = new AutoCloseableResource()) {
// Code block where the resource is used but not directly referenced.
}
//Catch Block with Unnamed Exception Variable
try {
// Code that might throw an exception.
} catch (SomeSpecificException _) {
// Handle an exception without using the exception object.
}
//Lambda Expression with Unnamed Parameter
Stream.of("One", "Two", "Three")
.forEach(_ -> System.out.println("Element processed."));

Future Directions (Java 23)

Even though we have just got Java 22, there are JEPs already in progress for Java 23 to be targeted later this year.

Here are few helpful links for further reading. Happy Reading!

--

--