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é" :
- Le Subscriber appelle subscription.request(n) pour demander n éléments.
- Le Publisher pousse (onNext) jusqu'à n éléments maximum.
- 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

🧠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 Flow | Partiellement |
☕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