Skip to content
Published on

Modern Java Complete Syntax Guide: Java 17/21 Edition

Authors

Modern Java Complete Syntax Guide: Java 17/21 Edition

Java continues to evolve rapidly. Java 21 LTS includes revolutionary features like Project Loom (Virtual Threads), Pattern Matching, Records, and Sealed Classes. This guide gives you a complete mastery of the modern Java syntax you must know for professional development.


1. Java 21 Core New Features (LTS)

1.1 Record Classes (Java 16+)

Records provide a concise way to declare immutable data classes. The compiler automatically generates the constructor, getters, equals, hashCode, and toString.

// Old approach (full of boilerplate)
public class PersonOld {
    private final String name;
    private final int age;

    public PersonOld(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() { return name; }
    public int getAge() { return age; }

    @Override public boolean equals(Object o) { /* ... */ }
    @Override public int hashCode() { /* ... */ }
    @Override public String toString() { /* ... */ }
}

// Record approach (one line!)
public record Person(String name, int age) {}

// Usage
Person p = new Person("Alice", 30);
System.out.println(p.name()); // "Alice"  (getter names match field names)
System.out.println(p);        // Person[name=Alice, age=30]

// Custom constructor for validation
public record Person(String name, int age) {
    public Person {
        if (name == null || name.isBlank()) throw new IllegalArgumentException("name cannot be blank");
        if (age < 0 || age > 150) throw new IllegalArgumentException("invalid age: " + age);
    }

    // Additional methods can be defined
    public String greeting() {
        return "Hello, I'm " + name + " and I'm " + age + " years old.";
    }
}

// Records can implement interfaces
public record Point(double x, double y) implements Comparable<Point> {
    @Override
    public int compareTo(Point other) {
        return Double.compare(this.distance(), other.distance());
    }

    private double distance() {
        return Math.sqrt(x * x + y * y);
    }
}

Record constraints:

  • Cannot be extended (implicitly final)
  • All fields are private final
  • Cannot add instance fields (static fields are allowed)

1.2 Sealed Classes (Java 17+)

Sealed classes explicitly restrict which classes or interfaces can inherit from or implement a given type, enabling closed type hierarchies.

// Shape can only be extended by Circle, Rectangle, Triangle
public sealed class Shape permits Circle, Rectangle, Triangle {}

public final class Circle extends Shape {
    private final double radius;
    public Circle(double radius) { this.radius = radius; }
    public double radius() { return radius; }
}

public final class Rectangle extends Shape {
    private final double width, height;
    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }
    public double width() { return width; }
    public double height() { return height; }
}

public non-sealed class Triangle extends Shape {
    // non-sealed: classes extending Triangle are unrestricted
    private final double base, height;
    public Triangle(double base, double height) {
        this.base = base;
        this.height = height;
    }
}

// Sealed interfaces work too
public sealed interface Result<T> permits Result.Success, Result.Failure {
    record Success<T>(T value) implements Result<T> {}
    record Failure<T>(String error) implements Result<T> {}
}

1.3 Pattern Matching for instanceof (Java 16+)

No more explicit casting after instanceof checks.

// Old approach
if (obj instanceof String) {
    String s = (String) obj;
    System.out.println(s.length());
}

// Modern approach (Pattern Matching)
if (obj instanceof String s) {
    System.out.println(s.length()); // s is automatically cast to String
}

// Combined with conditions
if (obj instanceof String s && s.length() > 5) {
    System.out.println("Long string: " + s);
}

// Negation pattern
if (!(obj instanceof String s)) {
    System.out.println("Not a string");
    return;
}
// From here s is available
System.out.println(s.toUpperCase());

1.4 Pattern Matching for switch (Java 21)

Switch expressions support type pattern matching, especially powerful combined with Sealed Classes.

// Type-based switch
public double calculateArea(Shape shape) {
    return switch (shape) {
        case Circle c -> Math.PI * c.radius() * c.radius();
        case Rectangle r -> r.width() * r.height();
        case Triangle t -> 0.5 * t.base() * t.height();
        // No default needed since Shape is sealed (all cases covered)
    };
}

// Guarded patterns (conditional patterns)
public String classify(Object obj) {
    return switch (obj) {
        case Integer i when i < 0 -> "Negative integer: " + i;
        case Integer i when i == 0 -> "Zero";
        case Integer i -> "Positive integer: " + i;
        case String s when s.isEmpty() -> "Empty string";
        case String s -> "String: " + s;
        case null -> "Null value";
        default -> "Other: " + obj.getClass().getSimpleName();
    };
}

