Sunday, March 30, 2025

Java 24: Scoped Values

Java 24 introduces Scoped Values, a powerful alternative to ThreadLocal that offers better performance and cleaner code for managing per-thread data. This is a preview language feature.

Here's an example of ScopedValue in action:

private static final ScopedValue<String> USER_ID = ScopedValue.newInstance();

public void handle(Request req, String userId) {
  ScopedValue.where(USER_ID, userId)
    .run(() -> handle(req));
}

private void handle(Request req) {
  String data = getData(req);
  // Do something else
}

private String getData(Request req) {
  return runQuery(req, USER_ID.get());
}

As shown above, ScopedValue provides a means to pass data (the userId) securely to a faraway method without using method parameters. The faraway method can access the data via the ScopedValue object. This eliminates the need to pass additional parameters explicitly through multiple method calls.

ScopedValue vs ThreadLocal
  • Scoped Values are immutable once set inside ScopedValue.where(...). On the other hand, ThreadLocal allows values to be changed at any time, which can lead to inconsistent state across different parts of a request.
  • Scoped Values are automatically removed after the scope ends, whereas ThreadLocal requires an explicit call to remove() to avoid memory leaks, especially in thread pools.
  • Scoped Values bind data to a specific execution scope, ensuring that when a new task starts on a thread, it doesn’t inherit values from a previous request. ThreadLocal stores data at the thread level, meaning values persist across multiple tasks when using a thread pool.
  • Scoped Values work well with virtual threads and structured concurrency APIs.

For comparison, here's the same example using ThreadLocal:

private static final ThreadLocal<String> USER_ID = new ThreadLocal<>();

public void handle(Request req, String userId) {
  try {
    USER_ID.set(userId);
    handle(req);
  } finally {
    USER_ID.remove(); // to prevent memory leaks
  }
}

private void handle(Request req) {
  String data = getData(req);
  // Do something else
}

private String getData(Request req) {
  return runQuery(req, USER_ID.get());
}

While ThreadLocal still has its uses, most new applications will benefit from Scoped Values’ immutability, automatic cleanup, and better thread management.

Friday, March 28, 2025

Java 24: Primitive Types in Patterns, instanceof, and switch

Java 24 introduces enhancements to pattern matching by allowing primitive types in all pattern contexts, and extending instanceof and switch to work with all primitive types. This is a preview language feature.

Previously, pattern matching for switch only supported reference types such as Integer i, but now it supports primitive types too. For example:

int i = 100;
String s = switch(i) {
  case 1 -> "one";
  case 2 -> "two";
  case int i when i > 2 -> "too big";
  default -> "unsupported";
}

Similarly, instanceof has been enhanced to support primitives, as shown in the example below:

int i = 1;
if (i instanceof byte b) {
  // i has been cast to byte and assigned to b
}
Related posts:
Java 19: Record Patterns
Java 17: Pattern Matching for Switch
Java 14: Pattern Matching for instanceof