Virtual Threads
Virtual threads enable high-throughput concurrent applications with lightweight threads.
<code>
// Creating and starting a virtual thread
Thread virtualThread = Thread.ofVirtual().start(() -> {
System.out.println("Hello from virtual thread!");
});
virtualThread.join();
// Creating virtual threads with custom names
Thread.Builder builder = Thread.ofVirtual().name("MyVirtualThread");
Runnable task = () -> System.out.println("Running in: " + Thread.currentThread().getName());
Thread thread = builder.start(task);
thread.join();
// Using ExecutorService with virtual threads
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 1000; i++) {
int taskId = i;
executor.submit(() -> {
System.out.println("Task " + taskId + " running on virtual thread");
});
}
} // Executor automatically closes and waits for tasks
</code>
Record Patterns
Record patterns simplify extracting data from records without explicit accessor method calls
<code>
// Define records
public record Position(int x, int y) {}
public record Path(Position from, Position to) {}
// Using record patterns with instanceof
public void printPosition(Object obj) {
if (obj instanceof Position(int x, int y)) {
System.out.printf("Position: x=%d, y=%d%n", x, y);
}
}
// Nested record patterns
public void printPath(Object obj) {
if (obj instanceof Path(Position(int x1, int y1), Position(int x2, int y2))) {
System.out.printf("Path from (%d,%d) to (%d,%d)%n", x1, y1, x2, y2);
}
}
// With different record types
public sealed interface Position permits Position2D, Position3D {}
public record Position2D(int x, int y) implements Position {}
public record Position3D(int x, int y, int z) implements Position {}
public void print(Object obj) {
switch (obj) {
case Path(Position2D from, Position2D to) ->
System.out.printf("2D path: %d/%d -> %d/%d%n",
from.x(), from.y(), to.x(), to.y());
case Path(Position3D from, Position3D to) ->
System.out.printf("3D path: %d/%d/%d -> %d/%d/%d%n",
from.x(), from.y(), from.z(), to.x(), to.y(), to.z());
default -> System.out.println("Unknown type");
}
}
</code>
Pattern Matching for switch
Pattern matching for switch enables type checking and extraction in switch statements.
<code>
// Pattern matching with guards
public void processObject(Object obj) {
switch (obj) {
case String s when s.length() > 5 ->
System.out.println(s.toUpperCase());
case String s ->
System.out.println(s.toLowerCase());
case Integer i ->
System.out.println(i * i);
case Position(int x, int y) ->
System.out.println(x + "/" + y);
default -> System.out.println("Unknown type");
}
}
// Exhaustive switch with sealed types
public sealed interface Shape permits Rectangle, Circle {}
public record Rectangle(Position topLeft, Position bottomRight) implements Shape {}
public record Circle(Position center, int radius) implements Shape {}
public void debugShape(Shape shape) {
switch (shape) {
case Rectangle r ->
System.out.printf("Rectangle: %s to %s%n", r.topLeft(), r.bottomRight());
case Circle c ->
System.out.printf("Circle: center=%s, radius=%d%n", c.center(), c.radius());
// No default needed - compiler checks exhaustiveness
}
}
// Qualified enum constants
public sealed interface Direction permits CompassDirection, VerticalDirection {}
public enum CompassDirection implements Direction { NORTH, SOUTH, EAST, WEST }
public enum VerticalDirection implements Direction { UP, DOWN }
void fly(Direction direction) {
switch (direction) {
case CompassDirection.NORTH -> System.out.println("Flying north");
case CompassDirection.SOUTH -> System.out.println("Flying south");
case CompassDirection.EAST -> System.out.println("Flying east");
case CompassDirection.WEST -> System.out.println("Flying west");
case VerticalDirection.UP -> System.out.println("Gaining altitude");
case VerticalDirection.DOWN -> System.out.println("Losing altitude");
}
}
</code>
Sequenced Collections
Sequenced collections provide direct access to first and last elements.
<code>
// Working with List
List<String> list = new ArrayList<>(List.of("A", "B", "C", "D"));
String first = list.getFirst(); // "A"
String last = list.getLast(); // "D"
list.addFirst("Z");
list.addLast("E");
String removed = list.removeFirst();
// Working with LinkedHashSet
LinkedHashSet<String> set = new LinkedHashSet<>(List.of("One", "Two", "Three"));
String firstElement = set.getFirst(); // "One"
String lastElement = set.getLast(); // "Three"
// Reversed view
List<String> events = List.of("Event1", "Event2", "Event3");
for (String event : events.reversed()) {
System.out.println(event); // Prints: Event3, Event2, Event1
}
// Working with Deque
Deque<String> deque = new ArrayDeque<>();
deque.addFirst("Java 17");
deque.addLast("Java 21");
String front = deque.getFirst(); // "Java 17"
String back = deque.getLast(); // "Java 21"
// SequencedMap
LinkedHashMap<String, Integer> map = new LinkedHashMap<>();
map.put("first", 1);
map.put("second", 2);
map.put("third", 3);
Map.Entry<String, Integer> firstEntry = map.firstEntry(); // "first"=1
Map.Entry<String, Integer> lastEntry = map.lastEntry(); // "third"=3
map.putFirst("zero", 0);
map.putLast("fourth", 4);
</code>
String Templates (Preview)
String templates allow embedding expressions within strings for cleaner code.
<code>
// Basic string template
int a = 5;
int b = 10;
String result = STR."{a} times {b} = {Math.multiplyExact(a, b)}";
// Result: "5 times 10 = 50"
// Multi-line string template
String orderId = "12345";
String orderDate = "2025-11-02";
String product = "pens";
int quantity = 100;
String message = STR."""
Order processed successfully:
Order ID: {orderId}, placed on {orderDate}
{product}, {quantity} ({product.contains("pens") ? "dozens" : "units"})
Total Price: {calculatePrice(product, quantity)}
""";
// Method that returns values
double calculatePrice(String product, int quantity) {
return quantity * 2.5;
}
// Complex expressions in templates
String name = "John";
int age = 30;
String info = STR."Name: {name.toUpperCase()}, Age: {age}, Adult: {age >= 18}";
</code>
Unnamed Patterns and Variables (Preview)
Use underscore _ to ignore unused variables.
<code>
// Unused exception variable
try {
int number = Integer.parseInt(string);
} catch (NumberFormatException _) {
System.err.println("Not a number");
}
// Unused lambda parameter
Map<String, List<String>> map = new HashMap<>();
map.computeIfAbsent(key, _ -> new ArrayList<>()).add(value);
// Unused pattern variable
if (object instanceof Path(Position(int x1, int y1), _)) {
System.out.printf("Path starting at x=%d, y=%d%n", x1, y1);
}
// Multiple unused variables
try {
// Some code
} catch (IOException | SQLException _) {
System.err.println("Error occurred");
}
</code>
New String Methods
Enhanced string manipulation methods.
<code>
// indexOf with range
String text = "Hello World Hello";
int index = text.indexOf("Hello", 5, 15); // Search between indices 5 and 15
// splitWithDelimiters - includes delimiters in result
String sentence = "the red brown fox jumps over the lazy dog";
String[] parts = sentence.splitWithDelimiters(" ", 5);
// Result: ["the", " ", "red", " ", "brown", " ", "fox", " ", "jumps over the lazy dog"]
System.out.println(Arrays.toString(parts));
</code>
StringBuilder/StringBuffer Enhancements
Repeat operations for efficient string building.
<code>
// Repeat CharSequence
StringBuilder sb1 = new StringBuilder();
sb1.repeat("Hello ", 3); // "Hello Hello Hello "
System.out.println(sb1);
// Repeat character/code point
StringBuilder sb2 = new StringBuilder();
sb2.repeat('*', 10); // "**********"
sb2.repeat(0x1f600, 5); // Five emoji faces
sb2.repeat('!', 3); // "!!!"
System.out.println(sb2);
// Practical example
StringBuilder divider = new StringBuilder();
divider.repeat("-=", 20); // "-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-="
</code>
Character Emoji Methods
Check emoji properties.
<code>
// Check if character is an emoji
boolean isEmoji = Character.isEmoji('😀'); // true
boolean isNotEmoji = Character.isEmoji('A'); // false
// Check if emoji can have modifier (like skin tone)
boolean canModify = Character.isEmojiModifierBase('✋'); // true
// Check emoji presentation
int codePoint = "😀".codePointAt(0);
boolean hasEmojiPresentation = Character.isEmojiPresentation(codePoint);
// Check if it's an emoji component
boolean isComponent = Character.isEmojiComponent(0x1F3FB); // Skin tone modifier
</code>
Math.clamp() Method
Clamp values within a range.
<code> // Clamp integer values int value = 150; int clamped = Math.clamp(value, 0, 100); // Result: 100 // Clamp to ensure value is in range int score = -10; int validScore = Math.clamp(score, 0, 100); // Result: 0 // Clamp double values double temperature = 105.5; double safeTempC = Math.clamp(temperature, -50.0, 50.0); // Result: 50.0 // Practical usage int brightness = getUserInput(); int finalBrightness = Math.clamp(brightness, 0, 255); </code>
HttpClient Lifecycle Management
Enhanced lifecycle control for HttpClient.
<code>
// Auto-closeable HttpClient
try (HttpClient client = HttpClient.newHttpClient()) {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/data"))
.build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
} // Automatically closes when done
// Manual shutdown
HttpClient client = HttpClient.newHttpClient();
// ... use the client
client.shutdown(); // Initiates graceful shutdown
// Wait for termination with timeout
if (client.awaitTermination(Duration.ofMinutes(1))) {
System.out.println("Client terminated successfully");
} else {
System.out.println("Client termination took longer than expected");
}
// Immediate shutdown
client.shutdownNow(); // Forces immediate shutdown
// Check termination status
if (client.isTerminated()) {
System.out.println("Client has terminated");
}
</code>
Unnamed Classes and Instance Main Methods (Preview)
Simplified program entry point.
<code>
// Simple main method - complete Java program
void main() {
System.out.println("Hello World!");
}
// With fields and helper methods
final String GREETING_TEMPLATE = "Hello %s!";
void main() {
System.out.println(greet("World"));
}
String greet(String name) {
return GREETING_TEMPLATE.formatted(name);
}
// Traditional main still works
public static void main(String[] args) {
System.out.println("Traditional main method");
}
</code>