Recent Posts
Archives

Posts Tagged ‘StreamGatherers’

PostHeaderIcon [VoxxedDaysBucharest2026] Learning Modern Java the Playful Way: Marit van Dijk and Piotr Przybył on Embracing Recent Language Features

Lecturers

Marit van Dijk is a highly regarded Java Champion, Docker Captain, and Developer Advocate specializing in IDE productivity enhancements, Kotlin ecosystem contributions, and making advanced topics approachable for broad audiences. Piotr Przybył serves as Senior Developer Advocate at Elastic, holding Java Champion and Docker Captain distinctions. He is widely recognized for deep explorations of Java performance characteristics, language evolution, and practical development methodologies.

Abstract

Java’s consistent release rhythm introduces valuable innovations that can sometimes feel daunting to integrate, particularly for developers working with established codebases or transitioning from prior versions. Marit van Dijk and Piotr Przybył present an engaging, experimentation-focused approach to adopting contemporary Java capabilities. Through extensive live coding demonstrations within IntelliJ IDEA, they cover a rich selection of features including compact source files, enhanced records, stream gatherers, structured concurrency primitives, vector API computations, and documentation improvements. The session emphasizes immediate practical value, IDE assistance, and the joy of discovery.

The Motivation for Staying Current with Java Evolution

Java maintains a predictable six-month release cycle, consistently delivering enhancements across performance optimizations, concurrency abstractions, security improvements, and developer experience refinements. While Long-Term Support (LTS) releases provide stability crucial for risk-averse enterprises requiring extended commercial backing, the speakers encourage thoughtful consideration of intermediate versions for teams positioned to capitalize on their advancements.

They readily acknowledge legitimate concerns around upgrade processes, dependency compatibility, and organizational inertia. However, they highlight maturing ecosystem support—including comprehensive IDE tooling, community resources, and backward compatibility efforts—that significantly eases transitions. The overarching message promotes proactive engagement rather than passive waiting, positioning Java as a continually improving platform rather than a static legacy technology.

Learning modern Java is portrayed as an active, playful process reminiscent of childhood discovery. This perspective transforms potentially intimidating language additions into opportunities for creative exploration and immediate productivity gains, supported powerfully by modern development environments.

Syntactic Refinements: Compact Sources, Records, and Module Imports

Java 25’s introduction of compact source files and instance main methods marks a notable step toward reduced ceremony. Traditional requirements for explicit class declarations and static main entry points are relaxed, enabling concise scripts ideal for learning exercises, rapid prototyping, command-line utilities, and exploratory development. This change particularly benefits educational contexts and situations requiring quick experimentation.

Records, stabilized since Java 16, function as transparent, immutable carriers of data. They automatically provide canonical constructors, accessor methods, sensible equals, hashCode, and toString implementations, along with pattern matching support. Perfect for DTOs, value objects, and request/response models, records substantially reduce boilerplate and associated error opportunities. Developers retain the ability to customize behavior through explicit overrides when domain requirements demand specialized semantics.

Module imports offer further conciseness by permitting declarations such as import module java.base;, which encompasses the foundational JDK packages. This eliminates lengthy lists of individual import statements while preserving code clarity and avoiding namespace pollution. The IO utility class simplifies basic console input/output operations, replacing more verbose patterns involving Scanner and direct System.out usage.

Collectively, these syntactic improvements reflect Java’s ongoing commitment to balancing power with approachability, making the language more welcoming without compromising its industrial strengths.

Expressive Data Processing: Stream Gatherers and Parallel Operations

The stream gatherers feature, finalized in Java 24, significantly expands the expressiveness of the Streams API. Developers can now define custom intermediate operations that encapsulate complex processing logic, replacing ad-hoc combinations of filter, map, flatMap, and similar methods. This promotes creation of reusable, composable, and semantically rich pipelines suited to sophisticated data transformations.

A particularly valuable gatherer, mapConcurrent, enables safe parallel processing by leveraging virtual threads, avoiding traditional fork-join pool exhaustion issues common with parallel() streams in certain contexts. The speakers demonstrate practical applications ranging from straightforward data enrichment to more involved computations, highlighting improved readability and maintainability.

Concurrency Paradigms: Structured Scopes and Lazy Initialization

Structured concurrency, available as a preview feature in Java 26, introduces a more disciplined approach to managing concurrent operations. By structuring tasks within a StructuredTaskScope, developers achieve clearer code organization, automatic resource cleanup, and built-in cancellation semantics. This represents a substantial improvement over error-prone patterns relying on CompletableFuture compositions, reducing risks of thread leaks and orphaned computations.

Lazy constants provide controlled deferral of initialization until first access, contributing to faster startup times and more efficient resource utilization. Expanded pattern matching capabilities for primitive types enhance the power and readability of switch expressions and type checks across broader scenarios.

