Modern Java (Java 8-21+)

Java's evolution: Less verbose, more expressive, still type-safe

Records Java 14+

Records eliminate boilerplate for immutable data classes. Perfect for DTOs, value objects, and API responses.

// 50+ lines of boilerplate for a simple data class
public final class User {
    private final String id;
    private final String name;
    private final String email;

    public User(String id, String name, String email) {
        this.id = id;
        this.name = name;
        this.email = email;
    }

    public String getId() { return id; }
    public String getName() { return name; }
    public String getEmail() { return email; }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return Objects.equals(id, user.id) &&
               Objects.equals(name, user.name) &&
               Objects.equals(email, user.email);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name, email);
    }

    @Override
    public String toString() {
        return "User[id=" + id + ", name=" + name + ", email=" + email + "]";
    }
}
// One line! Gets: constructor, getters, equals, hashCode, toString
public record User(String id, String name, String email) { }

// Usage
User user = new User("1", "Alice", "alice@example.com");
user.name();   // "Alice" (not getName()!)
user.email();  // "alice@example.com"

// With validation
public record User(String id, String name, String email) {
    public User {  // Compact constructor
        Objects.requireNonNull(id, "id required");
        Objects.requireNonNull(name, "name required");
        if (!email.contains("@")) {
            throw new IllegalArgumentException("Invalid email");
        }
    }
}

// With additional methods
public record Point(double x, double y) {
    public double distanceFromOrigin() {
        return Math.sqrt(x * x + y * y);
    }

    public Point translate(double dx, double dy) {
        return new Point(x + dx, y + dy);  // Records are immutable
    }
}

var (Local Type Inference) Java 10+

# Python: always inferred
names = ["Alice", "Bob"]          # list
user = get_user(123)              # User
result = process(data)            # unknown until runtime
// Java 10+: var for local variables (still type-safe!)

// Before: redundant type on both sides
List<String> names = new ArrayList<String>();
Map<String, List<User>> usersByDept = new HashMap<String, List<User>>();

// After: var infers the type
var names = new ArrayList<String>();      // type: ArrayList<String>
var usersByDept = new HashMap<String, List<User>>();

// Great for complex types
var stream = list.stream()
    .filter(x -> x.isActive())
    .map(User::getName);  // IDE knows exact type

// Still type-safe! This is a compile error:
var name = "Alice";
name = 42;  // COMPILE ERROR: incompatible types

// When NOT to use var:
var result = getResult();  // Unclear what type result is
UserResponse result = getResult();  // Clear!

// var is local variables only - not fields, parameters, or return types

Pattern Matching Java 16+

// Old way: instanceof + cast
if (obj instanceof String) {
    String s = (String) obj;  // Redundant cast
    System.out.println(s.length());
}

// New way: pattern matching for instanceof (Java 16+)
if (obj instanceof String s) {
    System.out.println(s.length());  // s already typed!
}

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

// Guards with &&
if (obj instanceof Integer i && i > 0) {
    System.out.println("Positive: " + i);
}
// Switch expressions (Java 14+) - return values!

// Old switch statement
String result;
switch (day) {
    case MONDAY:
    case FRIDAY:
        result = "Busy";
        break;
    case SATURDAY:
    case SUNDAY:
        result = "Weekend";
        break;
    default:
        result = "Normal";
}

// New switch expression
String result = switch (day) {
    case MONDAY, FRIDAY -> "Busy";
    case SATURDAY, SUNDAY -> "Weekend";
    default -> "Normal";
};

// Pattern matching in switch (Java 21+)
String describe(Object obj) {
    return switch (obj) {
        case Integer i -> "Integer: " + i;
        case Long l -> "Long: " + l;
        case String s -> "String: " + s;
        case null -> "null";
        default -> "Unknown";
    };
}

// With guards (Java 21+)
String format(Object obj) {
    return switch (obj) {
        case String s when s.isEmpty() -> "Empty string";
        case String s -> "String: " + s;
        case Integer i when i < 0 -> "Negative";
        case Integer i -> "Positive or zero";
        default -> "Other";
    };
}
// Record patterns: destructure records in patterns (Java 21+)

record Point(int x, int y) {}
record Circle(Point center, int radius) {}

// Destructure in instanceof
if (shape instanceof Circle(Point(var x, var y), var r)) {
    System.out.println("Circle at (" + x + "," + y + ") with radius " + r);
}

// Destructure in switch
String describe(Object shape) {
    return switch (shape) {
        case Circle(Point(var x, var y), var r) when r > 10 ->
            "Large circle at (" + x + "," + y + ")";
        case Circle(Point(var x, var y), var r) ->
            "Small circle at (" + x + "," + y + ")";
        case Point(var x, var y) ->
            "Point at (" + x + "," + y + ")";
        default -> "Unknown shape";
    };
}

Sealed Classes Java 17+

// Control exactly which classes can extend/implement
// Compiler knows ALL possible subtypes

public sealed interface Shape
    permits Circle, Rectangle, Triangle {
    double area();
}

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

public record Rectangle(double width, double height) implements Shape {
    @Override
    public double area() {
        return width * height;
    }
}

