// Java Interview — Senior Level

🚀 PART 2: Java Versions & Evolution (Q41–75)

Java 5 through Java 21+ — generics, lambdas, streams, modules, records, sealed classes, virtual threads.

35 Questions
22 Mid
7 Advanced
6 Expert
41 What features were introduced in Java 5? MID

Java 5 (2004) was a watershed release:

  1. Generics: Type-safe collections and classes
  2. Enhanced for-loop (for-each): for (String s : list)
  3. Autoboxing/unboxing: Automatic primitive-wrapper conversion
  4. Enums: enum Direction { NORTH, SOUTH, EAST, WEST }
  5. Varargs: void method(String... args)
  6. Annotations: @Override, @Deprecated, @SuppressWarnings, custom annotations
  7. Static imports: import static java.lang.Math.PI
  8. Concurrent utilities: java.util.concurrent – Executor framework, locks, atomic classes
  9. Formatted I/O: printf, String.format
  10. Iterable interface: Enables for-each on custom classes

Java 5 generics are implemented via type erasure – generic type info is removed at runtime. This is a compromise for backward compatibility but has implications: can't do new T(), instanceof T, or List<String>.class.

42 What is generics? MID

Generics enable type-safe, reusable code by parameterizing types.

// Without generics (Java 1.4)
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0); // Unsafe cast, possible ClassCastException

// With generics (Java 5+)
List<String> list = new ArrayList<>();
list.add("hello");
String s = list.get(0); // No cast needed, type-safe

Type erasure: At compile time, List<String> is checked. At runtime, it becomes List (raw type). Generic info is mostly lost.

Wildcards:

  • ? extends T (upper bounded): read-only, covariant. "Producer Extends"
  • ? super T (lower bounded): write-capable, contravariant. "Consumer Super"
  • PECS rule (Producer Extends, Consumer Super)
// Copy elements from src to dest
<T> void copy(List<? super T> dest, List<? extends T> src) {
    for (T item : src) dest.add(item);
}

Bounded type parameters:

<T extends Comparable<T>> T max(T a, T b) { return a.compareTo(b) > 0 ? a : b; }

Generic methods vs generic classes: Method can have its own type parameters independent of the class.

Reifiable types: Only raw types, non-generic types, unbounded wildcards are reifiable (accessible at runtime via reflection).

43 What is enhanced for-loop? MID

The enhanced for-loop (for-each) iterates over arrays and Iterable implementations:

// Array
int[] numbers = {1, 2, 3};
for (int n : numbers) { System.out.println(n); }

// Iterable
List<String> names = List.of("Alice", "Bob");
for (String name : names) { System.out.println(name); }

Under the hood, for Iterable, the compiler generates:

Iterator<String> it = names.iterator();
while (it.hasNext()) {
    String name = it.next();
    // body
}

For arrays, it generates a traditional index-based loop.

Limitations:

  • Can't modify the collection during iteration (ConcurrentModificationException)
  • Can't access index
  • Can't iterate multiple collections simultaneously
  • Can't iterate in reverse

For indexed access, use for (int i = 0; i < list.size(); i++). For removal during iteration, use it.remove() or list.removeIf().

44 Java 7 major features? MID
  1. try-with-resources: Auto-closing of AutoCloseable resources
  2. Multi-catch: catch (IOException | SQLException e)
  3. Diamond operator: List<String> list = new ArrayList<>() (infer generic type)
  4. String in switch: switch (str) { case "value": }
  5. Numeric literals with underscores: 1_000_000, 0xFF_FF
  6. Binary literals: 0b1010
  7. Fork/Join framework: ForkJoinPool, RecursiveTask
  8. NIO.2 (java.nio.file): Path, Files, WatchService, better file handling
  9. invokedynamic bytecode: Foundation for lambdas in Java 8

Java 7 Project Coin: Small language improvements collected under "Project Coin".

