Enterprise Java

Mastering Backpressure in Spring WebFlux for Streaming APIs

How to Handle High-Throughput Streaming Scenarios Without Overwhelming Consumers

Reactive systems excel at handling streaming data, but without careful backpressure management, you risk overwhelming downstream consumers, running out of memory, or introducing unacceptable latency.

Spring WebFlux, part of the Spring ecosystem since version 5, is built on Project Reactor, which natively supports reactive streams and backpressure-aware processing. This guide shows you how to:

  • Understand how backpressure works in WebFlux and Reactor
  • Control data flow in high-throughput streaming APIs
  • Use practical patterns to avoid buffer overflows and slow subscribers
  • Implement end-to-end backpressure handling with code examples

Let’s learn how to build resilient reactive pipelines.

Why Backpressure Matters

In classic synchronous APIs, the server blocks until the client is ready. In streaming APIs, producers may generate data faster than consumers can process it.

Backpressure provides a mechanism to signal:

“Please slow down—I’m not ready for more data yet.”

If you ignore backpressure:

  • Buffers fill up
  • Latency spikes
  • Out-of-memory errors occur

Proper backpressure handling ensures your application remains responsive under load.

Prerequisites

  • Java 11+ (Java 17 recommended)
  • Spring Boot 2.5+ or 3.x
  • Familiarity with Project Reactor (Flux, Mono)
  • Basic understanding of reactive programming concepts

1️⃣ How Backpressure Works in Reactor

At the core of Project Reactor is the Reactive Streams specification, which defines how publishers and subscribers communicate:

  • Publisher emits data
  • Subscriber consumes data
  • Subscriber requests a specific number of items
  • Publisher honors that request and does not emit more than requested

This request signaling is what backpressure is all about.

Example:

Flux<Integer> numbers = Flux.range(1, 1_000_000);

numbers.subscribe(new BaseSubscriber<>() {
    @Override
    protected void hookOnSubscribe(Subscription subscription) {
        // Request 10 items initially
        request(10);
    }

    @Override
    protected void hookOnNext(Integer value) {
        System.out.println("Received: " + value);
        // Request the next item one by one
        request(1);
    }
});

This subscriber explicitly controls how many elements it wants.

2️⃣ Typical Backpressure Pitfalls

Problem #1: Consuming a large Flux without limiting request size:

@GetMapping(value = "/numbers", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<Integer> streamNumbers() {
    return Flux.range(1, 1_000_000);
}

This will push 1 million items as fast as possible—if the client is slow, you will quickly fill buffers.

3️⃣ Applying Rate Limiting with limitRate

One of the simplest ways to manage backpressure is using limitRate():

return Flux.range(1, 1_000_000)
        .limitRate(10);

This splits the data flow into chunks—by default, it requests 75% of the limit before re-requesting more.

How it works:

  • First requests 10 elements.
  • Once 7 are processed (75%), requests 7 more.
  • Keeps this window moving.

4️⃣ Buffering and Dropping Strategies

Sometimes you need to buffer or drop excess items:

Buffering

return fastProducer
        .onBackpressureBuffer(1000); // Buffer up to 1000 elements

Warning: If your consumer is too slow, buffers will eventually fill.

Dropping

return fastProducer
        .onBackpressureDrop(item -> {
            log.warn("Dropping item {}", item);
        });

This approach drops data that can’t be processed—suitable for telemetry or logs where occasional loss is acceptable.

5️⃣ Applying Backpressure in Streaming APIs

Example Controller

Let’s build an endpoint emitting timestamps every 10ms, which risks overwhelming clients:

@GetMapping(value = "/timestamps", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> streamTimestamps() {
    return Flux.interval(Duration.ofMillis(10))
            .map(tick -> "Time: " + Instant.now())
            .onBackpressureDrop(t -> log.warn("Dropped tick {}", t))
            .limitRate(100);
};

What happens here:

  • Flux.interval is fast (emits every 10ms)
  • onBackpressureDrop discards events if the client can’t keep up
  • limitRate further controls demand

6️⃣ Client-Side Flow Control

If your client is another WebFlux app or a WebClient, it can control how fast it consumes:

WebClient client = WebClient.create();

Flux<String> stream = client.get()
        .uri("http://localhost:8080/timestamps")
        .retrieve()
        .bodyToFlux(String.class)
        .limitRate(50); // Limit processing rate

stream.subscribe(System.out::println);

Even if the server produces faster, the client only processes 50 items at a time.

7️⃣ Practical Patterns for Resilience

Use limitRate on producers to chunk delivery
Apply onBackpressureBuffer cautiously, with a buffer size
Use onBackpressureDrop for non-critical data
Avoid blocking operations inside your subscribers
Monitor logs for dropped items or buffer overflows

Example Use Cases

  • Stock price streaming: Limit rate to avoid UI lock-ups
  • Telemetry pipelines: Drop excess metrics if clients are too slow
  • Chat applications: Buffer a limited backlog of messages

Sources & Further Reading

Conclusion

Backpressure is essential for stable, responsive streaming APIs. By understanding and applying Reactor’s built-in operators like limitRate, onBackpressureBuffer, and onBackpressureDrop, you can build systems that gracefully adapt to varying loads and client capabilities.

Eleftheria Drosopoulou

Eleftheria is an Experienced Business Analyst with a robust background in the computer software industry. Proficient in Computer Software Training, Digital Marketing, HTML Scripting, and Microsoft Office, they bring a wealth of technical skills to the table. Additionally, she has a love for writing articles on various tech subjects, showcasing a talent for translating complex concepts into accessible content.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button