Performance Acceleration: Vector API and Documentation Innovations

Project Panama’s Vector API unlocks access to hardware vector instructions (SIMD), enabling significant performance improvements for data-parallel workloads without requiring low-level assembly coding. Through high-level Java abstractions, developers can express vectorized operations on arrays and other data structures, achieving throughput gains particularly beneficial in scientific computing, financial modeling, image processing, and machine learning applications.

The speakers present concrete benchmarks comparing scalar and vectorized implementations, illustrating real-world speedups. Documentation enhancements, specifically Markdown support within Javadoc comments introduced in Java 23, modernize API documentation practices. Rich formatting capabilities render effectively in contemporary IDEs, reducing the maintenance burden historically associated with HTML-based Javadoc.

Ecosystem Synergies and Forward Outlook

The presentation extends beyond core language features to examine synergies with development tools, community resources, and evolving practices. Modern IDEs like IntelliJ IDEA provide sophisticated support for preview features, refactoring assistance, and code analysis that accelerate adoption. Community engagement through user groups, conferences, and open-source contributions further enriches the learning experience.

The speakers encourage deliberate experimentation within personal or side projects as a low-risk pathway to building confidence before enterprise-wide rollout. They stress the importance of updating not merely version numbers but also mental models and coding idioms to fully leverage Java’s capabilities.

Java’s ongoing development demonstrates its continued relevance as a powerful, productive platform for contemporary software engineering. By embracing curiosity and playful exploration, developers can harness these advancements to enhance both personal satisfaction and professional effectiveness.

Links:

PostHeaderIcon [VoxxedDaysAmsterdam2026] Stream Tricks That You Don’t Wanna Miss: Enhancing Java Streams with Gatherers and String Templates in JDK 25

Lecturer
Aicha Laafia is a Java software engineer at Havana Group, currently based in France while originally from Morocco. She is passionate about sustainable technology, green programming, and advocating for greater representation of women in tech. Aicha actively participates in various communities, serves as a Women Techmakers and Girls Code ambassador, and facilitates IAmRemarkable workshops. She was recently promoted to Oracle ACE Associate, recognizing her contributions to the Java ecosystem.

Abstract
In this engaging session from Voxxed Days Amsterdam 2026, Aicha Laafia explores significant enhancements to Java’s stream processing capabilities and string handling introduced in JDK 25. She addresses longstanding pain points with traditional streams—such as the inability to maintain state mid-pipeline, complex custom collectors for batching or sliding windows, and error-prone string concatenation for SQL, JSON, or logs—through the new Stream Gatherers API and String Templates. Drawing on live code demonstrations and relatable examples from Formula 1 racing data, the presentation illustrates how these features introduce memory and statefulness to streams, simplify data transformations, and promote safer, more readable code. The talk underscores Java’s continued evolution toward more expressive and maintainable programming paradigms, encouraging developers to upgrade and share knowledge about these advancements.

The Persistent Challenges with Traditional Java Streams

Java developers have long appreciated streams for producing cleaner, more declarative, and expressive code compared to imperative loops. However, as Aicha points out, streams can occasionally leave programmers feeling frustrated or even “like complete idiots” when attempting advanced operations. The core limitation stems from the stateless nature of intermediate operations like map, filter, or flatMap. Each element processes independently and is immediately forgotten, making it impossible to track accumulated state, create overlapping windows, or group data mid-pipeline without terminating the stream via a collector.

Common pain points include manual batching implementations that rely on counters, lists, and careful index management to avoid off-by-one errors or lost elements. Grouping overlapping data—for instance, creating sliding windows of size n for rolling averages or trend detection—often requires intricate custom collectors that become difficult to understand or maintain over time, even for the original author. Furthermore, once a collector is applied, the pipeline ends; no further stream operations are possible afterward. These issues lead to verbose, error-prone code or a reluctant fallback to traditional for-loops, undermining the very benefits streams were meant to deliver.

Aicha emphasizes that these problems arise because prior to JDK 25, streams lacked “memory.” Elements flowed through independently without retaining context from previous items, forcing developers into workarounds that compromised readability and maintainability.

Introducing Stream Gatherers: Bringing Memory and Flexibility to Streams

JDK 25 addresses these limitations head-on with the Stream Gatherers API, which equips streams with stateful processing capabilities while remaining intermediate operations. Unlike terminal collectors, gatherers allow continued chaining after stateful transformations. A gatherer consists of up to four components, though only the integrator is mandatory:

  • Initializer (optional): Executes once before any elements arrive, establishing initial state such as an empty list or counter.
  • Integrator: The core logic, invoked for every element. It receives the current element, the mutable state, and a downstream consumer. Developers implement accumulation or transformation here, returning true to continue or false to short-circuit the pipeline.
  • Combiner (optional): Essential for parallel streams, merging partial states from different threads.
  • Finisher (optional): Runs once at the end of the stream, ensuring no residual state (such as an incomplete final batch) is lost by pushing any remaining elements downstream.