45 What is try-with-resources? MID

try-with-resources automatically closes resources that implement AutoCloseable:

// Java 6 way (verbose, easy to forget closing in finally)
Connection conn = null;
try {
    conn = dataSource.getConnection();
    // use connection
} finally {
    if (conn != null) conn.close(); // can throw too!
}

// Java 7 way
try (Connection conn = dataSource.getConnection();
     PreparedStatement stmt = conn.prepareStatement(sql)) {
    // use conn and stmt
    // both auto-closed in reverse order on exit
}

Close order: Resources closed in reverse order of opening (stmt closed before conn).

Suppressed exceptions: If body throws AND close() throws, the close() exception is suppressed and attached to the primary exception:

try {
    Throwable t = e.getSuppressed()[0]; // access suppressed exception
}

Java 9 improvement: Can reference effectively-final variables:

Connection conn = getConnection();
try (conn) { // no need to redeclare
    // use conn
}
46 What is multi-catch exception? MID

Multi-catch allows catching multiple exception types in one catch block:

// Java 6
try { ... }
catch (IOException e) { log(e); throw new ServiceException(e); }
catch (SQLException e) { log(e); throw new ServiceException(e); } // code duplication

// Java 7
try { ... }
catch (IOException | SQLException e) {
    log(e); // DRY
    throw new ServiceException(e);
}

