DEV Community

Cover image for Java 17 Features Every Senior Developer Should Know - Part 6: Complete Reference Guide & Syntax Cheat Sheet
9mac-dev
9mac-dev

Posted on

Java 17 Features Every Senior Developer Should Know - Part 6: Complete Reference Guide & Syntax Cheat Sheet

Part 6 of 6 | The final installment in our comprehensive Java 17 features series

This is Part 6 (final) of "Java 17 Features Every Senior Developer Should Know" - your complete desktop reference for all 6 modern Java features spanning Java 10-17. This guide consolidates everything from Parts 1-5, providing syntax cards, decision matrices, and real-world patterns you can reference while working with contemporary Java code.


Complete Series Overview

We've covered 6 major Java features that fundamentally changed how developers write clean, maintainable code:

Part Feature Release Purpose
1 var - Type Inference Java 10 Eliminate verbose type declarations
2 Records - Immutable Data Java 16 Replace boilerplate data classes
3 Sealed Classes - Hierarchy Control Java 17 Enforce closed type systems
4 Pattern Matching - Type Safety Java 16 Atomic type checking + binding
5 Switch Expressions - Value Returns Java 14 Modern conditional logic
6 Text Blocks - Multi-line Strings Java 15 No escape sequence hell

Why This Matters

Modern Java (10-17) brought three transformative principles:

1. Less Boilerplate

Before Java 16, creating a simple data class meant 30+ lines of repetitive code. Today:

// Java 8
public final class Point {
    private final int x;
    private final int y;

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

    public int getX() { return x; }
    public int getY() { return y; }

    @Override
    public boolean equals(Object obj) { /* ... */ }

    @Override
    public int hashCode() { /* ... */ }

    @Override
    public String toString() { /* ... */ }
}

// Java 17
record Point(int x, int y) {}
Enter fullscreen mode Exit fullscreen mode

2. More Safety

The compiler now catches entire categories of errors at compile-time:

// Sealed classes + pattern matching = exhaustive checking
sealed interface Result permits Success, Failure {}

String msg = switch (result) {
    case Success(var val) -> "Got: " + val;
    case Failure(var err) -> "Error: " + err;
    // Compiler ensures all cases covered!
};
Enter fullscreen mode Exit fullscreen mode

3. Better Readability

Code now expresses intent directly:

// Multi-line strings without escape sequences
var query = """
    SELECT u.name, u.email, COUNT(o.id) as order_count
    FROM users u
    LEFT JOIN orders o ON u.id = o.user_id
    WHERE o.status = 'COMPLETED'
    GROUP BY u.name, u.email
    ORDER BY order_count DESC""";
Enter fullscreen mode Exit fullscreen mode

Quick Reference: All 6 Features at a Glance

var Keyword (Java 10) - Type Inference

When: Type is obvious from the right side

var message = "Hello";                          // Clear ✅
var count = 42;                                 // Clear ✅
var users = List.of("Alice", "Bob");           // Clear ✅
var map = new HashMap<String, Integer>();      // Clear ✅
var data = fetchData();                         // Unclear ❌
Enter fullscreen mode Exit fullscreen mode

Key insight: Compile-time inference only - not dynamic typing. Local variables only.


Records (Java 16) - Immutable Data Carriers

When: Creating immutable data structures (DTOs, value objects, configuration)

// Basic record
record Person(String name, int age) {}

// With validation
record Range(int start, int end) {
    public Range {
        if (start > end) {
            throw new IllegalArgumentException("Invalid range");
        }
    }
}

// With methods
record User(String email, int age) {
    public User {
        email = email.toLowerCase(); // Normalization
    }

    public boolean isAdult() {
        return age >= 18;
    }

    public User withAge(int newAge) {
        return new User(email, newAge);
    }
}

// Generic records
record Pair<T, U>(T first, U second) {
    public Pair<U, T> swap() {
        return new Pair<>(second, first);
    }
}

// Implementing interfaces
sealed interface Shape permits Circle, Rectangle {}

record Circle(double radius) implements Shape {
    public double area() { return Math.PI * radius * radius; }
}

record Rectangle(double width, double height) implements Shape {
    public double area() { return width * height; }
}
Enter fullscreen mode Exit fullscreen mode

Automatic: constructor, accessors (x() not getX()), equals(), hashCode(), toString()

Key insight: Immutable by default. One line replaces 30+ lines of boilerplate.


Sealed Classes (Java 17) - Controlled Inheritance

When: You need a closed type hierarchy with known subtypes

// Fixed alternatives
sealed interface Payment permits CreditCard, PayPal, BankTransfer {}

record CreditCard(String number, String expiry) implements Payment {}
record PayPal(String email) implements Payment {}
record BankTransfer(String accountNumber) implements Payment {}

