Skip to content

Java 16

Neelu edited this page Jul 26, 2026 · 1 revision

☕ Java 16: Records & Pattern Matching Go Standard

Release Date: March 16, 2021 | Type: Standard (non-LTS) | Status: ⚠️ End of Life


📋 Table of Contents

  1. Overview
  2. Core Features
  3. Best Practices

🎯 Overview

Java 16 graduates two of the most-loved previews — Records and Pattern Matching for instanceof — to standard features, and begins strongly encapsulating JDK internals by default, foreshadowing Java 17's stricter enforcement.

flowchart LR
    A["Java 14: Records Preview"] --> B["Java 15: Records 2nd Preview"] --> C["Java 16: Records STANDARD ✅"]
    D["Java 14: instanceof Preview"] --> E["Java 15: 2nd Preview"] --> F["Java 16: instanceof pattern matching STANDARD ✅"]
Loading

🚀 Core Features

1️⃣ Records (Standard)

// Production-ready, no --enable-preview flag needed
public record Employee(String name, String department, double salary) {

    // Compact constructor for validation
    public Employee {
        if (salary < 0) throw new IllegalArgumentException("Salary cannot be negative");
    }

    // Static factory method
    public static Employee of(String name, String department, double salary) {
        return new Employee(name, department, salary);
    }

    // Derived/computed method
    public double annualSalary() {
        return salary * 12;
    }
}

// Records work great with Streams
List<Employee> employees = List.of(
    new Employee("Alice", "Engineering", 9000),
    new Employee("Bob", "Sales", 7000)
);

Map<String, List<Employee>> byDept = employees.stream()
    .collect(Collectors.groupingBy(Employee::department));

Records implement interfaces too:

public interface Payable {
    double salary();
}

public record Employee(String name, double salary) implements Payable {}

2️⃣ Pattern Matching for instanceof (Standard)

public double calculateDiscount(Object customer) {
    if (customer instanceof PremiumCustomer p && p.yearsActive() > 5) {
        return 0.20;
    } else if (customer instanceof PremiumCustomer) {
        return 0.10;
    } else if (customer instanceof RegularCustomer r) {
        return r.hasCoupon() ? 0.05 : 0.0;
    }
    return 0.0;
}

Scope rules — the pattern variable is only in scope where the compiler can prove the type check succeeded:

// Negated instanceof — variable is in scope AFTER the if, if it returns/throws
if (!(obj instanceof String s)) {
    return;
}
System.out.println(s.length());  // ✅ s is in scope here — compiler proved obj is a String

3️⃣ Vector API (Incubator)

What: A new API to express vector computations that reliably compile to optimal SIMD instructions on supported CPUs — huge for numerical/ML workloads.

import jdk.incubator.vector.*;

static void vectorAdd(float[] a, float[] b, float[] result) {
    var species = FloatVector.SPECIES_PREFERRED;
    int i = 0;
    int upperBound = species.loopBound(a.length);

    for (; i < upperBound; i += species.length()) {
        var va = FloatVector.fromArray(species, a, i);
        var vb = FloatVector.fromArray(species, b, i);
        va.add(vb).intoArray(result, i);
    }
    // scalar tail loop for remaining elements
    for (; i < a.length; i++) {
        result[i] = a[i] + b[i];
    }
}
flowchart LR
    A["Scalar loop\none add per cycle"] -->|Vector API| B["SIMD loop\n4-8+ adds per cycle"]
    B --> C["2-8x speedup\non numeric workloads"]
Loading

4️⃣ Strongly Encapsulate JDK Internals by Default

What: --illegal-access=permit (the old default that let reflective access to internal JDK APIs slide with a warning) is now deny by default.

# Java 16+: internal API access now fails by default
java --illegal-access=permit ...  # deprecated option, has no effect

# You must explicitly open modules if truly needed
java --add-opens java.base/java.lang=ALL-UNNAMED -jar app.jar

Why it matters: This is the direct predecessor to Java 17's full enforcement. If your app or a library dependency reflects into java.lang, java.util, etc., test on Java 16 now before you're forced to on 17.


5️⃣ Unix-Domain Socket Channels

var address = UnixDomainSocketAddress.of("/tmp/mysocket.sock");
try (var serverChannel = ServerSocketChannel.open(StandardProtocolFamily.UNIX)) {
    serverChannel.bind(address);
    // Faster local IPC than TCP loopback — no network stack overhead
}

💡 Best Practices

✅ Migrate DTOs/value objects to record now that it's standard — no preview flag risk
✅ Run your test suite on Java 16 with default settings to catch illegal-access issues before Java 17
✅ Consider the Vector API for hot numeric loops (image processing, ML preprocessing, physics) — but benchmark, it's still incubating
❌ Don't reach for Unix domain sockets unless you specifically need local-only IPC — TCP loopback is simpler and portable


Clone this wiki locally