๐ WebSocket Communication in Jakarta EE
The Story of the Magic Walkie-Talkie
Imagine you and your best friend have special walkie-talkies. Unlike regular phones where you call, talk, and hang up, these magical walkie-talkies stay connected ALL the time! You can talk whenever you want, and your friend hears you instantly. No need to dial again and again.
Thatโs exactly what WebSocket is! A connection that stays open between your app and a server, so messages flow back and forth like a real conversation.
๐ฏ What Weโll Learn
| Concept | What It Does |
|---|---|
| Message Handlers | Catch and respond to messages |
| Encoders | Turn objects into text/bytes |
| Decoders | Turn text/bytes back into objects |
| Path Parameters | Add IDs in the URL |
| WebSocket Configurator | Custom setup before connecting |
| WebSocket Client API | Connect TO a server (not just receive) |
๐ฌ Message Handlers
What Are They?
Think of message handlers like a mailbox with a smart helper inside. When a letter arrives, the helper reads it and knows exactly what to do!
In WebSocket, when someone sends you a message, a Message Handler catches it and runs your code.
The Three Types of Handlers
@ServerEndpoint("/chat")
public class ChatEndpoint {
// 1. Text messages (like letters)
@OnMessage
public void onTextMessage(
String message,
Session session) {
System.out.println("Got: " + message);
}
// 2. Binary messages (like packages)
@OnMessage
public void onBinaryMessage(
ByteBuffer data,
Session session) {
System.out.println("Got bytes!");
}
// 3. Pong messages (heartbeat reply)
@OnMessage
public void onPongMessage(
PongMessage pong,
Session session) {
System.out.println("Still alive!");
}
}
Simple Example: Echo Server
@ServerEndpoint("/echo")
public class EchoEndpoint {
@OnMessage
public String echo(String msg) {
// Whatever you send, I send back!
return "You said: " + msg;
}
}
Real Life: This is like shouting in a canyon and hearing your voice come back!
๐ Encoders
What Are They?
Imagine you want to send a toy car through the mail. You canโt just throw it in the mailbox! You need to pack it in a box first.
Encoders pack your Java objects into text (JSON) or bytes so they can travel through WebSocket.
Text Encoder Example
public class MessageEncoder
implements Encoder.Text<ChatMessage> {
@Override
public String encode(ChatMessage msg) {
// Pack the object into JSON text
return "{\"user\":\"" + msg.getUser()
+ "\",\"text\":\"" + msg.getText()
+ "\"}";
}
@Override
public void init(EndpointConfig config) {}
@Override
public void destroy() {}
}
Using the Encoder
@ServerEndpoint(
value = "/chat",
encoders = { MessageEncoder.class }
)
public class ChatEndpoint {
@OnMessage
public ChatMessage respond(String input) {
// Return an object - encoder packs it!
return new ChatMessage("Bot", "Hi there!");
}
}
Think of it like: Your encoder is a gift-wrapper that makes objects ready to travel!
๐ฆ Decoders
What Are They?
Now the opposite! When a package arrives, you need to unpack it to see the toy inside.
Decoders unpack incoming text or bytes back into Java objects you can use.
Text Decoder Example
public class MessageDecoder
implements Decoder.Text<ChatMessage> {
@Override
public ChatMessage decode(String json) {
// Unpack JSON into an object
// (simplified parsing)
String user = extractUser(json);
String text = extractText(json);
return new ChatMessage(user, text);
}
@Override
public boolean willDecode(String json) {
// Can we decode this message?
return json.contains("user")
&& json.contains("text");
}
@Override
public void init(EndpointConfig config) {}
@Override
public void destroy() {}
}
Using the Decoder
@ServerEndpoint(
value = "/chat",
decoders = { MessageDecoder.class }
)
public class ChatEndpoint {
@OnMessage
public void handleMessage(ChatMessage msg) {
// Decoder already unpacked it!
System.out.println(msg.getUser()
+ " says: " + msg.getText());
}
}
graph TD A["Client sends JSON text"] --> B["Decoder unpacks it"] B --> C["Your code gets ChatMessage object"] C --> D["You create response object"] D --> E["Encoder packs it"] E --> F["Client receives JSON text"]
๐ค๏ธ WebSocket Path Parameters
What Are They?
Think about houses on a street. Each house has a number. When the pizza delivery person looks for โ123 Main Street,โ they use the number to find YOUR house.
Path Parameters are like house numbers in your WebSocket URL!
Example: Chat Rooms
@ServerEndpoint("/chat/{roomId}")
public class RoomEndpoint {
@OnOpen
public void onOpen(
Session session,
@PathParam("roomId") String roomId) {
System.out.println(
"Joined room: " + roomId);
}
@OnMessage
public void onMessage(
String message,
@PathParam("roomId") String roomId) {
System.out.println(
"Message in " + roomId
+ ": " + message);
}
}
How Clients Connect
| URL | Room ID |
|---|---|
ws://server/chat/games |
games |
ws://server/chat/music |
music |
ws://server/chat/123 |
123 |
Multiple Parameters
@ServerEndpoint("/game/{gameId}/player/{playerId}")
public class GameEndpoint {
@OnOpen
public void join(
@PathParam("gameId") String gameId,
@PathParam("playerId") String playerId) {
System.out.println(playerId
+ " joined game " + gameId);
}
}
URL: ws://server/game/chess/player/alice
Result: Alice joins the chess game!
โ๏ธ WebSocket Configurator
What Is It?
Before guests enter a party, sometimes you check their invitation at the door. You might also give them a name tag!
Configurator lets you do special setup BEFORE the WebSocket connection opens.
Common Uses
- Check cookies or tokens (authentication)
- Read HTTP headers
- Pass custom data to the endpoint
Example: Authentication Check
public class AuthConfigurator
extends ServerEndpointConfig.Configurator {
@Override
public void modifyHandshake(
ServerEndpointConfig config,
HandshakeRequest request,
HandshakeResponse response) {
// Get cookies from the request
Map<String, List<String>> headers =
request.getHeaders();
// Store user info for later
String token = extractToken(headers);
config.getUserProperties()
.put("authToken", token);
}
}
Using the Configurator
@ServerEndpoint(
value = "/secure",
configurator = AuthConfigurator.class
)
public class SecureEndpoint {
@OnOpen
public void onOpen(
Session session,
EndpointConfig config) {
// Get the token we saved earlier
String token = (String) config
.getUserProperties()
.get("authToken");
if (!isValid(token)) {
session.close();
}
}
}
graph TD A["Client connects"] --> B["Configurator runs first"] B --> C{Check token} C -->|Valid| D["Allow connection"] C -->|Invalid| E["Reject connection"] D --> F["OnOpen runs"]
๐ก WebSocket Client API
What Is It?
So far, weโve been the server waiting for connections. But what if YOUR code needs to connect TO another server?
The Client API lets your Java code be the one making the call!
Simple Client Example
@ClientEndpoint
public class MyClient {
private Session session;
@OnOpen
public void onOpen(Session session) {
this.session = session;
System.out.println("Connected!");
}
@OnMessage
public void onMessage(String message) {
System.out.println("Got: " + message);
}
public void sendMessage(String msg)
throws IOException {
session.getBasicRemote()
.sendText(msg);
}
}
Connecting to a Server
public class ClientApp {
public static void main(String[] args)
throws Exception {
WebSocketContainer container =
ContainerProvider
.getWebSocketContainer();
URI uri = new URI(
"ws://example.com/chat");
MyClient client = new MyClient();
// Make the connection!
Session session = container
.connectToServer(client, uri);
// Send a message
client.sendMessage("Hello server!");
}
}
Client with Encoders/Decoders
@ClientEndpoint(
encoders = { MessageEncoder.class },
decoders = { MessageDecoder.class }
)
public class SmartClient {
@OnMessage
public void onMessage(ChatMessage msg) {
// Decoder already unpacked it!
System.out.println(
msg.getUser() + ": " + msg.getText());
}
}
๐จ Putting It All Together
Hereโs a complete chat room example using EVERYTHING:
// The message object
public class ChatMessage {
private String user;
private String text;
private String room;
// getters and setters...
}
// Encoder
public class ChatEncoder
implements Encoder.Text<ChatMessage> {
@Override
public String encode(ChatMessage m) {
return String.format(
"{\"user\":\"%s\",\"text\":\"%s\"}",
m.getUser(), m.getText());
}
// init and destroy...
}
// Decoder
public class ChatDecoder
implements Decoder.Text<ChatMessage> {
@Override
public ChatMessage decode(String s) {
// Parse JSON and return object
return parseJson(s);
}
@Override
public boolean willDecode(String s) {
return s.contains("user");
}
// init and destroy...
}
// Configurator
public class ChatConfig
extends ServerEndpointConfig.Configurator {
@Override
public void modifyHandshake(
ServerEndpointConfig sec,
HandshakeRequest req,
HandshakeResponse res) {
sec.getUserProperties()
.put("ip", getClientIP(req));
}
}
// The endpoint (server)
@ServerEndpoint(
value = "/chat/{room}",
encoders = { ChatEncoder.class },
decoders = { ChatDecoder.class },
configurator = ChatConfig.class
)
public class ChatServer {
@OnOpen
public void onOpen(
Session session,
@PathParam("room") String room) {
System.out.println(
"Someone joined " + room);
}
@OnMessage
public ChatMessage onMessage(
ChatMessage msg,
@PathParam("room") String room) {
msg.setRoom(room);
return msg; // Echo to sender
}
}
๐ Quick Summary
| Concept | One-Liner |
|---|---|
| Message Handlers | Catch messages with @OnMessage |
| Encoders | Pack objects โ text/bytes |
| Decoders | Unpack text/bytes โ objects |
| Path Parameters | IDs in the URL like /chat/{roomId} |
| Configurator | Setup before connection opens |
| Client API | YOUR code connects to a server |
๐ก Remember This!
WebSocket = Always-open walkie-talkie
- Handlers answer the call
- Encoders pack your gifts
- Decoders unwrap incoming gifts
- Path Params are room numbers
- Configurator checks invitations
- Client API lets YOU call someone
Youโre now ready to build real-time apps that talk back and forth instantly! ๐