// With extension point
sealed interface PaymentExtension permits CreditCard, PayPal, CustomPayment {}
non-sealed interface CustomPayment extends PaymentExtension {} // Open for extension

// Class hierarchy
sealed class Result<T> permits Success, Failure {}

final class Success<T> extends Result<T> {
    private final T value;

    public Success(T value) { this.value = value; }
    public T getValue() { return value; }
}

final class Failure<T> extends Result<T> {
    private final String error;

    public Failure(String error) { this.error = error; }
    public String getError() { return error; }
}
Enter fullscreen mode Exit fullscreen mode

Subtypes must be: final, sealed, or non-sealed

Key insight: Compiler knows all subtypes - enables exhaustive pattern matching without default cases.


Pattern Matching (Java 16) - Type Check + Cast

When: Type checking with immediate variable use

// Basic pattern matching
if (obj instanceof String s) {
    System.out.println(s.toUpperCase());
}

// With guards
if (obj instanceof String s && s.length() > 0) {
    return s.toUpperCase();
}

// Simplified equals()
@Override
public boolean equals(Object obj) {
    return obj instanceof Point p
        && this.x == p.x
        && this.y == p.y;
}

// Polymorphic dispatch
if (shape instanceof Circle c) {
    return Math.PI * c.radius() * c.radius();
} else if (shape instanceof Rectangle r) {
    return r.width() * r.height();
}

// Type hierarchies with pattern matching in switch
double area = switch (shape) {
    case Circle(var r) -> Math.PI * r * r;
    case Rectangle(var w, var h) -> w * h;
    case Triangle(var b, var h) -> (b * h) / 2.0;
};
Enter fullscreen mode Exit fullscreen mode

Scope rules: Variables are scoped by flow analysis:

if (obj instanceof String s && s.length() > 5) {
    // 's' in scope here
}

if (!(obj instanceof String s)) {
    // 's' NOT in scope
} else {
    // 's' IS in scope here
}
Enter fullscreen mode Exit fullscreen mode

Key insight: Eliminates manual casting. Combines type check + cast + assignment in one atomic operation.


Switch Expressions (Java 14) - Value-Returning Switch

When: Returning values from multiple conditional branches

// Basic switch expression
String dayType = switch (day) {
    case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> "Weekday";
    case SATURDAY, SUNDAY -> "Weekend";
};

// With yield for multi-statement
int result = switch (operation) {
    case ADD -> {
        System.out.println("Adding numbers");
        yield a + b;
    }
    case SUBTRACT -> {
        System.out.println("Subtracting");
        yield a - b;
    }
    case MULTIPLY -> a * b;
    case DIVIDE -> a / b;
    default -> throw new IllegalArgumentException("Unknown operation");
};

// Exhaustive enum matching (no default needed)
int monthDays = switch (month) {
    case JANUARY, MARCH, MAY, JULY, AUGUST, OCTOBER, DECEMBER -> 31;
    case APRIL, JUNE, SEPTEMBER, NOVEMBER -> 30;
    case FEBRUARY -> 28; // Ignoring leap years for simplicity
};

// Combined with sealed types for exhaustiveness
String message = switch (result) {
    case Success(var value) -> "Success: " + value;
    case Failure(var error) -> "Error: " + error;
    // No default needed - compiler verifies all cases!
};
Enter fullscreen mode Exit fullscreen mode

Arrow syntax: -> prevents fall-through (unlike traditional :)

Rules:

  • Must be exhaustive (all cases covered or default)
  • Use yield for multi-statement branches
  • No break needed with arrows

Key insight: Cleaner than if-else chains. Compiler enforces completeness.


Text Blocks (Java 15) - Multi-line Strings

When: Multi-line strings without escape sequence hell

// JSON without escaping quotes
String json = """
    {
      "name": "Alice Johnson",
      "email": "alice@example.com",
      "age": 30,
      "active": true
    }""";

// SQL queries with proper formatting
String sqlQuery = """
    SELECT u.id, u.name, COUNT(o.id) as order_count
    FROM users u
    LEFT JOIN orders o ON u.id = o.user_id
    WHERE u.status = 'ACTIVE'
      AND o.created_at > NOW() - INTERVAL 30 DAY
    GROUP BY u.id, u.name
    HAVING COUNT(o.id) > 5
    ORDER BY order_count DESC""";

// HTML without escaping
String html = """
    <div class="container">
      <h1>Welcome, <span class="user">%s</span></h1>
      <p>Your account balance: <strong>$%.2f</strong></p>
      <a href="/dashboard">View Dashboard</a>
    </div>""".formatted(userName, balance);

