This guide covers upgrading from Atmosphere 2.x or 3.x to Atmosphere 4.0. It is organized by topic so you can jump to the sections relevant to your application.
- Prerequisites
- Dependency Changes
- Package Renames (javax to jakarta)
- Configuration Changes
- Server-Side API Changes
- Client Library Migration (atmosphere.js)
- Server Container Requirements
- New Features in 4.0
- Testing
- Troubleshooting
Atmosphere 4.0 requires JDK 21 or later. Previous versions supported JDK 8+.
# Verify your JDK version
java -version
# Must show 21 or laterAtmosphere 4.0 targets Jakarta Servlet 6.0 (Jakarta EE 10). You need a container that implements these APIs:
| Container | Minimum Version |
|---|---|
| Apache Tomcat | 11.0+ |
| Eclipse Jetty | 12.0+ (EE10 module) |
| Undertow (standalone) | 2.3+ |
| Quarkus | 3.21+ (via atmosphere-quarkus-extension) |
| Spring Boot | 4.0+ (via atmosphere-spring-boot-starter) |
Older containers (Tomcat 9/10, Jetty 9/10/11, WildFly 26 and below) are not supported.
Maven 3.6.3 or later is required (enforced by the build).
The groupId and artifactId are unchanged. Only the version changes.
Before (2.x / 3.x):
<dependency>
<groupId>org.atmosphere</groupId>
<artifactId>atmosphere-runtime</artifactId>
<version>2.7.14</version> <!-- or 3.0.x -->
</dependency>After (4.0):
<dependency>
<groupId>org.atmosphere</groupId>
<artifactId>atmosphere-runtime</artifactId>
<version>4.0.0</version>
</dependency>The Servlet API dependency has changed from javax.servlet to jakarta.servlet:
Before:
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>After:
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>5.0.0</version>
<scope>provided</scope>
</dependency><!-- Before -->
<dependency>
<groupId>javax.websocket</groupId>
<artifactId>javax.websocket-api</artifactId>
<version>1.1</version>
<scope>provided</scope>
</dependency>
<!-- After -->
<dependency>
<groupId>jakarta.websocket</groupId>
<artifactId>jakarta.websocket-api</artifactId>
<version>2.0.0</version>
<scope>provided</scope>
</dependency>The following external dependencies and integration modules from older Atmosphere releases are no longer shipped as part of the core distribution. If you were using them, check the new dedicated modules:
| Old Dependency / Module | Replacement |
|---|---|
atmosphere-redis (2.x plugin) |
atmosphere-redis (new module, org.atmosphere:atmosphere-redis:4.0.0) |
atmosphere-kafka (external) |
atmosphere-kafka (new module, org.atmosphere:atmosphere-kafka:4.0.0) |
atmosphere-jgroups |
Removed. Use Redis or Kafka for clustering. |
atmosphere-jms |
Removed. Use Redis or Kafka for clustering. |
atmosphere-hazelcast |
Removed. Use Redis or Kafka for clustering. |
atmosphere-rabbitmq |
Removed. Use Redis or Kafka for clustering. |
atmosphere-xmpp |
Removed. |
atmosphere-jersey |
Removed. Use @ManagedService or @AtmosphereService directly. |
The following APIs and features have been removed in 4.0:
| Removed | Description | Alternative |
|---|---|---|
@MeteorService |
Annotation-driven Meteor/Comet endpoints | Use @ManagedService with long-polling or SSE transport |
Meteor class |
Comet push abstraction | Use @ManagedService with Broadcaster for server push |
JSONPAtmosphereInterceptor |
JSONP transport support | Use long-polling or SSE — all modern browsers support CORS |
NettyCometSupport |
Netty native comet transport | Use Jetty 12, Tomcat 11, or another Jakarta EE 10 container |
Note: These removals are intentional. The Meteor API predated WebSocket and modern SSE support. JSONP was a workaround for same-origin-policy restrictions that CORS has replaced. Netty comet was a niche deployment option superseded by the standard Jakarta Servlet container model.
Atmosphere 4.0 introduces several new optional modules:
<!-- Spring Boot auto-configuration -->
<dependency>
<groupId>org.atmosphere</groupId>
<artifactId>atmosphere-spring-boot-starter</artifactId>
<version>4.0.0</version>
</dependency>
<!-- Quarkus extension -->
<dependency>
<groupId>org.atmosphere</groupId>
<artifactId>atmosphere-quarkus-extension</artifactId>
<version>4.0.0</version>
</dependency>
<!-- AI/LLM streaming SPI -->
<dependency>
<groupId>org.atmosphere</groupId>
<artifactId>atmosphere-ai</artifactId>
<version>4.0.0</version>
</dependency>
<!-- Redis clustering -->
<dependency>
<groupId>org.atmosphere</groupId>
<artifactId>atmosphere-redis</artifactId>
<version>4.0.0</version>
</dependency>
<!-- Kafka clustering -->
<dependency>
<groupId>org.atmosphere</groupId>
<artifactId>atmosphere-kafka</artifactId>
<version>4.0.0</version>
</dependency>
<!-- Durable sessions (survives server restart) -->
<dependency>
<groupId>org.atmosphere</groupId>
<artifactId>atmosphere-durable-sessions</artifactId>
<version>4.0.0</version>
</dependency>
<!-- Durable sessions backed by SQLite -->
<dependency>
<groupId>org.atmosphere</groupId>
<artifactId>atmosphere-durable-sessions-sqlite</artifactId>
<version>4.0.0</version>
</dependency>
<!-- Durable sessions backed by Redis -->
<dependency>
<groupId>org.atmosphere</groupId>
<artifactId>atmosphere-durable-sessions-redis</artifactId>
<version>4.0.0</version>
</dependency>
<!-- MCP (Model Context Protocol) support -->
<dependency>
<groupId>org.atmosphere</groupId>
<artifactId>atmosphere-mcp</artifactId>
<version>4.0.0</version>
</dependency>
<!-- Spring AI integration -->
<dependency>
<groupId>org.atmosphere</groupId>
<artifactId>atmosphere-spring-ai</artifactId>
<version>4.0.0</version>
</dependency>
<!-- LangChain4j integration -->
<dependency>
<groupId>org.atmosphere</groupId>
<artifactId>atmosphere-langchain4j</artifactId>
<version>4.0.0</version>
</dependency>
<!-- Kotlin extensions -->
<dependency>
<groupId>org.atmosphere</groupId>
<artifactId>atmosphere-kotlin</artifactId>
<version>4.0.0</version>
</dependency>This is the most impactful change for existing code. Every javax.servlet
and javax.websocket import must be changed to the jakarta namespace.
Run this across your codebase:
| Old Import | New Import |
|---|---|
javax.servlet.* |
jakarta.servlet.* |
javax.websocket.* |
jakarta.websocket.* |
javax.inject.* |
jakarta.inject.* |
javax.enterprise.* |
jakarta.enterprise.* |
Example:
// Before
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
// After
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;Atmosphere's own packages (org.atmosphere.*) are unchanged.
For large codebases, use an IDE migration tool or a script:
# Linux/macOS - update all Java files
find src -name "*.java" -exec sed -i '' \
-e 's/javax\.servlet/jakarta.servlet/g' \
-e 's/javax\.websocket/jakarta.websocket/g' \
-e 's/javax\.inject/jakarta.inject/g' \
{} +If you use a web.xml, update the namespace:
Before:
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">After:
<web-app version="6.0" xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd">The core servlet class names are unchanged. The main required change is the namespace (see above).
<servlet>
<servlet-name>AtmosphereServlet</servlet-name>
<!-- Class name is unchanged -->
<servlet-class>org.atmosphere.cpr.AtmosphereServlet</servlet-class>
<init-param>
<param-name>org.atmosphere.cpr.packages</param-name>
<param-value>com.yourapp.atmosphere</param-value>
</init-param>
<load-on-startup>0</load-on-startup>
<async-supported>true</async-supported>
</servlet>
<servlet-mapping>
<servlet-name>AtmosphereServlet</servlet-name>
<url-pattern>/atmosphere/*</url-pattern>
</servlet-mapping>All org.atmosphere.* init-param names remain the same. The key configuration
constants in ApplicationConfig are unchanged.
Atmosphere 4.0 adds several new init-params:
| Parameter | Default | Description |
|---|---|---|
org.atmosphere.useVirtualThreads |
true |
Use JDK 21 virtual threads for message dispatching. Set to false to use platform thread pools. |
org.atmosphere.redis.url |
redis://localhost:6379 |
Redis URL for RedisBroadcaster |
org.atmosphere.redis.password |
(none) | Redis password |
org.atmosphere.kafka.bootstrap.servers |
localhost:9092 |
Kafka bootstrap servers for KafkaBroadcaster |
org.atmosphere.kafka.topic.prefix |
atmosphere. |
Kafka topic name prefix |
org.atmosphere.kafka.group.id |
(auto-generated) | Kafka consumer group ID |
If you are migrating to Spring Boot 4.0, you can replace your web.xml and manual
servlet registration entirely with auto-configuration.
Before (manual servlet registration in Spring Boot 2.x/3.x):
@Configuration
public class AtmosphereConfig {
@Bean
public AtmosphereServlet atmosphereServlet() {
return new AtmosphereServlet();
}
@Bean
public ServletRegistrationBean<AtmosphereServlet> registration(AtmosphereServlet servlet) {
ServletRegistrationBean<AtmosphereServlet> reg =
new ServletRegistrationBean<>(servlet, "/atmosphere/*");
reg.setLoadOnStartup(0);
reg.setAsyncSupported(true);
reg.addInitParameter("org.atmosphere.cpr.packages", "com.yourapp");
return reg;
}
}After (Spring Boot 4.0 with auto-configuration):
Just add the starter dependency and configure via application.yml:
atmosphere:
packages: com.yourapp.atmosphere
servlet-path: /atmosphere/*
session-support: false
heartbeat-interval-in-seconds: 60
init-params:
org.atmosphere.websocket.maxIdleTime: "300000"No @Configuration class needed. The auto-configuration:
- Registers the
AtmosphereServletwith async support - Injects Spring beans via
SpringAtmosphereObjectFactory - Scans for Atmosphere annotations (
@ManagedService,@RoomService, etc.) - Exposes
AtmosphereFrameworkandRoomManageras Spring beans - Optionally integrates with Spring Boot Actuator for health checks
For Quarkus 3.21+, use the extension instead of web.xml:
# application.properties
quarkus.atmosphere.packages=com.yourapp.atmosphere
quarkus.atmosphere.servlet-path=/atmosphere/*
quarkus.atmosphere.load-on-startup=1
quarkus.atmosphere.session-support=falseNote: load-on-startup must be greater than 0 in Quarkus (Quarkus skips servlet
initialization when the value is 0 or negative).
All existing Atmosphere annotations remain and work as before:
@ManagedService-- still the primary way to create Atmosphere endpoints@AtmosphereHandlerService@AtmosphereService@WebSocketHandlerService@Ready,@Disconnect,@Message,@Heartbeat
New annotations in 4.0:
| Annotation | Module | Description |
|---|---|---|
@RoomService |
atmosphere-runtime |
Like @ManagedService but scoped to a Room. Supports path params (e.g., /chat/{roomId}). |
@AiEndpoint |
atmosphere-ai |
Eliminates boilerplate for AI streaming endpoints. Pair with @Prompt. |
@Prompt |
atmosphere-ai |
Marks the method that receives user messages in an @AiEndpoint. |
@RoomService example:
@RoomService(path = "/chat/{roomId}")
public class ChatRoom {
@Ready
public void onJoin(AtmosphereResource r) {
// invoked when a client joins the room
}
@Message
public String onMessage(String message) {
return message; // broadcast to all room members
}
@Disconnect
public void onLeave(AtmosphereResourceEvent event) {
// invoked when a client disconnects
}
}@AiEndpoint example:
@AiEndpoint(path = "/atmosphere/ai-chat",
systemPrompt = "You are a helpful assistant.")
public class MyAiChat {
@Prompt
public void onPrompt(String message, StreamingSession session) {
// Stream tokens back to the client
session.token("Hello ");
session.token("World!");
session.complete();
}
}The class org.atmosphere.cpr.AtmosphereServlet is unchanged. It still extends
jakarta.servlet.http.HttpServlet (was javax.servlet.http.HttpServlet).
The AtmosphereFramework class API is largely unchanged. Key points:
framework.init()still works the same wayframework.addAtmosphereHandler()still worksframework.objectFactory()now accepts a Spring or CDI object factory
Internal refactoring (4.0): AtmosphereFramework has been decomposed
into focused component classes (BroadcasterSetup, ClasspathScanner,
InterceptorRegistry, HandlerRegistry, WebSocketConfig,
FrameworkEventDispatcher, FrameworkDiagnostics). The public API is
fully preserved -- no application code changes are required.
If your code accesses AtmosphereHandlerWrapper fields directly:
// 3.x -- direct field access
wrapper.broadcaster = myBroadcaster;
wrapper.interceptors.add(myInterceptor);
handler = wrapper.atmosphereHandler;
// 4.0 -- use accessor methods
wrapper.setBroadcaster(myBroadcaster);
wrapper.interceptors().add(myInterceptor);
handler = wrapper.atmosphereHandler();The DefaultBroadcaster API is unchanged. One behavioral change:
- Virtual threads by default.
DefaultBroadcasternow usesReentrantLockinstead ofsynchronizedto avoid pinning virtual threads. This is transparent to application code, but if you have customBroadcasterimplementations that usesynchronizedblocks with blocking I/O, consider switching toReentrantLock. - Thread pool sizes (
maxProcessingThreads,maxAsyncWriteThreads) are ignored when virtual threads are enabled because virtual threads do not use bounded pools. Setorg.atmosphere.useVirtualThreads=falseto restore the old behavior.
Atmosphere 4.0 adds a high-level Room abstraction on top of Broadcaster:
RoomManager rooms = RoomManager.getOrCreate(framework);
Room lobby = rooms.room("lobby");
// Join a resource to a room
lobby.join(resource);
// Broadcast to all room members
lobby.broadcast("Hello everyone!");
// Send to a specific member by UUID
lobby.sendTo("some message", targetUuid);
// Track presence events
lobby.onPresence(event -> {
System.out.println(event.member() + " " + event.type());
});
// Virtual members (e.g., AI agents)
lobby.joinVirtual(myVirtualMember);Atmosphere 4.0 has optional Micrometer and OpenTelemetry support built into the core runtime (optional dependencies):
// Micrometer metrics
MeterRegistry registry = new SimpleMeterRegistry();
AtmosphereMetrics.install(framework, registry);
// Published metrics:
// atmosphere.connections.active (gauge)
// atmosphere.connections.total (counter)
// atmosphere.connections.disconnects (counter)
// atmosphere.broadcasters.active (gauge)
// atmosphere.messages.broadcast (counter)
// atmosphere.messages.delivered (counter)Clustering is now handled through dedicated modules instead of external plugins:
Before (2.x with atmosphere-redis plugin):
<dependency>
<groupId>org.atmosphere</groupId>
<artifactId>atmosphere-redis</artifactId>
<version>2.7.x</version>
</dependency>After (4.0):
<dependency>
<groupId>org.atmosphere</groupId>
<artifactId>atmosphere-redis</artifactId>
<version>4.0.0</version>
</dependency>Configuration is done via init-params:
<init-param>
<param-name>org.atmosphere.redis.url</param-name>
<param-value>redis://your-redis-host:6379</param-value>
</init-param>This is a breaking change. The old atmosphere.js (jQuery-based, 2.x/3.x)
has been replaced with a modern TypeScript library (5.0.0).
Before (2.x / 3.x):
<script src="atmosphere.js"></script>
<!-- or -->
<script src="jquery.atmosphere.js"></script>After (5.0):
npm install atmosphere.jsBefore (global atmosphere object):
var socket = atmosphere;
var request = new atmosphere.AtmosphereRequest();
request.url = '/chat';
request.transport = 'websocket';
request.fallbackTransport = 'long-polling';
request.onOpen = function(response) { /* ... */ };
request.onMessage = function(response) { /* ... */ };
request.onClose = function(response) { /* ... */ };
request.onError = function(response) { /* ... */ };
var subSocket = socket.subscribe(request);
subSocket.push(JSON.stringify(msg));After (ES module with TypeScript):
import { Atmosphere } from 'atmosphere.js';
const atmosphere = new Atmosphere();
const subscription = await atmosphere.subscribe(
{
url: '/chat',
transport: 'websocket',
fallbackTransport: 'long-polling',
},
{
open: (response) => { /* ... */ },
message: (response) => {
const data = response.responseBody;
// ...
},
close: (response) => { /* ... */ },
error: (error) => { /* ... */ },
reconnect: (request, response) => { /* ... */ },
transportFailure: (reason, request) => { /* ... */ },
}
);
subscription.push(JSON.stringify(msg));
// Or push objects directly (auto-serialized to JSON)
subscription.push({ author: 'user', message: 'hello' });| Feature | 2.x / 3.x | 5.0 |
|---|---|---|
| Module system | Global / AMD | ES modules + CommonJS |
| Language | JavaScript | TypeScript (with full type definitions) |
| jQuery dependency | Required for some transports | None |
| Subscribe | atmosphere.subscribe(request) |
await atmosphere.subscribe(request, handlers) |
| Callbacks | Properties on request (request.onOpen) |
Separate handlers object |
| Push | subSocket.push(string) |
subscription.push(string | object | ArrayBuffer) |
| Close | subSocket.close() |
await subscription.close() |
| Close all | atmosphere.unsubscribe() |
await atmosphere.closeAll() |
| Transport types | 'websocket', 'sse', 'long-polling', 'streaming', 'jsonp' |
'websocket', 'sse', 'long-polling', 'streaming' (JSONP removed) |
| Fallback | Single fallback | Configurable fallback: e.g. WebSocket → SSE or WebSocket → Long-Polling |
| Event system | Callbacks only | Callbacks + subscription.on('event', handler) |
| Interceptors | N/A | AtmosphereInterceptor interface for transforming outgoing/incoming messages |
| Reconnect config | request.reconnectInterval |
Same, plus maxReconnectOnClose, maxWebsocketErrorRetries |
The 5.0 client ships with framework-specific hooks:
React:
import { AtmosphereProvider, useAtmosphere, useRoom } from 'atmosphere.js/react';
function App() {
return (
<AtmosphereProvider>
<ChatComponent />
</AtmosphereProvider>
);
}
function ChatComponent() {
const { data, state, push } = useAtmosphere<ChatMessage>({
request: { url: '/atmosphere/chat', transport: 'websocket' },
});
return (
<div>
<p>State: {state}</p>
{data && <p>Last message: {JSON.stringify(data)}</p>}
<button onClick={() => push({ text: 'Hello!' })}>Send</button>
</div>
);
}Room support in React:
import { useRoom } from 'atmosphere.js/react';
function ChatRoom() {
const { members, messages, broadcast, joined } = useRoom<ChatMessage>({
request: { url: '/atmosphere/room', transport: 'websocket' },
room: 'lobby',
member: { id: 'user-1' },
});
return (
<div>
<p>Members: {members.map(m => m.id).join(', ')}</p>
{messages.map((m, i) => <p key={i}>{m.member.id}: {JSON.stringify(m.data)}</p>)}
<button onClick={() => broadcast({ text: 'Hello room!' })}>Send</button>
</div>
);
}Vue:
<script setup lang="ts">
import { useAtmosphere } from 'atmosphere.js/vue';
const { data, state, push } = useAtmosphere({
request: { url: '/atmosphere/chat', transport: 'websocket' },
});
</script>For @AiEndpoint server endpoints:
import { atmosphere, subscribeStreaming } from 'atmosphere.js';
const handle = await subscribeStreaming(atmosphere, {
url: '/atmosphere/ai-chat',
transport: 'websocket',
}, {
onToken: (token) => process.stdout.write(token),
onProgress: (msg) => console.log('Progress:', msg),
onComplete: (summary) => console.log('\nDone!', summary),
onError: (err) => console.error(err),
onMetadata: (key, value) => console.log(`${key}: ${value}`),
});
handle.send('What is Atmosphere?');For @RoomService server endpoints:
import { Atmosphere, AtmosphereRooms } from 'atmosphere.js';
const atmosphere = new Atmosphere();
const rooms = new AtmosphereRooms(atmosphere, {
url: '/atmosphere/room',
transport: 'websocket',
});
const lobby = await rooms.join('lobby', { id: 'user-1' }, {
message: (data, member) => console.log(`${member.id}: ${data}`),
join: (event) => console.log(`${event.member.id} joined`),
leave: (event) => console.log(`${event.member.id} left`),
joined: (roomName, memberList) => console.log(`Joined ${roomName} with`, memberList),
});
lobby.broadcast('Hello everyone!');
lobby.sendTo('user-2', 'Private message');
lobby.leave();| Container | Minimum Version | Servlet API | Notes |
|---|---|---|---|
| Tomcat | 11.0.x | 6.0 | Requires Jakarta EE 10 |
| Jetty | 12.0.x | 5.0/6.0 | Use jetty-ee10-servlet or jetty-ee11-servlet module |
| Undertow | 2.3.x | 5.0 | Used by Quarkus internally |
| WildFly | 30+ | 6.0 |
The following containers are no longer supported:
- Tomcat 7, 8, 8.5, 9, 10, 10.1
- Jetty 7, 8, 9, 10, 11
- GlassFish 3, 4, 5 (use GlassFish 7+ or Payara 6+)
- JBoss AS 7 / WildFly 8-29
- WebLogic 12c (use WebLogic 14.1.2+ with Jakarta EE 10)
If you embed Jetty, you need to update to the Jetty 12 API:
Before (Jetty 9/10/11):
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer;After (Jetty 12 EE10):
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
import org.eclipse.jetty.ee10.servlet.ServletHolder;
import org.eclipse.jetty.ee10.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer;
Server server = new Server();
ServerConnector connector = new ServerConnector(server);
connector.setPort(8080);
server.addConnector(connector);
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
context.setContextPath("/");
// Configure WebSocket FIRST (before AtmosphereServlet init)
JakartaWebSocketServletContainerInitializer.configure(context, (servletContext, serverContainer) -> {
// WebSocket ServerContainer is now available
});
// Add AtmosphereServlet
ServletHolder atmosphereServlet = new ServletHolder(AtmosphereServlet.class);
atmosphereServlet.setInitParameter(ApplicationConfig.ANNOTATION_PACKAGE, "com.yourapp");
atmosphereServlet.setInitParameter(ApplicationConfig.WEBSOCKET_SUPPORT, "true");
atmosphereServlet.setInitOrder(1);
atmosphereServlet.setAsyncSupported(true);
context.addServlet(atmosphereServlet, "/atmosphere/*");
server.setHandler(context);
server.start();Atmosphere 4.0 uses JDK 21 virtual threads by default for all message dispatching and async write operations. This provides near-unlimited scalability without configuring thread pool sizes.
ExecutorsFactorycreatesExecutors.newVirtualThreadPerTaskExecutor()by default- Thread pool size configurations (
maxProcessingThreads,maxAsyncWriteThreads) are ignored when virtual threads are enabled DefaultBroadcasterusesReentrantLockinstead ofsynchronizedto avoid pinning virtual threads- Only
ScheduledExecutorService(used for timed tasks like heartbeats) remains on platform threads, which is expected
To opt out:
<init-param>
<param-name>org.atmosphere.useVirtualThreads</param-name>
<param-value>false</param-value>
</init-param>The org.atmosphere.room package provides a higher-level abstraction over
broadcasters. See the Room interface, RoomManager, RoomMember, and
PresenceEvent classes.
Sessions that survive server restarts, with pluggable storage backends:
InMemorySessionStore(default, development only)SqliteSessionStore(requiresatmosphere-durable-sessions-sqlite)RedisSessionStore(requiresatmosphere-durable-sessions-redis)
Spring Boot configuration:
atmosphere:
durable-sessions:
enabled: true
session-ttl-minutes: 1440
cleanup-interval-seconds: 60The atmosphere-ai module provides @AiEndpoint, @Prompt, and
StreamingSession for building real-time AI chat applications with token-by-token
streaming.
Built-in Micrometer metrics and OpenTelemetry tracing (optional dependencies in
atmosphere-runtime). The Spring Boot starter integrates automatically with
Spring Boot Actuator.
First-party modules for clustering Atmosphere across multiple server instances.
- Core module (
atmosphere-runtime): Tests use TestNG. This is unchanged from 2.x/3.x. - Spring Boot starter: Tests use JUnit 5 via
spring-boot-starter-test. - Quarkus extension: Tests use JUnit 5 via
quarkus-junit5. - For new application code, either TestNG or JUnit 5 works fine.
# Run all tests
./mvnw test
# Run tests for a specific module
./mvnw test -pl modules/cpr
# Run a single test class
./mvnw test -pl modules/cpr -Dtest=BroadcasterTest
# Run a single test method
./mvnw test -pl modules/cpr -Dtest=BroadcasterTest#testBroadcast
# Run with debug output
./mvnw test -pl modules/cpr -Dtest=BroadcasterTest -Dsurefire.useFile=falseThe test suite requires --add-opens flags for concurrent lock access:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>
--add-opens java.base/java.util.concurrent.locks=ALL-UNNAMED
--add-opens java.base/java.util.concurrent=ALL-UNNAMED
</argLine>
</configuration>
</plugin>You have code or a dependency still referencing the old javax.servlet namespace.
Update all imports to jakarta.servlet. Check transitive dependencies with:
./mvnw dependency:tree | grep javax.servletIf you see warnings about virtual thread pinning (VirtualThread ... pinned),
check for synchronized blocks in your Atmosphere handlers that perform blocking
I/O. Replace them with ReentrantLock:
// Before (pins virtual thread)
synchronized (lock) {
blockingOperation();
}
// After (virtual-thread friendly)
private final ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
blockingOperation();
} finally {
lock.unlock();
}If the Atmosphere servlet is not starting in Quarkus, verify that
quarkus.atmosphere.load-on-startup is greater than 0:
quarkus.atmosphere.load-on-startup=1Quarkus skips setLoadOnStartup when the value is 0 or negative, unlike the
standard Servlet spec where 0 means "load on startup".
If your @ManagedService classes are not being discovered, make sure the
atmosphere.packages property includes the package containing your annotated
classes:
atmosphere:
packages: com.yourapp.atmosphereThe Spring Boot auto-configuration uses Spring's classpath scanner (since embedded
containers do not process @HandlesTypes from ServletContainerInitializer).
The JSONP transport has been removed in atmosphere.js 5.0. If your application relied on JSONP, switch to long-polling or SSE, which work across all modern browsers without JSONP.
The old jQuery-based atmosphere.js (and jquery.atmosphere.js) is not compatible
with Atmosphere 4.0 server. You must upgrade to atmosphere.js 5.0 (the npm
package). If you need to keep a jQuery-based frontend during migration, you can
proxy the old client through a compatibility layer, but this is not officially
supported.
- Upgrade JDK to 21+
- Upgrade server container (Tomcat 11, Jetty 12, etc.)
- Update
atmosphere-runtimeversion to4.0.0 - Replace
javax.servlet-apiwithjakarta.servlet-api - Replace
javax.websocket-apiwithjakarta.websocket-api - Find and replace all
javax.servletimports withjakarta.servlet - Find and replace all
javax.websocketimports withjakarta.websocket - Update
web.xmlnamespace (or switch to Spring Boot / Quarkus config) - Replace
atmosphere-javascript/ jQuery client withatmosphere.js5.0 npm package - Update client-side code to new subscribe/handlers API
- Replace any removed clustering plugins (JGroups, JMS, etc.) with Redis or Kafka modules
- Test with virtual threads enabled (default) and check for pinning issues
- Run full test suite:
./mvnw install