JDK 12 Key Features — Switch Expressions and Shenandoah GC

Switch Expressions (Preview)

The most notable feature in Java 12 is Switch Expressions (JEP 325). The traditional switch statement had a longstanding issue where forgetting a break would cause fall-through. The new arrow (->) syntax fundamentally resolves this problem.

Think of it like a vending machine. The old switch was like a broken machine that dispensed every drink in a row when you pressed a button, while the new switch is a properly working machine that dispenses only the drink you selected.

public class SwitchExpressionDemo {
    public static void main(String[] args) {
        // === Traditional switch statement: risk of fall-through when break is missing ===
        String day = "MONDAY";
        int numLettersOld;
        switch (day) {
            case "MONDAY":
            case "FRIDAY":
            case "SUNDAY":
                numLettersOld = 6;
                break;
            case "TUESDAY":
                numLettersOld = 7;
                break;
            default:
                numLettersOld = -1;
        }

        // === New switch expression: arrow syntax, returns a value ===
        int numLettersNew = switch (day) {
            case "MONDAY", "FRIDAY", "SUNDAY" -> 6;   // Multiple cases with commas
            case "TUESDAY"                    -> 7;
            case "WEDNESDAY", "THURSDAY"      -> 8;
            case "SATURDAY"                   -> 8;
            default                           -> -1;
        };

        System.out.println("Old way: " + numLettersOld);
        System.out.println("New way: " + numLettersNew);
        // Output:
        // Old way: 6
        // New way: 6
    }
}

The arrow syntax has three advantages. First, no break is needed, eliminating fall-through bugs entirely. Second, multiple values can be listed with commas in a single case. Third, the switch itself becomes an expression that returns a value.

Note that in Java 12 this is a preview feature, so the --enable-preview flag is required at compile time.

javac --enable-preview --source 12 SwitchExpressionDemo.java
java --enable-preview SwitchExpressionDemo

Shenandoah GC — A New Option for Low-Latency GC

Shenandoah GC (JEP 189) is a low-latency garbage collector led by Red Hat. It aims for consistently short GC pause times regardless of heap size.

Using a highway construction analogy: if traditional GC is like shutting down all lanes for roadwork, Shenandoah is like doing construction alongside moving traffic.

GCPause Time CharacteristicsHeap Size Impact
G1 (default)Tens to hundreds of msIncreases with heap size
ShenandoahTarget under 10msIndependent of heap size
ZGCTarget under 10msIndependent of heap size

To enable Shenandoah, add JVM options:

java -XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC -jar your-app.jar

G1 GC Improvements

Java 12 brought two important improvements to the default G1 GC.

Promptly Return Unused Committed Memory (JEP 346): G1 returns Java heap memory to the OS during idle time. This is particularly effective for saving memory in container environments.

Abortable Mixed Collections (JEP 344): Mixed GC can now be aborted midway if it exceeds target pause time. This improves GC pause time predictability.

Compact Number Formatting

CompactNumberFormat displays numbers concisely — 1,000 as “1K”, 1,000,000 as “1M”, etc. This is useful for dashboards and statistics screens.

import java.text.NumberFormat;
import java.util.Locale;

public class CompactNumberDemo {
    public static void main(String[] args) {
        // Korean locale — short format
        NumberFormat shortKo = NumberFormat.getCompactNumberInstance(
            Locale.KOREA, NumberFormat.Style.SHORT
        );

        System.out.println("1,000 -> " + shortKo.format(1000));
        System.out.println("1,000,000 -> " + shortKo.format(1_000_000));
        System.out.println("1,000,000,000 -> " + shortKo.format(1_000_000_000));

        // English locale — long format
        NumberFormat longEn = NumberFormat.getCompactNumberInstance(
            Locale.US, NumberFormat.Style.LONG
        );
        System.out.println("2500 (EN LONG) -> " + longEn.format(2500));

        // Set fraction digits
        shortKo.setMaximumFractionDigits(1);
        System.out.println("15,750 -> " + shortKo.format(15_750));

        // Output:
        // 1,000 -> 1K (locale-dependent)
        // 1,000,000 -> 100M (locale-dependent)
        // 1,000,000,000 -> 1B (locale-dependent)
        // 2500 (EN LONG) -> 3 thousand
        // 15,750 -> 15.8K (locale-dependent)
    }
}

New String Methods — indent() and transform()

Java 12 added two convenience methods to the String class.

public class StringMethodsDemo {
    public static void main(String[] args) {
        // === indent(n): Add n spaces before each line (negative removes them) ===
        String text = "First line\nSecond line\nThird line";

        String indented = text.indent(4);
        System.out.println("With indentation:");
        System.out.println(indented);
        // Output:
        //     First line
        //     Second line
        //     Third line

        // === transform(): Apply a function to the string and return the result ===
        String result = "hello, java 12"
            .transform(String::toUpperCase)
            .transform(s -> s.replace(" ", "_"))
            .transform(s -> "[ " + s + " ]");

        System.out.println(result);
        // Output: [ HELLO,_JAVA_12 ]

        // transform is useful for method chaining
        // Transformations that previously needed intermediate variables can be chained like a pipeline
        int length = "Java 12 features"
            .transform(String::trim)
            .transform(String::length);

        System.out.println("String length: " + length);
        // Output: String length: 16
    }
}

transform() applies the functional pipeline pattern to String. It allows chaining transformations without intermediate variables, improving readability.

JMH Included by Default

Starting with Java 12, JMH (Java Microbenchmark Harness) is included in the JDK source tree (JEP 230). JMH is a tool that helps write accurate microbenchmarks by avoiding pitfalls like JVM warmup, JIT compilation, and dead code elimination.

Previously, JMH had to be added as a separate dependency, but from JDK 12 onward it was included by default to make it easier to track performance regressions in the JDK itself.

Summary

FeatureStatusKey Points
Switch ExpressionsPreviewArrow syntax, value return, fall-through prevention
Shenandoah GCExperimentalLow-latency GC independent of heap size
G1 GC ImprovementsFinalUnused memory returned to OS, Abortable Mixed GC
CompactNumberFormatFinalConcise number formatting like 1000 -> “1K”
String.indent()FinalPer-line indentation add/remove
String.transform()FinalFunctional pipeline chaining
JMH IncludedFinalBenchmark framework built into JDK source

Java 12 marks the beginning of switch syntax modernization and expands JVM performance tuning options. Switch Expressions become finalized in Java 14, and Shenandoah GC is promoted to production-ready in Java 15. Don’t dismiss preview features — learning them early means a smoother transition when upgrading versions.

Was this article helpful?