Important rule: In multi-catch, the variable e is implicitly final (can't reassign). This makes sense because e could be either type.

Can't catch related exceptions: catch (Exception | IOException e) is illegal because IOException extends Exception – it would be redundant.

47 Java 8 key features? MID

Java 8 (2014) was the most transformative release since Java 5:

  1. Lambda expressions: (x, y) -> x + y
  2. Functional interfaces: @FunctionalInterface
  3. Stream API: Declarative data processing
  4. Optional: Null-safe value containers
  5. Default and static methods in interfaces
  6. Method references: String::toUpperCase, System.out::println
  7. New Date/Time API (java.time): LocalDate, LocalDateTime, ZonedDateTime, Duration, Period
  8. CompletableFuture: Async programming
  9. Nashorn JavaScript engine (deprecated in 11)
  10. Base64 encoding: Base64.getEncoder()
  11. forEach on Iterable: list.forEach(System.out::println)
  12. Map.getOrDefault(), Map.computeIfAbsent()`, etc.
  13. StringJoiner
  14. Parallel array sorting: Arrays.parallelSort()

Java 8 enabled functional programming patterns in Java and drastically changed how Java code is written.

48 What is lambda expression? MID

A lambda is a concise anonymous function that can be passed around as a value.

Syntax: (parameters) -> expression or (parameters) -> { statements; }

// Old way
Runnable r = new Runnable() {
    @Override
    public void run() { System.out.println("Hello"); }
};

// Lambda
Runnable r = () -> System.out.println("Hello");

// With parameters
Comparator<String> comp = (a, b) -> a.compareTo(b);

// Multi-line
Function<String, String> process = input -> {
    String trimmed = input.trim();
    return trimmed.toUpperCase();
};

Target typing: Lambda type is inferred from context. The target must be a functional interface (exactly one abstract method).

Variable capture: Lambdas can capture effectively final local variables (not just final). Effectively final = not reassigned after initialization.

String prefix = "Hello"; // effectively final
Function<String, String> greet = name -> prefix + " " + name; // OK
prefix = "Hi"; // This would make lambda illegal

Method references are lambdas in disguise: String::toUpperCase = s -> s.toUpperCase()

Types of method references:

  • Static: Integer::parseInt
  • Instance (bound): str::contains (specific instance)
  • Instance (unbound): String::toUpperCase (called on parameter)
  • Constructor: ArrayList::new
49 Functional interface? MID

A functional interface has exactly one abstract method. Lambdas can be used wherever a functional interface is expected.

@FunctionalInterface annotation: Optional but recommended (compile-time check).

Built-in functional interfaces (java.util.function):

InterfaceSignatureUse
Runnable() -> voidRun code
Supplier<T>() -> TProduce value
Consumer<T>T -> voidConsume value
Function<T,R>T -> RTransform
Predicate<T>T -> booleanTest condition
BiFunction<T,U,R>(T,U) -> RTwo-arg transform
UnaryOperator<T>T -> TTransform same type
BinaryOperator<T>(T,T) -> TReduce same type

Functional interfaces can have:

  • Default methods
  • Static methods
  • Methods from Object (equals, toString, etc.) – don't count as abstract
50 Predicate vs Function? MID

Predicate<T>: Takes T, returns boolean

Predicate<String> isLong = s -> s.length() > 10;
isLong.test("Hello World!"); // true

// Composing predicates
Predicate<String> isLongAndUpper = isLong.and(s -> s.equals(s.toUpperCase()));
Predicate<String> isLongOrEmpty = isLong.or(String::isEmpty);
Predicate<String> isShort = isLong.negate();

Function<T, R>: Takes T, returns R

Function<String, Integer> length = String::length;
length.apply("Hello"); // 5

// Composing functions
Function<String, String> trim = String::trim;
Function<String, String> upper = String::toUpperCase;
Function<String, String> trimThenUpper = trim.andThen(upper);
Function<String, String> upperAfterTrim = upper.compose(trim); // same result

Use Predicate for filtering/testing, Function for transforming.

BiPredicate<T, U>: Two arguments, returns boolean.

BiFunction<T, U, R>: Two arguments, returns R.

51 What is Stream API? MID

Stream API provides a declarative, functional approach to processing sequences of elements.

List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");

// Imperative
List<String> result = new ArrayList<>();
for (String name : names) {
    if (name.length() > 4) result.add(name.toUpperCase());
}
Collections.sort(result);

// Stream (declarative)
List<String> result = names.stream()
    .filter(name -> name.length() > 4)
    .map(String::toUpperCase)
    .sorted()
    .collect(Collectors.toList());

Key characteristics:

  1. Lazy: Intermediate operations don't execute until a terminal operation is called
  2. Single use: Streams can't be reused
  3. Non-interfering: Should not modify the source during processing
  4. Stateless (mostly): Operations should be side-effect-free for parallel safety

Operations:

  • Intermediate (lazy): filter, map, flatMap, sorted, distinct, limit, skip, peek
  • Terminal (trigger execution): collect, forEach, reduce, count, findFirst, anyMatch, allMatch, toList() (Java 16)

Specialized streams: IntStream, LongStream, DoubleStream – avoid boxing overhead.

IntStream.range(0, 10).sum(); // 45
IntStream.of(1, 2, 3).average(); // OptionalDouble
52 How does Stream differ from Collection? MID
CollectionStream
Stores dataProcesses data
In-memory data structureComputational pipeline
Can be traversed multiple timesSingle-use
Eager: elements computed when addedLazy: computed on demand
Mutate elementsNo mutation (produces new stream/result)
Has sizeMay be infinite (Stream.iterate, Stream.generate)
IterableBaseStream

Key distinction: A Collection is data. A Stream processes data.

// Infinite stream (possible only with laziness)
Stream.iterate(0, n -> n + 2)
    .filter(n -> n % 6 == 0)
    .limit(5)
    .forEach(System.out::println); // 0, 6, 12, 18, 24

Streams can be created FROM collections (.stream()), arrays (Arrays.stream()), values (Stream.of()), files (Files.lines()), etc.

53 What is Optional? MID

Optional<T> is a container that may or may not contain a value. It's a way to express "nullable return" explicitly in the type system.

// Instead of returning null
public Optional<User> findById(Long id) {
    return users.stream()
        .filter(u -> u.getId().equals(id))
        .findFirst(); // Returns Optional<User>
}

// Usage
Optional<User> user = findById(1L);

// Bad practice (defeats the purpose)
if (user.isPresent()) user.get(); // like null check

// Good practice
user.ifPresent(u -> sendEmail(u.getEmail()));
String name = user.map(User::getName).orElse("Unknown");
User found = user.orElseThrow(() -> new UserNotFoundException(id));

Methods:

  • isPresent(), isEmpty() (Java 11)
  • get() (throws if empty – use cautiously)
  • orElse(default) – always evaluates default
  • orElseGet(supplier) – lazy, only evaluates if empty
  • orElseThrow(supplier)
  • map(), flatMap(), filter()
  • ifPresent(), ifPresentOrElse() (Java 9)
  • or(supplier) (Java 9) – return other Optional if empty
  • stream() (Java 9) – 0 or 1 element stream

When NOT to use Optional:

  • As method parameter (use overloads or nullability annotations instead)
  • As field in a class (not serializable, overhead)
  • In collections (use empty collection instead of Optional)
54 Java 9 module system (JPMS)? MID

Java Platform Module System (JPMS / Project Jigsaw)module-info.java

module com.company.service {
    requires java.sql;              // depend on module
    requires transitive java.logging; // transitive dependency
    exports com.company.service.api;  // make packages available
    exports com.company.service.impl to com.company.other; // qualified export
    opens com.company.service.model;  // allow reflection
    uses com.company.spi.Plugin;       // ServiceLoader
    provides com.company.spi.Plugin with com.company.service.DefaultPlugin;
}

Goals:

  1. Strong encapsulation: Even public types in unexported packages aren't accessible
  2. Reliable configuration: Explicit dependency graph; missing modules detected at startup
  3. Scalable platform: Build custom JREs with only needed modules (jlink)

Module types:

  • Named module: Has module-info.java
  • Unnamed module: Classpath code (backward compatible)
  • Automatic module: JAR on module path without module-info.java

Impact in practice: Most enterprise apps still use classpath (unnamed module) for backward compatibility. Libraries are adding module-info.java. Spring Boot has limited module support.

55 What is jlink? MID

jlink is a tool to create custom, minimal JRE images containing only the modules your application needs.

jlink --module-path $JAVA_HOME/jmods:mods \
      --add-modules com.example.myapp \
      --output custom-runtime \
      --strip-debug \
      --compress=2

Benefits:

  • Smaller deployments: Runtime can be 20–50MB vs 200MB+ full JDK
  • Faster startup: Fewer classes to load
  • Security: Reduced attack surface
  • Docker images: Much smaller container images

Used heavily in containerized microservices and GraalVM native image scenarios.

56 Java 10 local variable type inference? MID

Java 10 introduced var for local variable type inference:

// Before
ArrayList<Map<String, List<Integer>>> map = new ArrayList<>();
HttpURLConnection conn = (HttpURLConnection) url.openConnection();

// With var
var map = new ArrayList<Map<String, List<Integer>>>();
var conn = (HttpURLConnection) url.openConnection();
57 What is var keyword? MID

var is not a type – it's syntactic sugar for type inference in local variables. The compiler infers the type from the right-hand side; the type is still static.

Where var can be used:

  • Local variable declarations with initializer
  • for-loop index and enhanced for-loop
  • try-with-resources

Where var CANNOT be used:

  • Method parameters or return types
  • Fields
  • var x = null; (can't infer from null)
  • Lambda parameters (actually CAN in Java 11: (var x, var y) -> x + y)

Senior considerations:

  • Improves readability for complex generic types
  • Can harm readability with primitives or unclear types: var result = compute(); – what type is result?
  • Good practice: use var when the type is obvious from the right side
  • var is a reserved type name, not a keyword – int var = 5; is still valid (terrible style, but compiles)
58 Java 11 features? MID

Java 11 (2018) – LTS release, major features:

  1. HTTP Client API (java.net.http) – finalized from Java 9 incubator
  2. String new methods: isBlank(), lines(), strip(), stripLeading(), stripTrailing(), repeat(n)
  3. Files.readString(), Files.writeString()`
  4. var in lambda parameters: (var x, var y) -> x + y
  5. Running single-file programs: java HelloWorld.java without compiling
  6. Epsilon GC: No-op GC (for testing/benchmarking)
  7. ZGC: Low-latency GC (experimental)
  8. Nest-based access control: Inner classes don't need synthetic accessor methods
  9. Removal of Java EE and CORBA modules: javax.xml.bind, javax.activation removed (need to add as dependencies)
  10. Removed Nashorn JavaScript engine (deprecated, finally removed in 15)

strip() vs trim(): trim() removes ASCII whitespace (≤ '\u0020'). strip() removes Unicode whitespace (uses Character.isWhitespace()).

59 Difference between Java 8 and Java 11? MID

Key differences for a senior developer:

AreaJava 8Java 11
String APINo isBlank(), lines(), strip()Added these
HTTP ClientHttpURLConnection (old)java.net.http (modern, async)
Local varNo varvar in lambdas
GCCMS (deprecated), G1 defaultZGC (experimental), Epsilon
Single fileMust compile firstjava File.java directly
Java EEjavax.* modules includedRemoved (must add as deps)
OptionalNo isEmpty(), or(), ifPresentOrElse()Added in 9/10
CollectionNo List.copyOf() etc.Added in 10
ModulesNot availableJPMS available (since 9)

Practically: Java 8 → Java 11 migration requires: removing Java EE dependencies (add explicitly), updating deprecated APIs, checking module compatibility.

60 What is HTTP Client API? MID

java.net.http (finalized in Java 11) – modern replacement for HttpURLConnection:

HttpClient client = HttpClient.newBuilder()
    .version(HttpClient.Version.HTTP_2)
    .connectTimeout(Duration.ofSeconds(10))
    .build();

HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.example.com/users"))
    .header("Accept", "application/json")
    .GET()
    .build();

// Synchronous
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());

