Reactive Streams and Flow API

Reactive Streams and Flow API in Java: A Complete Guide

In the world of modern applications, handling massive volumes of data with scalability and efficiency is crucial. Asynchronous, non-blocking data processing is no longer just an option — it’s a necessity.

This is where Reactive Streams and Java’s Flow API step in.

In this post, you will learn everything you need to know about Reactive Streams and the Flow API introduced in Java 9 — concepts, benefits, practical examples, and more.




Table of Contents

  • What Are Reactive Streams?

  • Problems with Traditional Streams

  • Goals of Reactive Streams

  • Core Concepts: Publisher, Subscriber, Subscription, Processor

  • The Java 9 Flow API

  • Implementing Reactive Streams with Flow API

  • Real-world Example

  • Benefits of Reactive Programming

  • Reactive Streams vs Traditional Streams

  • Conclusion


What Are Reactive Streams?

Reactive Streams is a standard for asynchronous stream processing with non-blocking backpressure.

In simple terms, it allows one component to produce data at its own pace while ensuring the consumer isn't overwhelmed.
It’s about controlling data flow — smartly.

Reactive Streams is not tied to any specific library. It’s a specification, and many libraries implement it, like:

  • Project Reactor (by Spring)

  • RxJava

  • Akka Streams

🔹 Key Idea: Make systems more resilient, responsive, and scalable.


Problems with Traditional Streams

Traditional Java Streams (like in Java 8) are:

  • Synchronous: Operations happen in the same thread.

  • Blocking: Wait for each data element to be processed.

  • Uncontrolled: Cannot handle overwhelming amounts of data (no backpressure).

In high-load environments (e.g., server apps, real-time dashboards), these issues can lead to:

  • High memory consumption

  • Slow response times

  • System crashes

Thus, Reactive Streams and Flow API were needed.


Goals of Reactive Streams

The Reactive Streams initiative aims to:

Goal Description
Non-Blocking Processing should not block threads unnecessarily.
Asynchronous Data can flow across threads or systems asynchronously.
Backpressure Support Consumer can signal to the producer how much data it can handle.
Minimal Overhead Performance should not degrade under load.
Interoperability Different libraries should easily connect (standard interface).

Core Concepts of Reactive Streams

Reactive Streams define four interfaces:

1. Publisher

  • Produces data.

  • Sends data to Subscribers according to their demand.

public interface Publisher<T> {
    void subscribe(Subscriber<? super T> subscriber);
}

2. Subscriber

  • Consumes data.

  • Signals how much data it can process.

public interface Subscriber<T> {
    void onSubscribe(Subscription subscription);
    void onNext(T item);
    void onError(Throwable throwable);
    void onComplete();
}

3. Subscription

  • A link between Publisher and Subscriber.

  • Allows Subscriber to control the flow by requesting items.

public interface Subscription {
    void request(long n);
    void cancel();
}

4. Processor

  • Both Subscriber and Publisher.

  • Can transform data in between.

public interface Processor<T, R> extends Subscriber<T>, Publisher<R> {}

The Java 9 Flow API

Java 9 adopted the Reactive Streams specification through a new package:
java.util.concurrent.Flow

It introduced the same four interfaces:

  • Flow.Publisher

  • Flow.Subscriber

  • Flow.Subscription

  • Flow.Processor

This means Java developers now have a standard, built-in way to work with asynchronous data streams!

🔵 Good to Know: You still need to implement your own classes or use libraries like Reactor or RxJava for production use.


Implementing Reactive Streams with Flow API

Let’s create a simple example using Flow API!

Step 1: Create a Publisher

import java.util.concurrent.Flow;

public class SimplePublisher implements Flow.Publisher<String> {
    private Flow.Subscriber<? super String> subscriber;

    @Override
    public void subscribe(Flow.Subscriber<? super String> subscriber) {
        this.subscriber = subscriber;
        subscriber.onSubscribe(new SimpleSubscription(subscriber));
    }
}

Step 2: Create a Subscription

class SimpleSubscription implements Flow.Subscription {
    private final Flow.Subscriber<? super String> subscriber;
    private boolean canceled = false;

    public SimpleSubscription(Flow.Subscriber<? super String> subscriber) {
        this.subscriber = subscriber;
    }

    @Override
    public void request(long n) {
        for (int i = 0; i < n && !canceled; i++) {
            subscriber.onNext("Item " + i);
        }
        subscriber.onComplete();
    }

    @Override
    public void cancel() {
        canceled = true;
    }
}

Step 3: Create a Subscriber

public class SimpleSubscriber implements Flow.Subscriber<String> {
    private Flow.Subscription subscription;

    @Override
    public void onSubscribe(Flow.Subscription subscription) {
        this.subscription = subscription;
        subscription.request(5); // Request 5 items
    }

    @Override
    public void onNext(String item) {
        System.out.println("Received: " + item);
    }

    @Override
    public void onError(Throwable throwable) {
        throwable.printStackTrace();
    }

    @Override
    public void onComplete() {
        System.out.println("All items received");
    }
}

Step 4: Connect Publisher and Subscriber

public class Main {
    public static void main(String[] args) {
        SimplePublisher publisher = new SimplePublisher();
        SimpleSubscriber subscriber = new SimpleSubscriber();
        
        publisher.subscribe(subscriber);
    }
}

Output:

Received: Item 0
Received: Item 1
Received: Item 2
Received: Item 3
Received: Item 4
All items received

Real-world Example: Streaming Data from a Database

Imagine a server that streams 1 million records from a database to a web client.
Without backpressure, it could run out of memory.

With Reactive Streams:

  • Server fetches 1000 records at a time.

  • Sends them only when the client asks for it.

  • Avoids memory crashes.

  • Maintains smooth user experience.

Frameworks like Spring WebFlux and Project Reactor internally use Reactive Streams for this!


Benefits of Reactive Programming

Benefit Explanation
Scalability Handle thousands of connections efficiently.
Resilience System remains responsive under high load.
Resource Optimization Threads are used effectively without blocking.
Flexibility Easier to create real-time, event-driven applications.

Reactive Streams vs Traditional Streams

Feature Traditional Streams Reactive Streams
Processing Synchronous, blocking Asynchronous, non-blocking
Thread usage One thread at a time Multiple threads
Backpressure No Yes
Suitable for Small data sets High-load, real-time systems

Conclusion

Reactive Streams and the Flow API revolutionized how Java developers handle asynchronous data processing.

By introducing a standard way to implement non-blocking, backpressured streams, they enable the development of scalable, efficient, and responsive systems.

Whether you are building web applications, real-time dashboards, or microservices, Reactive Programming is an essential skill to master in today’s world.

👉 Start small, practice with examples, and soon you’ll be designing truly reactive systems with ease!




Previous
Next Post »