← Back to list

🚦 Backpressure in Java Explained — Stop Fast Producers from Overwhelming Slow Consumers

Have you ever built a system where one part keeps generating data faster than another part can handle it — causing memory spikes…

Samrat Alam · 2025-10-21 10:02 · 1 claps · 3.4 min read
#backpressure #java #kafka #blockingqueue #producer-consumer-problem
Open on Medium ↗
Wiki topics: 📰 · Journalism & News

🚦 Backpressure in Java Explained — Stop Fast Producers from Overwhelming Slow Consumers

Have you ever built a system where one part keeps generating data faster than another part can handle it — causing memory spikes, slowdowns, or crashes?

Welcome to one of the most underrated yet crucial concepts in concurrent systems: Backpressure.

💡 What Is Backpressure?

Backpressure means applying flow control between a producer (who generates data) and a consumer (who processes it).

Simply put: Backpressure ensures that a fast producer doesn’t flood a slow consumer.

🧠 Real-life Analogy

Imagine you’re filling glasses with water using a hose. If you pour too fast, water spills everywhere. Now imagine the glass can send a signal to the hose:

“Stop! I’m full.”

That’s backpressure — the consumer (glass) telling the producer (hose) to slow down.

⚙️ A Simple Producer–Consumer System in Java

Let’s simulate this with code.

❌ Without Backpressure

import java.util.concurrent.*;

public class WithoutBackpressure {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(2);
        BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(); // unbounded
        // Fast Producer
        executor.execute(() -> {
            for (int i = 0; i < 1000; i++) {
                queue.offer(i); // keeps producing fast
                System.out.println("Produced: " + i);
            }
        });
        // Slow Consumer
        executor.execute(() -> {
            while (true) {
                try {
                    Thread.sleep(100); // simulate slow processing
                    Integer data = queue.poll();
                    if (data != null)
                        System.out.println("Consumed: " + data);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        });
    }
}

🧩 What happens:

  • The producer floods the queue very quickly.
  • The consumer is too slow to keep up.
  • Eventually, the system may run out of memory (OOM) or slow down due to GC pressure.

There’s no backpressure — no signal that says “hold on, I’m not ready yet.”

✅ With Backpressure (Using a Bounded Queue)

Now let’s fix it with a bounded queue that forces the producer to wait when the consumer is slow.

import java.util.concurrent.*;

public class WithBackpressure {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(2);
        BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10); // bounded queue
        // Producer with flow control
        executor.execute(() -> {
            for (int i = 0; i < 1000; i++) {
                try {
                    queue.put(i); // blocks if queue is full
                    System.out.println("Produced: " + i);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        });
        // Slow Consumer
        executor.execute(() -> {
            while (true) {
                try {
                    Thread.sleep(100); // slow processing
                    Integer data = queue.take(); // waits if empty
                    System.out.println("Consumed: " + data);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        });
    }
}

🧠 What changed:

  • The producer now blocks when the queue is full.
  • It only resumes producing once the consumer takes some items out.
  • No memory overflow, no lost data — smooth data flow.
  • BlockingQueue to act as the buffer with a fixed capacity.
  • ArrayBlockingQueue<>(10) → allows only 10 items in the buffer at a time.
  • queue.put(i)blocks the producer when the queue is full. ➜ This is the backpressure — the producer waits until there’s space.
  • queue.take()blocks the consumer when the queue is empty. ➜ This prevents the consumer from processing non-existent data.

✅ This is manual backpressure using blocking.

🧩 Real-World Example: Kafka Consumers

In Apache Kafka, the concept of backpressure is crucial.

A consumer may read messages slower than they’re produced — that’s when consumer lag occurs.

Kafka allows backpressure through:

  • Pausing/resuming consumption
  • Controlling batch size and poll interval

Example:

consumer.pause(partitions); // temporarily stop consuming
// process current backlog
consumer.resume(partitions); // resume when ready

This prevents the consumer from getting overwhelmed by incoming messages.

⚡ Backpressure in Reactive Programming

Frameworks like Project Reactor and RxJava have built-in backpressure support.

Example (Project Reactor)

import reactor.core.publisher.Flux;
import java.time.Duration;

public class ReactiveBackpressure {
    public static void main(String[] args) throws InterruptedException {
        Flux.range(1, 1000)
            .delayElements(Duration.ofMillis(10)) // producer speed
            .onBackpressureBuffer(20,   // buffer up to 20 items
                dropped -> System.out.println("Dropped: " + dropped)) // handle overflow
            .delayElements(Duration.ofMillis(100)) // simulate slow consumer
            .subscribe(
                data -> System.out.println("Consumed: " + data),
                Throwable::printStackTrace,
                () -> System.out.println("Completed!")
            );
        Thread.sleep(20000); // keep app running
    }
}

🧩 Here:

  • The producer emits values quickly (delayElements(10ms)).
  • The consumer is slower (delayElements(100ms)).
  • onBackpressureBuffer(20) buffers 20 items max — beyond that, items are dropped safely.

✅ This is automatic, non-blocking backpressure — much more scalable than manual blocking queues.

⚙️ Common Backpressure Strategies

🧭 When Should You Care About Backpressure?

Backpressure matters whenever your system:

  • Streams data between asynchronous components (Kafka, RabbitMQ, WebFlux)
  • Processes large data volumes
  • Relies on event loops or thread pools

Ignoring backpressure leads to:

  • Memory overflow
  • High latency
  • Thread starvation
  • Crashes under load

✨ Final Thoughts

Backpressure is like a communication contract between producers and consumers. It ensures your system stays stable, responsive, and memory-safe, even under load.

A good system doesn’t just produce fast — it produces sustainably.


메타데이터
post_id
8773703efe06
slug
backpressure-in-java-explained-stop-fast-producers-from-overwhelming-slow-consumers-8773703efe06
url
https://medium.com/@samrat.alam/backpressure-in-java-explained-stop-fast-producers-from-overwhelming-slow-consumers-8773703efe06
canonical_url
https://medium.com/@samrat.alam/backpressure-in-java-explained-stop-fast-producers-from-overwhelming-slow-consumers-8773703efe06
author_url
https://medium.com/@samrat.alam
status
ok
fetched_at
2026-08-02 03:37:55