WebSockets en Java : quand HTTP ne suffit plus
\n\nImaginez une bourse en ligne. Vous regardez le graphique du prix du Bitcoin. Les chiffres changent chaque seconde. Ou un jeu multijoueur où vous déplacez votre personnage et une centaine d'autres joueurs le voient instantanément. Ou simplement un chat d'entreprise où les messages arrivent sans appuyer sur F5.
\n\nComment cela fonctionne-t-il sous le capot ? Avant, les développeurs se débrouillaient : polling (le client interroge le serveur chaque seconde en demandant "alors, des nouvelles ?"), long polling (le serveur maintient la connexion jusqu'à ce qu'une nouvelle arrive), Server-Sent Events (flux unidirectionnel). Tout cela, ce sont des béquilles. Le protocole HTTP a été conçu à l'origine comme une requête-réponse. Il ne sait pas "pousser" les données lui-même.
\n\nC'est là que les WebSockets entrent en jeu. C'est un protocole qui établit une connexion une seule fois et la maintient ouverte. Le serveur peut à tout moment envoyer des données au client, et vice versa. Une communication bidirectionnelle en temps réel, sans le surcoût des en-têtes HTTP.
\n\nDans cet article, nous allons décortiquer les WebSockets en Java de A à Z. Nous allons écrire un chat complet, analyser les pièges et comparer les bibliothèques populaires. Si vous êtes développeur Java et souhaitez créer des applications temps réel en Java, vous êtes au bon endroit.
\n\nComment fonctionne WebSocket ? Petit cours accéléré
\n\nWebSocket n'est pas de la magie, mais simplement un protocole au-dessus de TCP. Il commence comme une requête HTTP normale (handshake), puis "passe à un niveau supérieur" pour devenir un canal bidirectionnel permanent.
\n\nÉtape 1 : La poignée de main (Handshake)
\nLe client envoie une requête HTTP GET classique avec l'en-tête Upgrade: websocket. Le serveur répond avec le code 101 Switching Protocols. Et voilà. À partir de ce moment, les données ne voyagent plus comme des paquets HTTP, mais comme des trames WebSocket.
Étape 2 : Les trames de données
\nLes données sont transmises en petits paquets (trames). Une trame peut être textuelle (une chaîne de caractères simple) ou binaire (image, audio). Les bibliothèques Java masquent cette complexité : vous travaillez simplement avec des chaînes ou des octets.
\n\nÉtape 3 : La fermeture
\nChaque partie peut initier la fermeture de la connexion en envoyant une trame Close spéciale.
\n\nCela semble simple ? Et ça l'est. Mais le diable se cache dans les détails : timeouts, reconnexions, passage à l'échelle. Mais nous y reviendrons plus tard.
\n\nBibliothèques pour WebSockets en Java
\n\nDans l'écosystème Java, il existe trois approches principales :
\n\n- \n
- Jakarta WebSocket (JSR 356) — le standard Java EE / Jakarta EE. Fonctionne sur n'importe quel serveur compatible (Tomcat, Jetty, GlassFish). Beaucoup d'annotations, peu de code. \n
- Spring WebSocket — une surcouche du standard dans l'écosystème Spring. Offre l'intégration avec Security, STOMP (un protocole au-dessus de WebSocket pour les abonnements à des sujets) et des modèles pratiques. \n
- Netty / Vert.x / Undertow — des frameworks asynchrones de bas niveau. Performances maximales, mais plus de code. \n
Pour 90% des tâches, Jakarta ou Spring suffiront. Nous allons examiner les deux options avec des exemples concrets.
\n\nExemple 1 : Un chat simple avec Jakarta WebSocket (sans Spring)
\n\nSupposons que nous ayons un bon vieux Tomcat 10+ (Jakarta EE). Nous avons besoin d'un endpoint serveur et d'un client (HTML+JS).
\n\nServeur : classe endpoint
\nimport jakarta.websocket.*;\nimport jakarta.websocket.server.ServerEndpoint;\nimport java.io.IOException;\nimport java.util.Set;\nimport java.util.concurrent.CopyOnWriteArraySet;\n\n@ServerEndpoint("/chat")\npublic class ChatEndpoint {\n\n // Stocke toutes les sessions actives (ensemble thread-safe)\n private static final Set<Session> sessions = new CopyOnWriteArraySet<>();\n\n @OnOpen\n public void onOpen(Session session) {\n sessions.add(session);\n broadcast("Un utilisateur s'est connecté. Total dans le chat : " + sessions.size());\n }\n\n @OnMessage\n public void onMessage(String message, Session session) {\n // On peut ajouter une logique : envoyer uniquement à