// Record Pattern deconstruction
public String describePoint(Object obj) {
    return switch (obj) {
        case Point(double x, double y) when x == 0 && y == 0 -> "Origin";
        case Point(double x, double y) when x == 0 -> "On Y-axis at " + y;
        case Point(double x, double y) when y == 0 -> "On X-axis at " + x;
        case Point(double x, double y) -> "Point at (" + x + ", " + y + ")";
        default -> "Not a point";
    };
}

1.5 Text Blocks (Java 15+)

Write multi-line strings with improved readability.

// Old approach
String json = "{\n" +
    "  \"name\": \"Alice\",\n" +
    "  \"age\": 30\n" +
    "}";

// Text Block
String json = """
        {
          "name": "Alice",
          "age": 30
        }
        """;

// SQL example
String sql = """
        SELECT u.id, u.name, o.total
        FROM users u
        JOIN orders o ON u.id = o.user_id
        WHERE u.active = true
        ORDER BY o.total DESC
        """;

// Escape sequences: \s (preserve trailing whitespace), \ (suppress newline)
String oneLine = """
        Hello \
        World\
        """; // "Hello World"

1.6 Virtual Threads (Java 21, Project Loom)

Virtual Threads are lightweight threads managed by the JVM. Instead of 1:1 mapping to OS threads, they use M:N multiplexing allowing millions of concurrent tasks.

// Traditional platform thread
Thread platformThread = new Thread(() -> {
    System.out.println("Platform thread: " + Thread.currentThread());
});
platformThread.start();

// Virtual Thread creation
Thread virtualThread = Thread.ofVirtual()
    .name("my-virtual-thread")
    .start(() -> {
        System.out.println("Virtual thread: " + Thread.currentThread());
    });

// ExecutorService with Virtual Threads
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
    List<Future<String>> futures = new ArrayList<>();
    for (int i = 0; i < 10_000; i++) {
        int taskId = i;
        futures.add(executor.submit(() -> {
            // Blocking I/O is fine - Virtual Thread unmounts from carrier thread
            Thread.sleep(100);
            return "Task " + taskId + " completed";
        }));
    }
    for (Future<String> f : futures) {
        System.out.println(f.get());
    }
}

// With Spring Boot (application.properties)
// spring.threads.virtual.enabled=true

// Structured Concurrency (preview feature)
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
    Future<User> user = scope.fork(() -> fetchUser(userId));
    Future<List<Order>> orders = scope.fork(() -> fetchOrders(userId));
    scope.join().throwIfFailed();
    return new UserProfile(user.resultNow(), orders.resultNow());
}

1.7 Sequenced Collections (Java 21)

New interfaces add the concept of ordering to collections.

// SequencedCollection: getFirst(), getLast(), addFirst(), addLast(), reversed()
List<String> list = new ArrayList<>(List.of("a", "b", "c"));
System.out.println(list.getFirst()); // "a"
System.out.println(list.getLast());  // "c"
list.addFirst("z");                  // ["z", "a", "b", "c"]
list.addLast("x");                   // ["z", "a", "b", "c", "x"]
List<String> reversed = list.reversed(); // ["x", "c", "b", "a", "z"]

// SequencedMap: firstEntry(), lastEntry(), putFirst(), putLast(), reversed()
LinkedHashMap<String, Integer> map = new LinkedHashMap<>();
map.put("one", 1);
map.put("two", 2);
map.put("three", 3);
System.out.println(map.firstEntry()); // one=1
System.out.println(map.lastEntry());  // three=3

2. Lambda Expressions and Stream API

2.1 Lambda Expression Syntax

// Basic form
Runnable r = () -> System.out.println("Hello");
Comparator<String> comp = (a, b) -> a.compareTo(b);

// Block lambda (multiple statements)
Function<Integer, Integer> factorial = n -> {
    int result = 1;
    for (int i = 1; i <= n; i++) result *= i;
    return result;
};

// Type inference
List<String> names = List.of("Charlie", "Alice", "Bob");
names.sort((a, b) -> a.compareTo(b));
// Or with method reference
names.sort(String::compareTo);

2.2 Functional Interfaces

// Function<T, R>: T -> R
Function<String, Integer> strLen = String::length;
Function<Integer, Integer> doubler = x -> x * 2;
Function<String, Integer> lenThenDouble = strLen.andThen(doubler);
System.out.println(lenThenDouble.apply("Hello")); // 10

// BiFunction<T, U, R>: T, U -> R
BiFunction<String, Integer, String> repeat = (s, n) -> s.repeat(n);
System.out.println(repeat.apply("Java", 3)); // "JavaJavaJava"

// Predicate<T>: T -> boolean
Predicate<String> isLong = s -> s.length() > 5;
Predicate<String> startsWithA = s -> s.startsWith("A");
Predicate<String> combined = isLong.and(startsWithA);
Predicate<String> either = isLong.or(startsWithA);
Predicate<String> notLong = isLong.negate();

// Supplier<T>: () -> T
Supplier<LocalDate> today = LocalDate::now;
Supplier<List<String>> listFactory = ArrayList::new;

// Consumer<T>: T -> void
Consumer<String> printer = System.out::println;
Consumer<String> both = printer.andThen(s -> log.info("Processed: {}", s));

// UnaryOperator<T>: T -> T (specialization of Function)
UnaryOperator<String> toUpper = String::toUpperCase;

// BinaryOperator<T>: T, T -> T
BinaryOperator<Integer> add = Integer::sum;

2.3 Method References (4 Types)

// 1. Static method reference: ClassName::staticMethod
Function<String, Integer> parseInt = Integer::parseInt;

// 2. Bound instance method reference: instance::instanceMethod
String prefix = "Hello, ";
Function<String, String> greeter = prefix::concat;
System.out.println(greeter.apply("World")); // "Hello, World"

// 3. Unbound instance method reference: ClassName::instanceMethod
Function<String, String> upper = String::toUpperCase;
Comparator<String> byLength = Comparator.comparingInt(String::length);

// 4. Constructor reference: ClassName::new
Supplier<ArrayList<String>> listCreator = ArrayList::new;
Function<String, StringBuilder> sbCreator = StringBuilder::new;

2.4 Stream API Core Operations

List<Employee> employees = List.of(
    new Employee("Alice", "Engineering", 90_000),
    new Employee("Bob", "Engineering", 80_000),
    new Employee("Charlie", "Marketing", 60_000),
    new Employee("Diana", "Marketing", 70_000),
    new Employee("Eve", "Engineering", 95_000)
);

// filter + map + collect
List<String> highEarnerNames = employees.stream()
    .filter(e -> e.salary() > 75_000)
    .map(Employee::name)
    .sorted()
    .toList(); // Java 16+ (returns unmodifiable list)

// flatMap: Stream<Stream<T>> -> Stream<T>
List<List<Integer>> nested = List.of(List.of(1, 2, 3), List.of(4, 5), List.of(6));
List<Integer> flat = nested.stream()
    .flatMap(Collection::stream)
    .toList();
// [1, 2, 3, 4, 5, 6]

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

Map<String, Double> avgSalaryByDept = employees.stream()
    .collect(Collectors.groupingBy(
        Employee::department,
        Collectors.averagingInt(Employee::salary)
    ));

// partitioningBy
Map<Boolean, List<Employee>> partition = employees.stream()
    .collect(Collectors.partitioningBy(e -> e.salary() > 75_000));
// true -> high earners, false -> others

// joining
String names = employees.stream()
    .map(Employee::name)
    .collect(Collectors.joining(", ", "[", "]"));
// "[Alice, Bob, Charlie, Diana, Eve]"

// toMap
Map<String, Integer> nameSalaryMap = employees.stream()
    .collect(Collectors.toMap(Employee::name, Employee::salary));

2.5 Optional

// Creating Optional
Optional<String> opt1 = Optional.of("value");        // null disallowed
Optional<String> opt2 = Optional.ofNullable(null);   // null -> empty
Optional<String> opt3 = Optional.empty();

// Extracting values
String val1 = opt1.get();                              // throws NoSuchElementException
String val2 = opt1.orElse("default");                 // always evaluated
String val3 = opt1.orElseGet(() -> computeDefault()); // lazily evaluated
String val4 = opt1.orElseThrow(() -> new NotFoundException("not found"));

// Transformation
Optional<Integer> length = opt1.map(String::length);