public record Triangle(double base, double height) implements Shape {
    @Override
    public double area() {
        return 0.5 * base * height;
    }
}

// Exhaustive switch - compiler knows all cases!
String describe(Shape shape) {
    return switch (shape) {
        case Circle c -> "Circle with radius " + c.radius();
        case Rectangle r -> "Rectangle " + r.width() + "x" + r.height();
        case Triangle t -> "Triangle with base " + t.base();
        // No default needed! Compiler knows these are ALL cases
    };
}

// If you add a new Shape subtype, ALL switches must be updated
// This is caught at COMPILE TIME, not runtime!

Why Sealed Types Matter

Text Blocks Java 15+

# Python triple quotes
json_template = """
{
    "name": "%s",
    "email": "%s"
}
"""

sql = """
    SELECT u.id, u.name
    FROM users u
    WHERE u.active = true
"""
// Old way: escape hell
String json = "{\n" +
    "    \"name\": \"" + name + "\",\n" +
    "    \"email\": \"" + email + "\"\n" +
    "}";

// Text blocks (Java 15+)
String json = """
    {
        "name": "%s",
        "email": "%s"
    }
    """.formatted(name, email);

// Great for SQL
String sql = """
    SELECT u.id, u.name, u.email
    FROM users u
    JOIN orders o ON u.id = o.user_id
    WHERE u.active = true
      AND o.created_at > :since
    ORDER BY o.created_at DESC
    """;

// Great for HTML/templates
String html = """
    <html>
        <body>
            <h1>Hello, %s!</h1>
        </body>
    </html>
    """.formatted(username);

// Trailing backslash continues line without newline
String oneLine = """
    This is a very long line that I want to \
    wrap in source but not in the output""";

Virtual Threads (Project Loom) Java 21+

// Traditional threads: expensive, limited (thousands)
for (int i = 0; i < 10000; i++) {
    new Thread(() -> doWork()).start();  // May exhaust OS threads
}

// Virtual threads: cheap, unlimited (millions!)
for (int i = 0; i < 1_000_000; i++) {
    Thread.startVirtualThread(() -> doWork());  // No problem!
}

// Using ExecutorService
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    for (int i = 0; i < 10000; i++) {
        executor.submit(() -> {
            // Each task gets its own virtual thread
            String result = fetchFromApi();  // Blocking IO is fine!
            processResult(result);
        });
    }
}  // Waits for all tasks

// Why this matters:
// - Write simple blocking code (like Python)
// - Get async performance automatically
// - No callback hell, no reactive complexity
// - One virtual thread per request at Twilio scale = trivial

// Spring Boot 3.2+ supports virtual threads out of the box
// spring.threads.virtual.enabled=true

Virtual Threads for Architects

Virtual threads change everything for high-concurrency systems. You can now:

This is likely a hot topic for a Twilio Distinguished Architect interview.

Helpful NullPointerExceptions Java 14+

// Old NPE (before Java 14)
// Exception in thread "main" java.lang.NullPointerException
//     at Example.main(Example.java:15)
// Which part was null?!

// New NPE (Java 14+)
// Exception in thread "main" java.lang.NullPointerException:
//     Cannot invoke "String.toUpperCase()" because the return value
//     of "User.getName()" is null

String city = user.getAddress().getCity().toUpperCase();
// Now tells you EXACTLY which part was null:
// - user was null?
// - getAddress() returned null?
// - getCity() returned null?

Stream Improvements

// toList() instead of collect(Collectors.toList()) - Java 16+
List<String> names = users.stream()
    .map(User::getName)
    .toList();  // Returns immutable list

// mapMulti() - flatMap alternative (Java 16+)
List<Integer> numbers = Stream.of(1, 2, 3)
    .<Integer>mapMulti((n, consumer) -> {
        consumer.accept(n);
        consumer.accept(n * 10);
    })
    .toList();  // [1, 10, 2, 20, 3, 30]

// takeWhile/dropWhile (Java 9+)
List<Integer> nums = List.of(1, 2, 3, 4, 5);
nums.stream()
    .takeWhile(n -> n < 4)
    .toList();  // [1, 2, 3]

nums.stream()
    .dropWhile(n -> n < 4)
    .toList();  // [4, 5]

// Collectors.teeing() - two collectors at once (Java 12+)
var result = Stream.of(1, 2, 3, 4, 5).collect(
    Collectors.teeing(
        Collectors.summingInt(Integer::intValue),  // sum
        Collectors.counting(),                      // count
        (sum, count) -> sum / count                 // average
    )
);  // 3

Java Version Timeline

VersionKey FeaturesLTS?
Java 8 (2014)Lambdas, Streams, Optional, new Date/Time APIYes
Java 9Modules (JPMS), JShell, Stream improvementsNo
Java 10var (local type inference)No
Java 11 (2018)HTTP Client, String methodsYes
Java 14Records (preview), Switch expressionsNo
Java 15Text blocks, Sealed classes (preview)No
Java 16Records (final), Pattern matching instanceofNo
Java 17 (2021)Sealed classes (final), Pattern matching switch (preview)Yes
Java 21 (2023)Virtual threads, Record patterns, Pattern matching (final)Yes

Interview Tip

For a Distinguished Architect role, you should know: