Java 22: Stream Gatherers and Class-File API

Java 22: Stream Gatherers and Advanced APIs
Java 22 (March 2024) introduces stream gatherers for sophisticated stream operations and a public class-file API for bytecode manipulation. It's a non-LTS release with powerful tools for advanced developers.
1. Stream Gatherers (Preview)
Compositional stream transformations that go beyond traditional stream operations.
Before Gatherers:
// Complex stream operations were limited
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// Windowing - had to write custom code
List<List<Integer>> windows = new ArrayList<>();
for (int i = 0; i < numbers.size() - 1; i++) {
windows.add(numbers.subList(i, i + 2));
}
// Grouping consecutive items
// Complex manual logic requiredWith Gatherers (Java 22+):
// Powerful stream transformations
import java.util.stream.Gatherers;
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// Windowing operation
var windows = numbers.stream()
.gather(Gatherers.windowSliding(2))
.toList();
// Result: [[1,2], [2,3], [3,4], ..., [9,10]]
// Windowing with fixed size
var fixedWindows = numbers.stream()
.gather(Gatherers.windowFixed(3))
.toList();
// Result: [[1,2,3], [4,5,6], [7,8,9], [10]]
// Custom aggregation
var evenOddGroups = numbers.stream()
.gather(Gatherers.groupingBy(n -> n % 2 == 0 ? "even" : "odd"))
.toList();
// Scanning with state
var sums = numbers.stream()
.gather(Gatherers.scan(() -> 0, (acc, val) -> acc + val))
.toList();
// Result: [0, 1, 3, 6, 10, 15, ...]
// String tokenization
String text = "apple,banana,cherry,date";
var tokens = text.stream()
.gather(Gatherers.scan(
() -> new StringBuilder(),
(sb, c) -> {
if (c == ',') sb.delete(0, sb.length());
else sb.append(c);
return sb;
}
))
.filter(s -> !s.isEmpty())
.toList();Custom Gatherers:
// Creating custom gatherers
public class CustomGatherers {
// Gatherer for batching
static <T> Gatherer<T, ?, List<T>> batching(int batchSize) {
return Gatherer.of(
// Supplier: create accumulator
() -> new BatchAccumulator<T>(batchSize),
// Integrator: process element
(acc, element, downstream) -> {
acc.add(element);
if (acc.isFull()) {
downstream.push(acc.getBatch());
acc.reset();
}
return true;
},
// Finisher: flush remaining
(acc, downstream) -> {
if (!acc.isEmpty()) {
downstream.push(acc.getBatch());
}
}
);
}
static class BatchAccumulator<T> {
final int batchSize;
final List<T> batch = new ArrayList<>();
BatchAccumulator(int size) { this.batchSize = size; }
void add(T item) { batch.add(item); }
boolean isFull() { return batch.size() == batchSize; }
boolean isEmpty() { return batch.isEmpty(); }
List<T> getBatch() { return new ArrayList<>(batch); }
void reset() { batch.clear(); }
}
}
// Using custom gatherer
var batches = numbers.stream()
.gather(CustomGatherers.batching(3))
.toList();2. Class-File API (Preview)
Programmatic manipulation of Java class files.
// Reading class files
import java.lang.classfile.*;
import java.Lang.classfile.attribute.*;
public class ClassFileReader {
public static void readClassInfo(Path classPath) throws IOException {
ClassFile cf = ClassFile.of().read(classPath);
// Get class information
System.out.println("Class: " + cf.thisClass().asSymbol());
System.out.println("Superclass: " + cf.superclass().asSymbol());
// List methods
for (MethodModel method : cf.methods()) {
System.out.println("Method: " + method.methodName() +
method.methodType());
}
// List fields
for (FieldModel field : cf.fields()) {
System.out.println("Field: " + field.fieldName() + " " +
field.fieldType());
}
}
// Creating class files programmatically
public static void createSimpleClass() throws IOException {
var cv = ClassModel.of()
.withVersion(65, 0) // Java 21
.withFlags(ClassFile.ACC_PUBLIC | ClassFile.ACC_FINAL)
.withThisClass("com/example/Generated")
.withSuperclass(ClassDesc.of("java.lang.Object"))
.withMethod(MethodModel.of()
.withFlags(ClassFile.ACC_PUBLIC)
.withName("<init>")
.withDescriptor("()V")
.build()
)
.build();
// Write to file
Files.write(
Paths.get("Generated.class"),
cv.toByteArray()
);
}
}Dynamic Proxy Generation:
// More powerful than java.lang.reflect.Proxy
public class DynamicProxyGenerator {
public static Object createProxy(Class<?> interfaceClass) {
return ClassFile.of().build(
ClassDesc.of("Proxy$" + System.identityHashCode(interfaceClass)),
classBuilder -> {
classBuilder.withFlags(ClassFile.ACC_PUBLIC);
classBuilder.withSuperclass(ClassDesc.of("java.lang.Object"));
classBuilder.withInterfaceSymbols(
ClassDesc.of(interfaceClass.getName())
);
// Generate proxy methods
for (Method m : interfaceClass.getMethods()) {
classBuilder.withMethod(generateMethod(m));
}
}
);
}
static MethodModel generateMethod(Method m) {
return MethodModel.of()
.withFlags(ClassFile.ACC_PUBLIC)
.withName(m.getName())
.build();
}
}3. String Interpolation Enhancements
Further refinement to string templates.
// String templates with format specifiers (Preview)
String name = "Alice";
int score = 95;
// STR template (simple interpolation)
String msg1 = STR."Hello \{name}, score: \{score}";
// TEMPLATE string with expressions
String msg2 = STR."""
Name: \{name}
Score: \{score}
Grade: \{score >= 90 ? "A" : "B"}
""";
// Logging with templates
var log = STR."User \{name} logged in with score \{score}";
// Deferred evaluation for better performance4. Regular Expression Named Groups
Better regex support.
// Named groups in regex patterns
String email = "alice@example.com";
Pattern emailPattern = Pattern.compile(
"(?<name>\\w+)@(?<domain>\\w+\\.\\w+)"
);
Matcher matcher = emailPattern.matcher(email);
if (matcher.find()) {
String username = matcher.group("name"); // alice
String domain = matcher.group("domain"); // example.com
System.out.println(username + " at " + domain);
}5. Launch Multi-File Source-Code Programs
Run Java programs with multiple source files without compilation.
# Before Java 11: Had to compile first
javac Main.java Utils.java
java Main
# Java 22+: Run multiple files directly
java Main.java Utils.java
# Or entire directory
java src/Practical Example:
// Main.java
public class Main {
public static void main(String[] args) {
System.out.println(Utils.getMessage());
}
}
// Utils.java
public class Utils {
public static String getMessage() {
return "Hello from Utils!";
}
}
// Run without compilation
// java Main.java Utils.java6. Unnamed Variables (Finalization)
Underscore for explicitly unused variables.
// Using underscore for unused variables
for (var _ : list) {
System.out.println("Iteration");
}
// Catching but ignoring exceptions
try {
riskyOperation();
} catch (Exception _) {
// Intentionally ignored
}
// Lambda parameters
list.forEach((_x, _y) -> process());
// Record patterns
if (point instanceof Point(_, var y)) {
System.out.println("Y: " + y);
}Developer Impact
Powerful Tools:
- Gatherers for sophisticated stream operations
- Class-file API for bytecode generation
- Better regex and string handling
- Simpler script-like Java execution
Challenges:
- Learning curve for advanced APIs
- Class-file API is low-level
- Gatherers require functional thinking
Pros and Cons
Pros ✅
- Stream Gatherers: Powerful compositional operations
- Class-File API: Safer than bytecode libraries
- Flexibility: Fine-grained control over streams
- Regex Improvements: Named groups simplify patterns
- Script Support: Run Java like Python scripts
- Unnamed Variables: Clearer intent
Cons ❌
- Complex APIs: Class-file API steep learning curve
- Preview Features: May change
- No LTS: 6-month support window
- Performance: Dynamic class generation overhead
- Advanced Users Only: Not for typical development
Real-World Example
// Stream processing with gatherers
public class DataProcessor {
public void processStream(Stream<Integer> data) {
// Complex stream transformation
var result = data
.gather(Gatherers.windowFixed(10)) // Batch by 10
.map(batch -> batch.stream()
.mapToInt(Integer::intValue)
.sum()
)
.gather(Gatherers.scan(
() -> 0L,
(sum, value) -> sum + value
))
.filter(value -> value > 1000)
.toList();
System.out.println("Processed: " + result);
}
}Conclusion
Java 22 provides powerful APIs for advanced use cases. Stream gatherers make complex data transformations elegant. The class-file API democratizes bytecode manipulation. While non-LTS and developer-focused, Java 22 shows Java's continued evolution.
Recommendation:
- Explore if working with streams heavily
- Use for dynamic class generation if needed
- Don't adopt for production without Java 21 LTS as foundation
- Expect some features to be finalized in Java 23+