// flatMap: avoids Optional<Optional<T>>
Optional<Address> address = Optional.of(user)
    .flatMap(u -> Optional.ofNullable(u.getAddress()));

// Conditional processing
opt1.ifPresent(System.out::println);
opt1.ifPresentOrElse(
    v -> System.out.println("Found: " + v),
    () -> System.out.println("Not found")
);

// Anti-patterns to avoid:
// - opt.get() without isPresent() check
// - Using Optional as method parameters (not recommended)
// - Returning Optional from getters in domain entities (use for return values only)

2.6 Parallel Streams — Caveats

// Parallel stream - best for CPU-bound stateless operations
List<Integer> numbers = IntStream.rangeClosed(1, 1_000_000)
    .boxed().collect(Collectors.toList());

long sum = numbers.parallelStream()
    .mapToLong(Integer::longValue)
    .sum(); // Safe: stateless operation

// DANGER: mutating shared state
List<Integer> results = new ArrayList<>();
numbers.parallelStream()
    .filter(n -> n % 2 == 0)
    .forEach(results::add); // Unsafe! ArrayList is not thread-safe

// Correct approach: use collect
List<Integer> safeResults = numbers.parallelStream()
    .filter(n -> n % 2 == 0)
    .collect(Collectors.toList()); // thread-safe

// For I/O-bound work, prefer Virtual Threads
// Parallel streams use ForkJoinPool.commonPool() — risk of pool starvation

3. Generics In Depth

3.1 Wildcards and the PECS Principle

PECS: Producer Extends, Consumer Super

// Covariant (extends) — for reading (Producer)
public double sumList(List<? extends Number> list) {
    double sum = 0;
    for (Number n : list) sum += n.doubleValue(); // reading OK
    // list.add(1.0); // compile error — writing forbidden
    return sum;
}
// Can call with List<Integer>, List<Double>, etc.

// Contravariant (super) — for writing (Consumer)
public void addNumbers(List<? super Integer> list) {
    list.add(1);
    list.add(2); // writing OK
    // Integer n = list.get(0); // compile error — type not guaranteed
    Object obj = list.get(0); // OK
}
// Can call with List<Integer>, List<Number>, List<Object>

// Copy method (full PECS application)
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
    for (T item : src) {  // src is Producer -> extends
        dest.add(item);   // dest is Consumer -> super
    }
}

// Unbounded wildcard: ? (any type)
public void printList(List<?> list) {
    for (Object o : list) System.out.println(o);
}

3.2 Generic Methods and Type Erasure

// Generic method
public static <T extends Comparable<T>> T max(T a, T b) {
    return a.compareTo(b) >= 0 ? a : b;
}

// Recursive type bound
public static <T extends Comparable<T>> T findMax(List<T> list) {
    return list.stream().max(Comparator.naturalOrder()).orElseThrow();
}

// Type Erasure
// After compilation, List<String> becomes List (no type info at runtime)
List<String> strings = new ArrayList<>();
List<Integer> integers = new ArrayList<>();
System.out.println(strings.getClass() == integers.getClass()); // true!

// Cannot use generic type in instanceof
// if (obj instanceof List<String>) {} // compile error

// Correct approach
if (obj instanceof List<?> list && !list.isEmpty() && list.get(0) instanceof String) {
    @SuppressWarnings("unchecked")
    List<String> stringList = (List<String>) list;
}

// Cannot create generic array
// T[] arr = new T[10]; // compile error
// Workaround:
@SuppressWarnings("unchecked")
T[] arr = (T[]) new Object[10];

4. Collections Framework

4.1 Immutable Collections (Java 9+)

// Immutable List (no nulls, preserves order)
List<String> immutableList = List.of("a", "b", "c");
// immutableList.add("d"); // UnsupportedOperationException

// Immutable Set
Set<Integer> immutableSet = Set.of(1, 2, 3, 4, 5);
// Set.of has no guaranteed order, no duplicates

// Immutable Map
Map<String, Integer> immutableMap = Map.of(
    "one", 1,
    "two", 2,
    "three", 3
);

// More than 10 entries: Map.ofEntries
Map<String, Integer> bigMap = Map.ofEntries(
    Map.entry("a", 1),
    Map.entry("b", 2),
    Map.entry("c", 3)
);

// Immutable copies
List<String> original = new ArrayList<>(List.of("a", "b"));
List<String> copy = List.copyOf(original);
Map<String, Integer> mapCopy = Map.copyOf(immutableMap);