// Asynchronous
CompletableFuture<HttpResponse<String>> future = client.sendAsync(request, HttpResponse.BodyHandlers.ofString());
future.thenApply(HttpResponse::body).thenAccept(System.out::println);

Features:

  • HTTP/1.1 and HTTP/2 support
  • WebSocket support
  • Async (CompletableFuture)
  • Reactive streams support
  • Authentication, cookie management, redirect policies
61 Java 12–13 useful features? ADVANCED

Java 12:

  • switch expressions (preview): yield keyword
  • String.indent(), String.transform()
  • Collectors.teeing() – combine two collectors

Java 13:

  • switch expressions (second preview)
  • Text blocks (preview): Multi-line strings
62 What are switch expressions? EXPERT

Switch expressions (finalized in Java 14) vs switch statements:

// Old switch statement (fall-through bug-prone)
String result;
switch (day) {
    case MONDAY: case TUESDAY: case WEDNESDAY:
        result = "Weekday"; break;
    case FRIDAY: case SATURDAY: case SUNDAY:
        result = "Weekend"; break;
    default: result = "Unknown";
}

// Java 14 switch expression (arrow form)
String result = switch (day) {
    case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> "Weekday";
    case SATURDAY, SUNDAY -> "Weekend";
};

// With blocks and yield
int numLetters = switch (day) {
    case MONDAY, FRIDAY, SUNDAY -> 6;
    case TUESDAY -> 7;
    default -> {
        String s = day.toString();
        yield s.length(); // yield returns value
    }
};

Benefits:

  • Expression (produces a value, can be used in assignments)
  • Exhaustiveness: Compiler checks all cases covered (for enums)
  • No fall-through: Arrow form doesn't fall through
  • Cleaner: No break statements

Pattern matching in switch (Java 21):

Object obj = ...;
String result = switch (obj) {
    case Integer i -> "Integer: " + i;
    case String s when s.length() > 5 -> "Long string";
    case String s -> "Short string";
    default -> "Other";
};
63 Java 14 records? ADVANCED

Records (finalized in Java 16) are immutable data carrier classes with concise syntax:

// Traditional immutable class (boilerplate)
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 x() { return x; }
    public int y() { return y; }
    @Override public boolean equals(Object o) { ... }
    @Override public int hashCode() { ... }
    @Override public String toString() { ... }
}

// Record
public record Point(int x, int y) {}
// Compiler auto-generates: constructor, accessors x(), y(), equals(), hashCode(), toString()

Customization:

public record Range(int min, int max) {
    // Compact constructor (validates)
    Range {
        if (min > max) throw new IllegalArgumentException("min > max");
    }
    
    // Custom method
    public int size() { return max - min; }
    
    // Custom accessor
    @Override public int min() { return Math.abs(min); } // override accessor
}

Records can:

  • Implement interfaces
  • Have static fields/methods
  • Have instance methods