This design provides a short-circuit mechanism and supports parallel execution when a combiner is supplied. Aicha demonstrates creating a custom batching gatherer in roughly 15 lines of code—far simpler than equivalent custom collectors or manual loops. The initializer creates an empty list; the integrator adds elements until the batch size is reached, then pushes the batch downstream and clears the buffer; the finisher handles any trailing incomplete batch.

Even better, JDK 25 ships with five built-in gatherers that eliminate most custom implementations:

  • windowFixed(n): Produces non-overlapping batches of exactly size n, including a final potentially smaller batch.
  • windowSliding(n): Generates overlapping windows, ideal for rolling calculations, trend detection, or analyzing sequential data patterns in production monitoring.
  • scan: Accumulates intermediate results similar to a fold, emitting every partial value starting from an initial element—unlike reduce, which yields only the final result.
  • fold: Similar accumulation but treats the operation as intermediate, returning an Optional while permitting further pipeline chaining.
  • mapConcurrent(maxConcurrency, mapper): Executes the mapper on virtual threads (up to the specified concurrency limit) while preserving encounter order, making it particularly suited for I/O-bound tasks without manual thread management.

These tools transform previously cumbersome tasks into concise, readable one- or few-line operations.

Live Demonstration: Analyzing Formula 1 Data with Gatherers

To illustrate practical application, Aicha uses racing data from Max Verstappen’s 2025 Formula 1 season, modeled as a record containing round number, Grand Prix name, position, and points. She contrasts traditional approaches—often involving dozens of lines of custom collector code with initializer, accumulator, combiner, and finisher—with gatherer-based solutions.

For batching every three races to compute cumulative points and wins, a windowFixed(3) gatherer replaces extensive custom logic, producing clean batches while automatically handling the final incomplete group. Sliding windows demonstrate overlapping views, such as performance trends across consecutive race triplets, again in just a few lines.

Accumulation across the entire season uses scan to emit running totals after each race, revealing Verstappen’s final 421 points and near-miss championship outcome. These examples highlight how gatherers retain “memory” of prior elements, enabling stateful yet fluent pipelines.

Aicha also touches on String Templates, another JDK 25 feature that enhances safety and readability. Traditional string concatenation or String.format often leads to injection vulnerabilities in SQL or JSON and creates “plus soup” that is hard to read. String Templates provide a clean, type-safe interpolation mechanism that reduces errors and improves security for logging, queries, and data serialization.

Implications and Recommendations for Modern Java Development

The introduction of gatherers and string templates reflects Java’s ongoing commitment to evolving without breaking compatibility, offering developers more powerful abstractions while preserving the language’s robustness. By reducing reliance on custom collectors and imperative workarounds, these features promote more maintainable, expressive codebases that are easier to reason about and debug.

Gatherers particularly shine in data processing pipelines, analytics, monitoring, and any domain requiring windowed or accumulated views. Their support for parallelism and short-circuiting adds efficiency, while the built-in variants cover the majority of common use cases, lowering the barrier to advanced stream usage.

Aicha encourages the community to upgrade to the latest JDK, experiment with these capabilities, write articles, and deliver talks to spread awareness. She notes that many scenarios previously abandoned to “for-loop hell” now become elegant stream solutions thanks to gatherers.

Code Sample: Batching with windowFixed

// Traditional complex collector approach omitted for brevity

// With Gatherers in JDK 25
var batches = races.stream()
    .gather(Gatherers.windowFixed(3))
    .map(batch -> computeStats(batch))  // e.g., sum points, count wins
    .toList();

Code Sample: Sliding Window for Trends

var slidingWindows = races.stream()
    .gather(Gatherers.windowSliding(3))
    .map(window -> analyzeTrend(window))
    .toList();

Code Sample: Accumulation with scan

var runningTotals = pointsStream
    .gather(Gatherers.scan(() -> 0, Integer::sum))
    .toList();  // Emits every intermediate sum

These snippets demonstrate the dramatic reduction in complexity while preserving full pipeline fluency.

In conclusion, Aicha Laafia’s presentation provides both a clear diagnosis of historical stream limitations and a compelling vision for their resolution in JDK 25. By incorporating statefulness through gatherers and safer string handling, Java strengthens its position as a modern, versatile language suitable for complex data-driven applications. Developers who adopt these features will benefit from shorter, more readable code, fewer maintenance headaches, and enhanced productivity.

Links: