Skip to content

Understanding RoomService

jfarcand edited this page Feb 21, 2026 · 2 revisions

Understanding @RoomService

@RoomService is a room-aware alternative to @ManagedService. It uses the same lifecycle annotations (@Ready, @Message, @Disconnect) but automatically creates and manages a Room via the RoomManager, giving you presence tracking, message history, and direct messaging out of the box.

Use @RoomService when your application is centred around rooms (chat, collaboration, multiplayer). Use @ManagedService when you need a generic real-time endpoint without room semantics.

Quick Example

@RoomService(path = "/chat/{roomId}", maxHistory = 50)
public class ChatRoom {

    @Ready
    public void onJoin(AtmosphereResource r) {
        // Client joined the room — presence event is sent automatically
    }

    @Message(encoders = {JacksonEncoder.class}, decoders = {JacksonDecoder.class})
    public Message onMessage(Message message) {
        return message; // broadcast to all room members
    }

    @Disconnect
    public void onLeave(AtmosphereResourceEvent event) {
        // Client left — presence event is sent automatically
    }
}

That's it — no RoomManager boilerplate, no RoomProtocolInterceptor setup. The annotation processor handles everything.

Annotation Reference

@RoomService(
    path = "/chat/{roomId}",   // mapping path — supports path parameters
    maxHistory = 0             // messages to keep in room history (0 = disabled)
)
Attribute Type Default Description
path String "/" URL mapping path. Supports {param} path templates
maxHistory int 0 Number of messages to keep in room history. Set to > 0 to replay history to joining clients

How It Works

When the annotation processor encounters @RoomService:

  1. Creates a ManagedAtmosphereHandler for the annotated class (same as @ManagedService)
  2. Registers the default managed-service interceptors (UUID cache, lifecycle, heartbeat, message tracking, suspend tracker)
  3. Maps the handler to the specified path
  4. Calls RoomManager.getOrCreate(framework) to get the singleton RoomManager
  5. Creates the Room via roomManager.room(path)
  6. If maxHistory > 0, calls room.enableHistory(maxHistory)

Because @RoomService uses the same ManagedAtmosphereHandler under the hood, all lifecycle annotations work identically to @ManagedService:

Annotation When invoked
@Ready Client connects and is suspended
@Message Client sends a message
@Disconnect Client disconnects (close or timeout)
@Heartbeat Heartbeat received

Path Parameters

Use {param} in the path to create dynamic rooms:

@RoomService(path = "/game/{gameId}")
public class GameRoom {

    @PathParam("gameId")
    private String gameId;

    @Message
    public String onMove(String move) {
        return move; // broadcast to all players in this game
    }
}

Each unique path (e.g. /game/abc, /game/xyz) gets its own Room and Broadcaster.

Message History

Enable history so new members receive recent messages when they join:

@RoomService(path = "/chat/general", maxHistory = 100)
public class GeneralChat {

    @Message(encoders = {JacksonEncoder.class}, decoders = {JacksonDecoder.class})
    public Message onMessage(Message message) {
        return message;
    }
}

The last 100 messages are replayed to any client that connects to this room.

Injection

Like @ManagedService, you can inject Atmosphere objects into @RoomService classes:

@RoomService(path = "/collab/{docId}")
public class CollabRoom {

    @Inject
    private AtmosphereResource resource;

    @Inject
    private AtmosphereResourceEvent event;

    @PathParam("docId")
    private String docId;

    @Ready
    public void onJoin() {
        // resource and docId are available here
    }
}

@RoomService vs @ManagedService

@RoomService @ManagedService
Purpose Room-centric real-time apps General-purpose real-time endpoints
Room auto-creation ✅ Automatic via RoomManager ❌ Manual setup required
Message history maxHistory attribute ❌ Must configure BroadcasterCache manually
Presence tracking ✅ Built-in via Room ❌ Must implement manually
Lifecycle annotations @Ready, @Message, @Disconnect, @Heartbeat Same
Path parameters {param} {param}
Encoders/Decoders Via @Message Via @Message or annotation attributes
Interceptors Default managed set Configurable via interceptors attribute
Broadcaster config Default Configurable via broadcaster attribute

Rule of thumb: If your clients join/leave named groups, use @RoomService. If you need a raw pub/sub endpoint or fine-grained Broadcaster control, use @ManagedService.

Client Integration

The client code is the same as for any @ManagedService endpoint. Connect with atmosphere.js and send JSON:

const subscription = await atmosphere.atmosphere.subscribe(
    {
        url: `${location.protocol}//${location.host}/chat/general`,
        transport: 'websocket',
        fallbackTransport: 'long-polling',
        trackMessageLength: true,
        contentType: 'application/json'
    },
    {
        open: (response) => console.log('Joined room'),
        message: (response) => {
            const msg = JSON.parse(response.responseBody);
            console.log(`${msg.author}: ${msg.message}`);
        }
    }
);

subscription.push(JSON.stringify({ author: 'Alice', message: 'Hello room!' }));

For the Room Protocol (join/leave/presence/direct messaging), see Understanding Rooms — Client Integration.

See Also

Clone this wiki locally