Records cannot:

  • Extend classes (implicitly extend Record)
  • Be extended
  • Have instance fields beyond record components
  • Be abstract
64 Use cases of records? EXPERT
  1. DTOs (Data Transfer Objects): API request/response models
public record UserDTO(String name, String email, int age) {}
  1. Value objects (DDD): Domain primitives
public record Money(BigDecimal amount, Currency currency) {}
public record EmailAddress(String value) {
    EmailAddress { if (!value.contains("@")) throw new IllegalArgumentException(); }
}
  1. Multiple return values:
public record PagedResult<T>(List<T> items, int totalCount) {}
  1. Composite map keys:
record CacheKey(String region, Long id) {} // equals/hashCode auto-generated
Map<CacheKey, Object> cache = new HashMap<>();
  1. Intermediate stream results:
.map(person -> new PersonScore(person.name(), calculateScore(person)))
.filter(ps -> ps.score() > 50)

Records work well with pattern matching (Java 21):

if (obj instanceof Point(int x, int y)) { // deconstruct
    System.out.println("x=" + x + " y=" + y);
}
65 Java 15 sealed classes? ADVANCED

Sealed classes (finalized in Java 17) restrict which classes can extend or implement a class/interface:

public sealed class Shape
    permits Circle, Rectangle, Triangle {
}

public final class Circle extends Shape {
    private final double radius;
}

public non-sealed class Rectangle extends Shape { // can be freely extended
    private final double width, height;
}

public sealed class Triangle extends Shape
    permits EquilateralTriangle, RightTriangle { // further restricted
}

Permitted classes must be: in the same package (or module), and must be final, sealed, or non-sealed.

Benefits:

  1. Controlled hierarchy: Know all possible subtypes at compile time
  2. Pattern matching exhaustiveness: Compiler knows all cases
  3. Domain modeling: Express algebraic data types
double area = switch (shape) {
    case Circle c -> Math.PI * c.radius() * c.radius();
    case Rectangle r -> r.width() * r.height();
    case Triangle t -> calculateTriangleArea(t);
    // No default needed - compiler knows all cases!
};
66 Java 16 pattern matching? EXPERT

Pattern matching (finalized in Java 16 for instanceof):

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

// Java 16 pattern matching
if (obj instanceof String s) {
    System.out.println(s.length()); // s is in scope here
}

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

Pattern matching in switch (Java 21):

static String describe(Object obj) {
    return switch (obj) {
        case Integer i when i < 0 -> "Negative integer: " + i;
        case Integer i -> "Positive integer: " + i;
        case String s -> "String of length " + s.length();
        case int[] arr -> "int array of length " + arr.length;
        case null -> "null";
        default -> "Something else";
    };
}

Record patterns (Java 21):

