Java 12 to 17 Features

Naveen AnamaneniUpdated · 5 min read

New school rules are often tried for a term before they become permanent, so teachers and students can point out problems first. From Java 12 onwards, Java started doing the same with language features: a new feature first appears as a preview, gets feedback for a release or two, and only then becomes final. Between Java 12 (March 2019) and Java 17 (September 2021, LTS), this process delivered the biggest language changes since Java 8: switch expressions, text blocks, records, pattern matching and sealed classes. In this lesson, you will get a quick reference to each release, see a tour program, and learn when each feature became final.

When Each Feature Became Final

FeaturePreview inFinal inRead more
Switch expressions (case X ->, yield)12, 1314"Switch Expressions"
Text blocks (""")13, 1415"Text Blocks in Java"
Pattern matching for instanceof14, 1516"Java Instanceof and Pattern Matching"
Records14, 1516"Records in Java"
Sealed classes and interfaces15, 1617"Sealed Classes and Interfaces in Java"

Other Changes by Release

ReleaseNotable additionsRead more
Java 12 (Mar 2019)String.indent and transform, Collectors.teeing, compact number formatting ("1K")"Modern String Methods in Java"
Java 13 (Sep 2019)Previews only (text blocks, yield)
Java 14 (Mar 2020)Helpful NullPointerException messages that name what was null; JFR event streaming"Helpful NullPointerException Messages"
Java 15 (Sep 2020)String.formatted, hidden classes, ZGC ready for production, Nashorn JavaScript engine removed
Java 16 (Mar 2021)Stream.toList(), jpackage for native installers, JDK internals strongly encapsulated by default"Jpackage in Java: Native Installers"
Java 17 (Sep 2021, LTS)Sealed classes final, new RandomGenerator API, Applet API and Security Manager deprecated for removal, macOS on Apple Silicon

A Java 12 to 17 Tour

Let's see a program that uses all five headline features together.

Tour17.java▶ Try it
import java.util.List;
 
public class Tour17 {
    sealed interface Fee permits Tuition, Transport {}           // 17
    record Tuition(String student, int amount) implements Fee {} // 16
    record Transport(String student, int km) implements Fee {}
 
    static int amountOf(Fee fee) {
        if (fee instanceof Tuition t) {                          // 16
            return t.amount();
        } else if (fee instanceof Transport tr) {
            return 800 + tr.km() * 20;
        }
        throw new IllegalArgumentException();
    }
 
    static String band(int amount) {
        return switch (amount / 5000) {                          // 14
            case 0 -> "low";
            case 1, 2 -> "medium";
            default -> "high";
        };
    }
 
    public static void main(String[] args) {
        List<Fee> fees = List.of(
                new Tuition("Navika", 15000),
                new Transport("Srihitha", 12),
                new Tuition("Advitha", 9000));
 
        for (Fee f : fees) {
            int amount = amountOf(f);
            System.out.println(f + " -> " + amount + " (" + band(amount) + ")");
        }
 
        String receipt = """
                RECEIPT
                  Student: %s
                  Amount : %d
                """.formatted("Navika", 15000);                   // 15
        System.out.print(receipt);
 
        List<String> names = fees.stream()
                .map(f -> f.getClass().getSimpleName())
                .toList();                                       // 16
        System.out.println(names);
    }
}
Output
Tuition[student=Navika, amount=15000] -> 15000 (high)
Transport[student=Srihitha, km=12] -> 1040 (low)
Tuition[student=Advitha, amount=9000] -> 9000 (medium)
RECEIPT
  Student: Navika
  Amount : 15000
[Tuition, Transport, Tuition]

Explanation:

  1. sealed interface Fee permits ... lists every allowed implementation, so the compiler knows all the cases.
  2. Records give Tuition and Transport a constructor, accessors, equals, hashCode and the readable toString seen in the output, in one line each.
  3. instanceof Tuition t tests and casts in one step.
  4. The switch expression returns a value, with case 1, 2 -> and no break.
  5. The text block keeps the receipt's layout, and formatted fills in the values.
  6. Stream.toList() is a shorter collect(Collectors.toList()), and the list it returns is unmodifiable.

Practice

  1. Easy: Rewrite this old switch as a switch expression: String r; switch (g) { case 'A': r = "Excellent"; break; case 'B': r = "Good"; break; default: r = "Keep going"; }
  2. Harder: Add a third record Hostel(String student, int months) to Tour17, and add it to the permits list. Compile without changing amountOf and read what happens at runtime; then fix it.

Quick check: Java 12 to 17

4 questions
1In which release did records become final?
2What does a sealed interface do?
3Which Java 14 feature names the exact null part in an NPE message?
4Which release in this range is LTS?

Key Takeaways

Was this tutorial helpful?

Lesson 563 of 578