What’s New in JDK 24? Top Preview Features and How to Use Them
JDK 24 introduces a suite of preview enhancements—including a simplified main method, primitive type pattern matching, flexible constructors, scoped values, module import declarations, structured concurrency, and ahead‑of‑time class loading—that improve Java performance, readability, and developer productivity ahead of the upcoming JDK 25 LTS release.
JDK 24 is a significant release because many preview features are likely to become part of JDK 25, scheduled for September 2025.
JDK 25 will be the next long‑term support (LTS) version after JDK 21.
JDK 24 brings notable performance and syntax improvements. All demo code uses preview features and requires the
--enable-previewflag at compile and run time.
Additional notes: LTS means long‑term support; preview features are not final and may change; they must be enabled both when compiling and running.
1. Simple source file and instance main method
JEP 495 introduces a new
void main()entry point that allows a concise “Hello, World” program.
<code>public class MyFirstClass {
public static void main(String[] args) {
System.out.println("Hello, World");
}
}</code>The traditional version can be verbose for beginners.
With JDK 24 the program can be written as:
<code>void main() {
println("Hello, World");
}</code>Experienced developers may still use the classic form.
The JVM now automatically imports certain packages, so calls like
println()no longer require explicit imports.
A more complete example that reads a name from the console and prints a greeting:
<code>void main() {
var name = readln();
var message = "Hello, World and " + name;
println(message);
}</code>When written with explicit imports the code looks like:
<code>import java.io.*;
public class A {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String name = sc.nextLine();
String message = "Hello, World and " + name;
System.out.println(message);
sc.close();
}
}</code>2. instanceof and Switch‑Case primitive type matching
JEP 488 allows
instanceofand pattern‑matching switch cases to match primitive types as well as reference types.
<code>public class Test {
public static String identifyType(Object value) {
if (value instanceof int) {
return String.format("int: %d", value);
} else if (value instanceof String) {
return String.format("String: %s", value);
} else if (value instanceof Double) {
return String.format("Double: %f", value);
} else {
return "Unknown type";
}
}
}</code> <code>String identifyType(Object value) {
return switch (value) {
case int i -> String.format("int: %d", i);
case Double d -> String.format("Double: %f", d);
case String s -> String.format("String: %s", s);
default -> "Unknown type";
};
}</code>3. Flexible constructor bodies
JEP 492 separates a constructor into a
Prologue(logic before
super()) and an
Epilogue(logic after
super()).
<code>public class A extends B {
public A() {
// Prologue
super();
// Epilogue
}
}</code>Example with computed values before calling the superclass:
<code>public class A extends B {
private static int multiplier = 10;
private final int childValue;
public A(int baseValue) {
// Prologue
int computedValue = baseValue * multiplier;
super(baseValue); // initialize B
// Epilogue
this.childValue = computedValue;
}
}</code>4. Scoped Values
ScopedValue (JEP 487) improves on ThreadLocal by sharing immutable data across threads and tasks, automatically cleaning up after the owning thread finishes.
Mutability: ScopedValue only allows immutable data, avoiding accidental side effects.
Unbounded lifetime: Values are cleared automatically when the owning thread ends.
Costly inheritance: ScopedValue shares memory efficiently between parent and child threads.
Simple demonstration:
<code>static final ScopedValue<String> USERNAME = ScopedValue.newInstance();
void main(String[] args) {
ScopedValue.where(USERNAME, "Amrit").run(() -> {
System.out.println("Value inside main task: " + USERNAME.get());
performSubTask();
});
}
static void performSubTask() {
System.out.println("Value inside sub‑task: " + USERNAME.get());
}</code>5. Module import declarations
JEP 494 lets a module import an entire set of packages with a single statement, reducing boilerplate.
Old style:
<code>import javax.xml.*;
import javax.xml.parsers.*;
import javax.xml.stream.*;</code>New style:
<code>import module java.xml;</code>Other modules such as java.base, java.desktop, and java.sql provide similar grouped imports.
6. Structured concurrency
JEP 499 introduces StructuredTaskScope, which treats a group of subtasks as a single unit so that failure of any subtask propagates to the whole task.
<code>try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Supplier<Integer> x = scope.fork(() -> fun1());
Supplier<Integer> y = scope.fork(() -> fun2());
scope.join().throwIfFailed();
} catch (Exception e) {
println("Both Fun 1 and Fun 2 are stopped because Fun 1 failed");
}</code>Contrast with the traditional ExecutorService approach, which does not automatically cancel sibling tasks.
7. Ahead‑of‑time class loading and linking
JEP 483 enables AOT class loading and linking, caching loaded classes to improve startup time and providing up to 40 % performance gains for Java applications.
More JDK 24 updates
The author lists the most useful preview features for Java developers and points to the official JDK 24 release page for additional candidates: https://openjdk.org/projects/jdk/24/.
Big Data Technology Tribe
Focused on computer science and cutting‑edge tech, we distill complex knowledge into clear, actionable insights. We track tech evolution, share industry trends and deep analysis, helping you keep learning, boost your technical edge, and ride the digital wave forward.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.