if (obj instanceof Point(int x, int y) p) {
    System.out.println("Point at " + x + ", " + y);
}
67 Java 17 LTS significance? MID

Java 17 (September 2021) is an LTS (Long-Term Support) release – the most significant LTS since Java 11 and Java 8.

Why it matters:

  • Oracle provides 8+ years of support
  • Most enterprises use LTS releases in production
  • Spring Boot 3+ requires Java 17 minimum
  • Many frameworks aligned to Java 17 as baseline

New features finalized in Java 17:

  • Sealed classes (Java 15 preview)
  • Pattern matching for instanceof (Java 14 preview)
  • Records (Java 14 preview)
  • Switch expressions (Java 14)
  • Text blocks (Java 13 preview)

Deprecations/Removals:

  • Applet API deprecated for removal
  • Security Manager deprecated
  • RMI Activation removed
  • Strong encapsulation of JDK internals (Unsafe-like access restricted)
68 Garbage collection improvements post Java 8? ADVANCED
GCIntroducedCharacteristics
G1 (Garbage First)Java 7, default from 9Region-based, aims for predictable pause times
ZGCJava 11 (experimental), 15 (production)Sub-millisecond pauses, scalable to TBs
ShenandoahJava 12 (RedHat), 15 OpenJDKLow-pause, concurrent, scale with heap
EpsilonJava 11No-op GC, for performance testing

G1 improvements over time:

  • Java 10: Parallel Full GC
  • Java 12: Promptly return memory to OS
  • Java 14: NUMA-aware memory allocation

ZGC evolution:

  • Java 11: 4TB max heap, requires 8-core+
  • Java 15: Production-ready
  • Java 16: Thread-local handshakes, improved latency
  • Java 21: Generational ZGC (default: -XX:+UseZGC)
69 What are virtual threads? EXPERT

Virtual threads (Java 21 – Project Loom) are lightweight threads managed by the JVM, not the OS.

// Platform thread (expensive, OS-backed)
Thread.ofPlatform().start(() -> handleRequest());

// Virtual thread (cheap, JVM-managed)
Thread.ofVirtual().start(() -> handleRequest());

// With executor
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    for (Request req : requests) {
        executor.submit(() -> handle(req));
    }
}

Why it matters:

  • Platform threads: ~1MB stack, OS-limited (~thousands)
  • Virtual threads: ~few KB, JVM-limited (millions possible)
  • Virtual threads block without blocking OS thread – when a virtual thread blocks (I/O, sleep), the carrier OS thread is freed to run other virtual threads

One-thread-per-request model becomes viable again for high-concurrency I/O workloads.

Not a replacement for async: Same mental model as blocking code, but with performance of reactive. You write:

String response = httpClient.get(url); // looks blocking
// but virtual thread is suspended, OS thread is freed

Considerations:

  • Don't use with ThreadLocal that holds expensive resources (use ScopedValue instead)
  • synchronized blocks still pin virtual thread to carrier (use ReentrantLock for fine-grained locking)
  • Not beneficial for CPU-bound tasks (no concurrency gain)
70 Java 19+ Loom overview? EXPERT

Project Loom delivered over multiple releases:

  • Java 19: Virtual threads (preview)
  • Java 20: Virtual threads (second preview), Structured Concurrency (incubator)
  • Java 21: Virtual threads (GA!), Structured Concurrency (preview), Scoped Values (preview)

Structured Concurrency (Java 21 preview):

try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
    Future<User> user = scope.fork(() -> fetchUser(id));
    Future<Order> order = scope.fork(() -> fetchOrder(id));
    
    scope.join().throwIfFailed();
    
    return new Response(user.resultNow(), order.resultNow());
}
// scope.close() cancels subtasks if any fail

Benefits:

  • Subtask lifetimes are bound to parent scope
  • Error handling and cancellation are structured
  • Easier to reason about than CompletableFuture chains

Scoped Values: Thread-local alternative designed for virtual threads.