4.2 Choosing the Right Map

// HashMap: no ordering, O(1) average
Map<String, Integer> hashMap = new HashMap<>();

// LinkedHashMap: insertion-order preserved, O(1) average
Map<String, Integer> linkedMap = new LinkedHashMap<>();

// TreeMap: sorted by key, O(log n)
Map<String, Integer> treeMap = new TreeMap<>();
Map<String, Integer> reverseMap = new TreeMap<>(Comparator.reverseOrder());

// ConcurrentHashMap: thread-safe, segment locking
ConcurrentHashMap<String, Integer> concurrentMap = new ConcurrentHashMap<>();
concurrentMap.computeIfAbsent("key", k -> 0);
concurrentMap.merge("key", 1, Integer::sum); // atomic counter

// EnumMap: Enum keys, extremely fast
EnumMap<DayOfWeek, String> schedule = new EnumMap<>(DayOfWeek.class);

4.3 List/Deque Implementations

// ArrayList: random access O(1), insert/delete O(n)
List<String> arrayList = new ArrayList<>();

// ArrayDeque: double-ended queue, both stack and queue roles, fast
Deque<String> deque = new ArrayDeque<>();
deque.push("first");      // stack: add to front
deque.offerLast("last");  // queue: add to back
String top = deque.peek();  // peek front (no removal)
String removed = deque.poll(); // remove front

// CopyOnWriteArrayList: thread-safe, for many reads / few writes
CopyOnWriteArrayList<String> cowList = new CopyOnWriteArrayList<>();

5. Exception Handling Patterns

5.1 Checked vs Unchecked

// Checked Exception: compiler forces handling
public String readFile(String path) throws IOException {
    return Files.readString(Path.of(path));
}

// Unchecked Exception: RuntimeException subclasses
public int divide(int a, int b) {
    if (b == 0) throw new ArithmeticException("Division by zero");
    return a / b;
}

// Custom exception hierarchy
public class AppException extends RuntimeException {
    private final String errorCode;

    public AppException(String errorCode, String message) {
        super(message);
        this.errorCode = errorCode;
    }

    public AppException(String errorCode, String message, Throwable cause) {
        super(message, cause);
        this.errorCode = errorCode;
    }

    public String getErrorCode() { return errorCode; }
}

public class ResourceNotFoundException extends AppException {
    public ResourceNotFoundException(String resourceType, Object id) {
        super("RESOURCE_NOT_FOUND",
              resourceType + " not found with id: " + id);
    }
}

public class ValidationException extends AppException {
    private final Map<String, String> fieldErrors;

    public ValidationException(Map<String, String> fieldErrors) {
        super("VALIDATION_FAILED", "Validation failed");
        this.fieldErrors = Map.copyOf(fieldErrors);
    }

    public Map<String, String> getFieldErrors() { return fieldErrors; }
}

5.2 try-with-resources and Multi-catch

// try-with-resources (implements AutoCloseable)
public String readAndClose(String path) throws IOException {
    try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
        return reader.lines().collect(Collectors.joining("\n"));
    } // reader.close() called automatically
}

// Multiple resources (closed in reverse order)
public void copyFile(String src, String dest) throws IOException {
    try (
        InputStream in = new FileInputStream(src);
        OutputStream out = new FileOutputStream(dest)
    ) {
        in.transferTo(out);
    }
}

// Multi-catch
public void process(String input) {
    try {
        int value = Integer.parseInt(input);
        String result = riskyOperation(value);
        System.out.println(result);
    } catch (NumberFormatException | IllegalArgumentException e) {
        System.err.println("Invalid input: " + e.getMessage());
    } catch (RuntimeException e) {
        log.error("Unexpected error", e);
        throw e;
    }
}

6. Concurrency

6.1 CompletableFuture

// Async operation chaining
CompletableFuture<String> future = CompletableFuture
    .supplyAsync(() -> fetchUserId())
    .thenApplyAsync(id -> fetchUser(id))
    .thenApply(user -> user.getName())
    .exceptionally(ex -> "Unknown User");

// thenCompose: flatMap for async (avoids CompletableFuture<CompletableFuture<T>>)
CompletableFuture<Order> orderFuture = CompletableFuture
    .supplyAsync(() -> fetchUser(userId))
    .thenCompose(user -> CompletableFuture.supplyAsync(() -> fetchLatestOrder(user)));

