Menu
Reactive Streams : comprendre et maîtriser la backpressure dans l'écosystème Java

Reactive Streams : comprendre et maîtriser la backpressure dans l'écosystème Java

Par Loïc DESCOTTE

03.10.2025

 

Tu l’as peut-être déjà vécu : une appli qui crashe parce qu’un Publisher balance des données plus vite que ton Subscriber ne peut les traiter. Résultat ? Threads bloqués, OutOfMemoryError , logs qui partent en vrille, et toi qui finis par maudire ton propre code.

 

Reactive Streams : historique et objectifs

Origine de la spécification (Netflix, Lightbend, Red Hat…)

Reactive Streams est né d'une initiative fin 2013 entre les ingénieurs de Netflix, Pivotal et Lightbend (société qui gère l'offre commerciale derrière langage Scala, Akka et Play Framework).
Les premières discussions ont débuté en 2013 entre les équipes Play Framework et Akka de Lightbend.
Parmi les autres contributeurs figurent Red Hat, Oracle, Twitter et spray.io.

 

Pourquoi standardiser la gestion des flux asynchrones ?

La spécification Reactive Streams a été conçue pour répondre à un besoin crucial dans les systèmes modernes :

Gérer des flux de données de manière asynchrone, non bloquante, avec contrôle de la pression (backpressure), tout en favorisant l’interopérabilité entre les bibliothèques réactives.

Elle vise à :

  • Standardiser les échanges de flux entre composants (éditeur/abonné)
  • Offrir une sémantique claire pour la gestion de la pression (backpressure)
  • Améliorer la scalabilité des systèmes réactifs (moins de threads, moins de blocages)
  • Assurer l’interopérabilité entre implémentations

 

Qu’est-ce que la backpressure ?

Le problème des flux asynchrones sans régulation

La backpressure (ou "contre-pression") est un mécanisme de régulation du débit entre un producteur (Publisher) et un consommateur (Subscriber) dans un système de flux asynchrone. Pour bien comprendre, voyons cela à travers les modèles push et pull, et comment Reactive Streams combine les deux.

 

Push vs Pull : deux modèles de gestion de flux

Aspect Push (impulsion du producteur) Pull (demande du consommateur) 
Contrôle du flux Producteur décide quand et combien de données sont émises Consommateur décide quand et combien il veut consommer
Risque principal Surcharge du consommateur si trop rapide  Ralentissement si le consommateur demande peu
Exemple concret onNext() appelé en boucle sans contrôle  next() appelé manuellement

 

Comment Reactive Streams gère la backpressure ?

Le modèle hybride “pull-based avec push contrôlé”

Reactive Streams propose un modèle hybride "pull-based avec push contrôlé" :

  1. Le Subscriber appelle subscription.request(n) pour demander n éléments.
  2. Le Publisher pousse (onNext) jusqu'à n éléments maximum.
  3. Si le Publisher veut émettre davantage, il doit attendre une nouvelle demande.

👉 Le flux est donc initié par le consommateur (pull), mais l’émission reste asynchrone (push).

 

Exemple de code Java avec Subscriber et Publisher

    public class MySubscriber implements Subscriber<Integer> {
    private Subscription subscription;

    public void onSubscribe(Subscription s) {
        this.subscription = s;
        s.request(5); // Je suis prêt à recevoir 5 éléments
    }

    public void onNext(Integer item) {
        System.out.println("Reçu: " + item);
        // Si on veut plus : subscription.request(1);
    }

    public void onError(Throwable t) {
        t.printStackTrace();
    }

    public void onComplete() {
        System.out.println("Terminé");
    }
}

Ici, le Subscriber demande à contrôler le flux. Le Publisher pousse uniquement le nombre d’éléments autorisés.

Les risques évités grâce à la backpressure

cascade-backpressure

🧠En résumé, 

  • Sans backpressure : push unilatéral → surcharge → OutOfMemory, thread bloqué, etc.
  •  Avec backpressure (Reactive Streams) :
    •  Le consommateur "tire" le flux avec request(n)
    • Le producteur "pousse" les données, dans la limite autorisée

 

📦Parallèle concret

Imagine un robinet (Publisher) et un seau (Subscriber) :

  • Sans backpressure : le robinet est à fond → le seau déborde.
  • Avec backpressure : le seau demande 1L à la fois → pas de débordement.

 