71 Impact of newer Java versions on Spring Boot? ADVANCED
  • Spring Boot 3.x requires Java 17 minimum (dropped Java 8/11 support)
  • Spring Boot 3.x is based on Spring Framework 6, which embraces Java 17 features (records as DTOs, sealed classes, etc.)
  • Virtual threads in Spring Boot 3.2+: spring.threads.virtual.enabled=true enables virtual thread executor
  • GraalVM native image support in Spring Boot 3 (ahead-of-time compilation)
  • Jakarta EE 10 (renamed from Java EE): javax.jakarta. namespace migration in Spring Boot 3
  • Pattern matching, records, text blocks used in Spring source and application code
72 Backward compatibility in Java? EXPERT

Java has a strong commitment to backward compatibility:

  • Programs compiled with Java 5 generally run on Java 21 JVM
  • --release flag: javac --release 8 Source.java compiles for Java 8 target
  • Deprecation process: Features deprecated before removal (usually multiple versions)

Breaking changes DO happen:

  • Strong encapsulation: --add-opens needed for deep reflection
  • Module system: Some internal APIs (sun., com.sun.) no longer accessible
  • Removed APIs: CMS GC removed (Java 14), Nashorn (Java 15), Applet API (Java 17)
  • JEE modules removed in Java 11

Tooling:

  • jdeprscan: Find usage of deprecated APIs
  • --illegal-access=warn: Identify reflective access that will break in future versions
  • Migration guides on OpenJDK site
73 Removed features in newer Java? ADVANCED
FeatureRemoved Version
PermGenJava 8 (replaced by Metaspace)
Java EE/CORBA modulesJava 11
Nashorn JS engineJava 15
CMS GCJava 14
RMI ActivationJava 15
Applet APIJava 17 (deprecated), removal pending
Security ManagerJava 17 (deprecated), pending
finalize()Java 18 (deprecated), pending
74 How do you choose Java version for production? MID

Decision factors:

  1. LTS vs non-LTS: Production → LTS (8, 11, 17, 21). Non-LTS for early adoption.
  2. Framework support: Spring Boot 3 → Java 17+. Most modern frameworks: Java 11+
  3. Vendor support lifecycle: Oracle, Amazon Corretto, Eclipse Temurin differ
  4. Team readiness: Training, code review awareness of new features
  5. Library compatibility: Third-party JARs may not support newer Java
  6. GC requirements: If you need ZGC/Shenandoah, Java 15+
  7. Virtual threads: Need Java 21
  8. Security patches: Older versions may lack backports

Current recommendation (2024): Java 21 for new projects (LTS, virtual threads, records, sealed classes, pattern matching all stable). Java 17 for projects on Spring Boot 3. Java 11 only if library constraints exist. Java 8 only if legacy systems force it.

75 How to migrate legacy Java apps? ADVANCED

Step-by-step migration approach:

  1. Assess: Run jdeprscan and jlink --list-modules. Identify deprecated API usage, illegal reflective access.
  1. Update dependencies: Upgrade all third-party libraries to versions compatible with target Java. Add javax.jakarta. if going to 11+ with Spring Boot 3.
  1. Fix compilation: Compile with --release <target>. Fix compiler errors.
  1. Handle module encapsulation: Add --add-opens JVM flags for necessary reflective access (temporary). Work to remove them over time.
  1. Update build tool: Maven/Gradle plugins must support target Java.
  1. Test thoroughly: Behavioral differences can be subtle. Especially concurrency, GC behavior.
  1. Incremental: Go 8 → 11 → 17 → 21 stepwise rather than jumping.
  1. JVM flags audit: Some old GC flags may be unrecognized (CMS flags removed). Use -XX:+PrintFlagsFinal to validate.
  1. Docker base image: Update to Java 17/21 base images.
  1. Feature adoption: After migration, gradually adopt new features (records, var, etc.) during refactoring.