// thenCombine: merge two async results
CompletableFuture<User> userFuture = CompletableFuture.supplyAsync(() -> fetchUser(id));
CompletableFuture<List<Order>> ordersFuture = CompletableFuture.supplyAsync(() -> fetchOrders(id));

CompletableFuture<UserProfile> profileFuture = userFuture.thenCombine(
    ordersFuture,
    (user, orders) -> new UserProfile(user, orders)
);

// allOf: wait for all to complete
CompletableFuture<Void> allDone = CompletableFuture.allOf(task1, task2, task3);

// anyOf: first to complete wins
CompletableFuture<Object> firstDone = CompletableFuture.anyOf(task1, task2, task3);

// Timeout
CompletableFuture<String> withTimeout = future
    .orTimeout(5, TimeUnit.SECONDS)
    .completeOnTimeout("default", 3, TimeUnit.SECONDS);

6.2 Locks

// ReentrantLock
ReentrantLock lock = new ReentrantLock(true); // fair lock
lock.lock();
try {
    // critical section
} finally {
    lock.unlock(); // always unlock in finally
}

// tryLock with timeout
if (lock.tryLock(1, TimeUnit.SECONDS)) {
    try {
        // critical section
    } finally {
        lock.unlock();
    }
}

// ReadWriteLock: many readers, single writer
ReadWriteLock rwLock = new ReentrantReadWriteLock();
Map<String, String> cache = new HashMap<>();

public String get(String key) {
    rwLock.readLock().lock();
    try { return cache.get(key); }
    finally { rwLock.readLock().unlock(); }
}

public void put(String key, String value) {
    rwLock.writeLock().lock();
    try { cache.put(key, value); }
    finally { rwLock.writeLock().unlock(); }
}

7. Modern Java Patterns

7.1 Builder Pattern

public class HttpRequest {
    private final String url;
    private final String method;
    private final Map<String, String> headers;
    private final String body;
    private final int timeoutMs;

    private HttpRequest(Builder builder) {
        this.url = builder.url;
        this.method = builder.method;
        this.headers = Map.copyOf(builder.headers);
        this.body = builder.body;
        this.timeoutMs = builder.timeoutMs;
    }

    public static Builder builder(String url) {
        return new Builder(url);
    }

    public static class Builder {
        private final String url;
        private String method = "GET";
        private Map<String, String> headers = new HashMap<>();
        private String body;
        private int timeoutMs = 5000;

        private Builder(String url) { this.url = url; }

        public Builder method(String method) { this.method = method; return this; }
        public Builder header(String key, String value) { headers.put(key, value); return this; }
        public Builder body(String body) { this.body = body; return this; }
        public Builder timeout(int ms) { this.timeoutMs = ms; return this; }
        public HttpRequest build() { return new HttpRequest(this); }
    }
}

// Usage
HttpRequest request = HttpRequest.builder("https://api.example.com/users")
    .method("POST")
    .header("Content-Type", "application/json")
    .header("Authorization", "Bearer token123")
    .body("{\"name\": \"Alice\"}")
    .timeout(3000)
    .build();

7.2 Sealed Class + Pattern Matching as ADT

// Result type (similar to Either)
public sealed interface Result<T> permits Result.Ok, Result.Err {

    record Ok<T>(T value) implements Result<T> {}
    record Err<T>(String message, Throwable cause) implements Result<T> {
        public Err(String message) { this(message, null); }
    }

    static <T> Result<T> ok(T value) { return new Ok<>(value); }
    static <T> Result<T> err(String message) { return new Err<>(message); }
    static <T> Result<T> of(Supplier<T> supplier) {
        try { return ok(supplier.get()); }
        catch (Exception e) { return err(e.getMessage()); }
    }

    default <U> Result<U> map(Function<T, U> mapper) {
        return switch (this) {
            case Ok<T> ok -> Result.ok(mapper.apply(ok.value()));
            case Err<T> err -> Result.err(err.message());
        };
    }

    default T getOrElse(T defaultValue) {
        return switch (this) {
            case Ok<T> ok -> ok.value();
            case Err<T> ignored -> defaultValue;
        };
    }
}