Les interfaces principales de Reactive Streams en Java

Publisher, Subscriber, Subscription, Processor Expliqués

Reactive Streams repose sur 4 interfaces principales (Java) :

Interface Rôle principal
Publisher<T> Émetteur de données (source). Il émet des éléments aux abonnés.
Subscriber<T> Consommateur de données. Il s'abonne à un Publisher.
Subscription Lien entre Publisher et Subscriber. Gère les demandes (request(n)) et les annulations (cancel()).
Processor<T,R> À la fois Publisher et Subscriber. Permet de transformer un flux.

Ces interfaces permettent d’instaurer un dialogue contrôlé et asynchrone entre les parties prenantes.

 

Intégration native depuis Java 9 avec java.util.concurrent.Flow

📦Outils et frameworks supportant Reactive Streams

Outil / Framework Support Reactive Streams
Project Reactor Oui
RxJava (2.x et +) Oui
Akka (Pekko) Streams Oui
Vert.x Oui
ZIO Streams Oui
 Spring WebFlux Oui (Via Project Reactor)
 Kotlin FlowPartiellement

 

☕Java et Reactive Streams

  • Depuis Java 9, Reactive Streams a été intégré dans le JDK (le kit de développement pour Java) via le package java.util.concurrent.Flow, avec des interfaces analogues :
    •  Flow.Publisher, Flow.Subscriber, Flow.Subscription, Flow.Processor
  •  Cette intégration permet une interopérabilité native dans les API Java modernes.

 

🧩 Unification & simplification des flux asynchrones

Avant Reactive Streams :

  • Chaque lib avait son modèle propriétaire (RxJava, Akka, Reactor)
  • La gestion de la pression était souvent absente
  • L’interopérabilité était difficile

Grâce à Reactive Streams :

  • Les librairies peuvent parler un langage commun
  • Le développeur peut brancher des composants de différentes sources
  • Backpressure devient un mécanisme standardisé
  • Le développement réactif devient plus fiable et composable

 

Implémentations pratiques pour développeurs backend

Exemple concret avec project Reactor en Kotlin

Voici un exemple de chaîne réactive avec Project Reactor (Reactor Core) qui est compatible Reactive Streams :
    package com.example

import reactor.core.publisher.Flux


fun main() {
    val source = Flux.range(1, 20)
        .map { it * 2 } // petite transformation sur les données

    source
        .buffer(10) // on demande les éléments par paquets de 10
        .subscribe { batch ->
            println("Received a batch: $batch")
        }
}

✅ Cette implémentation repose sur les interfaces de la spécification Reactive Streams, et gère la demande explicite (request(n)) de façon transparente.

Output :

    Received a batch: [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
Received a batch: [22, 24, 26, 28, 30, 32, 34, 36, 38, 40]

 

Pourquoi la backpressure est indispensable pour avoir un backend scalable ?

Scalabilité et performance

Moins de threads, pas de surcharge → meilleure résilience.

Prévenir les crashs et OutOfMemoryError

Sans régulation, le consommateur sature → crashs garantis.

Garantir la résilience des systèmes réactifs

Standardisation = composants qui dialoguent sans friction.

 

Mini défi pour tester la backpressure en Java

  •  Utilise ton LLM préféré (ChatGPT, Mistral, peu importe) pour booter un petit projet Java réactif.
  • Choisis un use case sympa (API météo, génération d’événements aléatoires, traitement de fichiers logs…).
  • Mets en place un Flux  ou un Publisher maison, et teste différents scénarios avec et sans backpressure

👉 Objectif : constater concrètement la différence. Tu verras vite qu’un flux sans backpressure, c’est un robinet ouvert à fond… et que la version contrôlée te sauve la mise en prod.

 

Conclusion : Reactive Streams, un standard incontournable pour Java

Avantages principaux de Reactive Streams
Standardisation de la programmation réactive
Support de la backpressure pour éviter les surcharges
Interopérabilité entre librairies / frameworks
Intégration native dans Java 9+

Grâce à Reactive Streams, tu imposes le rythme, tu gardes le contrôle, et tu construis des systèmes plus scalables et résilients.
C’est aujourd’hui la pierre angulaire de l’écosystème réactif en Java, utilisée dans des frameworks comme Spring ou Akka pour construire des applications réactives, résilientes, et scalables.

Retour aux articles

C'est à lire...