// With indentation control
String formatted = """
    Level 1
        Level 2 (indented)
            Level 3 (more indented)
        Back to level 2
    Back to level 1
    """;

// Line continuation (backslash)
String poem = """
    Roses are red, \\
    Violets are blue, \\
    Java 17 features \\
    Make code shine through.""";

// Essential space (survives whitespace stripping)
String codeBlock = """
    function hello() {
        console.log("Hello!");
    }\s
    """;

// XML configuration
String config = """
    <?xml version="1.0" encoding="UTF-8"?>
    <configuration>
      <database>
        <host>localhost</host>
        <port>5432</port>
        <name>myapp</name>
      </database>
      <logging>
        <level>INFO</level>
      </logging>
    </configuration>""";
Enter fullscreen mode Exit fullscreen mode

Special escapes:

  • \s - Essential space (survives whitespace stripping)
  • \ - Line continuation (no newline inserted)
  • Standard escapes: \n, \t, \", \\

Key insight: All line endings normalized to \n. Opening """ must be followed by newline. Closing """ position determines indent stripping.


Real-World Integration Patterns

Pattern 1: Records + var for Clean Data

var users = List.of(
    new User("alice@test.com", 30),
    new User("bob@test.com", 25),
    new User("charlie@test.com", 35)
);

// Process with var inference
for (var user : users) {
    if (user.age() >= 18) {
        processAdult(user);
    }
}

// Stream operations
var activeAdults = users.stream()
    .filter(u -> u.age() >= 18)
    .map(u -> u.email())
    .toList();
Enter fullscreen mode Exit fullscreen mode

Pattern 2: Sealed Classes + Pattern Matching for Type Safety

sealed interface ApiResponse<T> permits SuccessResponse, ErrorResponse {}

record SuccessResponse<T>(int status, T data) implements ApiResponse<T> {}
record ErrorResponse(int status, String message, String code) implements ApiResponse<Object> {}

// Exhaustive pattern matching guaranteed by compiler
public static <T> void handleResponse(ApiResponse<T> response) {
    switch (response) {
        case SuccessResponse(var status, var data) -> {
            System.out.println("Success: " + data);
        }
        case ErrorResponse(var status, var msg, var code) -> {
            System.err.println("Error [" + code + "]: " + msg);
        }
        // No default needed - compiler verifies all cases!
    }
}
Enter fullscreen mode Exit fullscreen mode

Pattern 3: Text Blocks + var for Template Building

var userName = "Alice";
var orderTotal = 149.99;
var orderId = "ORD-2025-001";

var emailTemplate = """
    <html>
      <body>
        <h2>Order Confirmation</h2>
        <p>Hello %s,</p>
        <p>Your order #%s has been confirmed.</p>
        <p><strong>Total: $%.2f</strong></p>
        <p>Thank you for your business!</p>
      </body>
    </html>""".formatted(userName, orderId, orderTotal);

var sqlTemplate = """
    INSERT INTO orders (user_id, total, status, created_at)
    VALUES (?, ?, 'PENDING', NOW())
    RETURNING id, created_at""";
Enter fullscreen mode Exit fullscreen mode

Pattern 4: Complete Domain Model (All Features Combined)

// 1. Sealed hierarchy for domain
sealed interface OrderStatus permits Pending, Processing, Shipped, Delivered, Cancelled {}

record Pending() implements OrderStatus {}
record Processing() implements OrderStatus {}
record Shipped(String trackingNumber) implements OrderStatus {}
record Delivered(LocalDateTime deliveredAt) implements OrderStatus {}
record Cancelled(String reason) implements OrderStatus {}

// 2. Records for data
record OrderItem(String productId, int quantity, BigDecimal price) {}

record Order(
    String id,
    String customerId,
    List<OrderItem> items,
    OrderStatus status,
    LocalDateTime createdAt
) {
    public Order {
        items = List.copyOf(items); // Defensive copy
    }

    public BigDecimal total() {
        return items.stream()
            .map(item -> item.price().multiply(BigDecimal.valueOf(item.quantity())))
            .reduce(BigDecimal.ZERO, BigDecimal::add);
    }
}

// 3. Switch expressions with pattern matching
public static String getStatusMessage(Order order) {
    return switch (order.status()) {
        case Pending() -> "Your order is pending processing";
        case Processing() -> "We're preparing your order";
        case Shipped(var tracking) -> "Shipped! Track: " + tracking;
        case Delivered(var date) -> "Delivered on " + date.format(DateTimeFormatter.ISO_LOCAL_DATE);
        case Cancelled(var reason) -> "Order cancelled: " + reason;
    };
}