// Command pattern as ADT
public sealed interface Command permits Command.CreateUser, Command.DeleteUser, Command.UpdateEmail {
    record CreateUser(String name, String email) implements Command {}
    record DeleteUser(String userId) implements Command {}
    record UpdateEmail(String userId, String newEmail) implements Command {}
}

public void handle(Command command) {
    switch (command) {
        case Command.CreateUser(String name, String email) ->
            userService.create(name, email);
        case Command.DeleteUser(String userId) ->
            userService.delete(userId);
        case Command.UpdateEmail(String userId, String newEmail) ->
            userService.updateEmail(userId, newEmail);
    }
}

7.3 Integration with Spring Boot

// Records as DTOs
public record CreateUserRequest(
    @NotBlank String name,
    @Email String email,
    @Min(0) @Max(150) int age
) {}

public record UserResponse(
    String id,
    String name,
    String email,
    LocalDateTime createdAt
) {
    public static UserResponse from(User user) {
        return new UserResponse(
            user.getId(),
            user.getName(),
            user.getEmail(),
            user.getCreatedAt()
        );
    }
}

// Controller
@RestController
@RequestMapping("/api/users")
public class UserController {

    @PostMapping
    public ResponseEntity<UserResponse> createUser(
            @RequestBody @Valid CreateUserRequest request) {
        User user = userService.create(request);
        return ResponseEntity.status(HttpStatus.CREATED)
                .body(UserResponse.from(user));
    }
}

8. Quizzes

Quiz 1: Which statement about Records is INCORRECT?

Options:

  • A) The compiler automatically generates equals, hashCode, and toString
  • B) You can declare additional instance fields
  • C) Records can implement interfaces
  • D) Records cannot extend other classes

Answer: B

Explanation: Records cannot declare additional instance fields beyond those defined in the header. Static fields are allowed. Records implicitly extend java.lang.Record and therefore cannot extend any other class, but they can implement interfaces.

Quiz 2: In the PECS principle, what is the correct wildcard for: public void addToList(List<___> list, T element)?

Options:

  • A) extends T
  • B) super T
  • C) ?
  • D) No wildcard needed

Answer: B (super T)

Explanation: PECS (Producer Extends, Consumer Super) — here the list acts as a Consumer (it receives elements), so ? super T is correct. ? extends T is for Producers (reading). List<? super T> accepts a list of T or any supertype of T, allowing safe addition of T elements.

Quiz 3: Which statement about Virtual Threads is correct?

Options:

  • A) Virtual Threads map 1:1 to OS threads
  • B) They are especially effective for CPU-bound tasks
  • C) When blocking on I/O, they unmount from the carrier thread
  • D) They always deliver optimal performance inside synchronized blocks

Answer: C

Explanation: When a Virtual Thread performs a blocking I/O operation, it unmounts from its OS carrier thread, freeing that carrier for other Virtual Threads. Virtual Threads are designed for I/O-bound rather than CPU-bound work. Inside synchronized blocks, pinning can occur (the carrier thread is held), which can hurt performance — ReentrantLock is preferred in hot paths.

Quiz 4: What is the result of: List.of(1, 2, 3, 4, 5).stream().filter(n -> n % 2 == 0).map(n -> n * n).reduce(0, Integer::sum)?

Options:

  • A) 20
  • B) 29
  • C) 25
  • D) 4

Answer: A (20)

Explanation: filter selects even numbers [2, 4], map squares them [4, 16], reduce sums them: 4 + 16 = 20.

Quiz 5: What keyword CANNOT be used as the direct subclass declaration in a sealed class hierarchy?

Options:

  • A) final
  • B) sealed
  • C) non-sealed
  • D) abstract (alone, without sealed/non-sealed/final)

Answer: D

Explanation: Every direct subclass of a sealed class must be declared as exactly one of final, sealed, or non-sealed. Using abstract alone does not fulfill this requirement. An abstract class can additionally be sealed (e.g., public abstract sealed class) but cannot be only abstract without specifying one of the three required modifiers.


Summary

Modern Java goes far beyond simple syntax improvements, providing functional programming, type safety, and high-performance concurrency at the language level. With Java 21 LTS:

  • Record + Sealed Class + Pattern Matching: type-safe domain modeling
  • Stream API + Optional: declarative data processing
  • Virtual Threads: high-throughput I/O concurrency
  • CompletableFuture: asynchronous pipelines

Combining these features appropriately lets you write Java code that is concise, safe, and high-performance.