// 4. var + text blocks for output
public static void printOrderSummary(Order order) {
    var items = order.items();
    var total = order.total();
    var status = getStatusMessage(order);

    var summary = """
        ==== ORDER SUMMARY ====
        Order ID: %s
        Customer: %s
        Status: %s

        Items (%d):
        %s

        Total: $%.2f

        Created: %s
        """.formatted(
            order.id(),
            order.customerId(),
            status,
            items.size(),
            items.stream()
                .map(item -> "  • %s (qty: %d) @ $%.2f each".formatted(
                    item.productId(), item.quantity(), item.price()))
                .collect(Collectors.joining("\n")),
            total,
            order.createdAt().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
        );

    System.out.println(summary);
}

// 5. Usage showing all features together
var order = new Order(
    "ORD-2025-001",
    "CUST-123",
    List.of(
        new OrderItem("PROD-A", 2, new BigDecimal("29.99")),
        new OrderItem("PROD-B", 1, new BigDecimal("49.99"))
    ),
    new Shipped("UPS-123456"),
    LocalDateTime.now().minusDays(2)
);

printOrderSummary(order);
Enter fullscreen mode Exit fullscreen mode

When to Use Each Feature

Use var When:

  • Type is obvious from right side: var user = new User("Alice", 30)
  • Long generic types: var map = new HashMap<String, List<Setting>>()
  • Stream chains: var filtered = list.stream().filter(...).map(...)

Use Records When:

  • Creating data transfer objects (DTOs)
  • Modeling immutable value objects
  • Building configuration objects
  • Returning multiple values from methods

Use Sealed Classes When:

  • You have a fixed set of subtypes
  • Building domain models with known alternatives
  • Want exhaustive pattern matching without defaults
  • Controlling API boundaries

Use Pattern Matching When:

  • Type checking with immediate variable use
  • Simplifying equals() implementations
  • Reducing null-check boilerplate
  • Guard conditions: obj instanceof String s && s.length() > 5

Use Switch Expressions When:

  • Mapping enum values to results
  • Returning values from branches (not just statements)
  • Exhaustive enum/sealed type matching
  • Complex conditional logic (cleaner than if-else chains)

Use Text Blocks When:

  • Multi-line JSON, SQL, HTML, or XML
  • Embedded code snippets in strings
  • Long error messages
  • Test fixtures and templates

Decision Matrix

Need Feature Why
Reduce type declarations var Type inference eliminates noise
Replace boilerplate classes Records Automatic equals(), hashCode(), toString()
Enforce type hierarchy Sealed Compiler knows all subtypes
Type-safe instanceof Pattern Matching One-line type check + cast + bind
Conditional return values Switch Expressions Cleaner than if-else chains
Multi-line strings Text Blocks No escape sequence nightmares

Key Insights

  1. These features work together: Records + sealed classes enable exhaustive pattern matching. var + records = clean data structures. Text blocks + var = template building.

  2. Backward compatible: Existing Java 8 code still works. Adopt features gradually.

  3. Compiler enforcement: Sealed classes and pattern matching catch entire categories of runtime errors at compile-time.

  4. Less ceremony: Together, these features reduce boilerplate by 50-70% compared to Java 8.

  5. Modern idioms: Understanding these features is essential for reading contemporary Java code.


Full Articles & Examples

For detailed coverage, advanced patterns, and comprehensive examples:

👉 Full Part 6 article: blog.9mac.dev/java-17-features-every-senior-developer-should-know-part-6-syntax-cheat-sheet

Complete Series


Run the Examples

All code examples are available in the repository with full test coverage:

git clone https://github.com/dawid-swist/blog-9mac-dev-code.git
cd blog-post-examples/java/2025-10-25-java17-features-every-senior-developer-should-know
../../gradlew test
Enter fullscreen mode Exit fullscreen mode

Run tests for specific features:

./gradlew test --tests "*VarExample*"
./gradlew test --tests "*RecordExample*"
./gradlew test --tests "*SealedExample*"
./gradlew test --tests "*PatternExample*"
./gradlew test --tests "*SwitchExample*"
./gradlew test --tests "*TextBlockExample*"
Enter fullscreen mode Exit fullscreen mode

Series Statistics

  • Total code examples: 40+ real-world patterns
  • Test coverage: 100% with BDD-style test names
  • Reading time: 1-2 hours for complete series
  • Java versions covered: 10, 14, 15, 16, 17
  • Features enabled: 6 major language enhancements
  • Lines of boilerplate eliminated: 50-70%

This concludes the "Java 17 Features Every Senior Developer Should Know" series.

All 6 parts are now published with comprehensive examples, best practices, and real-world patterns you can use immediately in production code.

Top comments (1)

Collapse
 
fred_functional profile image
Fred Functional

This guide really sealed the deal on modern Java—now my code can record wins and switch to cleaner patterns without throwing a fit!