quarkusio/quarkus · error · IllegalStateException
Broadcaster has been closed
Error message
Broadcaster has been closed
What it means
SseBroadcasterImpl.checkClosed() throws IllegalStateException when any broadcaster operation is attempted after close() was called. Once closed, the broadcaster no longer accepts registration, broadcast, onError, or onClose callbacks.
Source
Thrown at independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/jaxrs/SseBroadcasterImpl.java:146
onErrorConsumers.forEach(consumer -> {
consumer.accept(eventSink, throwable);
});
}
private void notifyOnCloseListeners(SseEventSink eventSink) {
// First remove the eventSink from the outputQueue to ensure that
// concurrent calls to this method will notify listeners only once for a
// given eventSink instance.
if (outputQueue.remove(eventSink)) {
closeConsumers.forEach(consumer -> {
consumer.accept(eventSink);
});
}
}
private void checkClosed() {
if (closed.get()) {
throw new IllegalStateException("Broadcaster has been closed");
}
}
@Override
public synchronized void close() {
close(true);
}
@Override
public synchronized void close(final boolean cascading) {
if (!closed.compareAndSet(false, true)) {
return;
}
if (cascading) {
writeLock.lock();
try {
//Javadoc says close the broadcaster and all subscribed {@link SseEventSink} instances.
//is it necessary to close the subscribed SseEventSink ?View on GitHub (pinned to e1c734241f)
Solutions
- Track broadcaster lifecycle and create a new SseBroadcaster after close() if reuse is needed.
- Check closed state before broadcasting (guard with a flag or synchronize around close/broadcast).
- Move close() to application shutdown (or @PreDestroy) instead of per-request completion if reuse is intended.
- Catch IllegalStateException around broadcast calls and reinitialize the broadcaster.
Example fix
// before
broadcaster.close(); // on one request end
... // later, other request
broadcaster.broadcast(event); // IllegalStateException
// after
if (!broadcasterClosed) {
broadcaster.broadcast(event);
} Defensive patterns
Strategy: try-catch
Validate before calling
if (broadcasterClosed.get()) {
broadcaster = sse.newBroadcaster();
broadcasterClosed.set(false);
} Try / catch
try {
broadcaster.broadcast(event);
} catch (IllegalStateException e) {
broadcaster = sse.newBroadcaster(); // recreate after close
} Prevention
- Close broadcasters only at application shutdown if shared
- Recreate the broadcaster after close instead of reusing
- Synchronize close/broadcast on a shared monitor
- Handle reconnects by registering on a fresh broadcaster
When it happens
Trigger: Calling register(), broadcast(), onError(handler), or onClose(handler) on an SseBroadcaster after close(); often when a request completes and closes the broadcaster while another thread still holds a reference.
Common situations: Long-lived broadcaster field closed on one request completion and reused by another; asynchronous task broadcasting after the connection shut down; forgetting to recreate the broadcaster per resource instance.
Related errors
- Already closed
- Already registered on a broadcaster
- Can only work with Quarkus-REST instances: ${sseEventSink}
- Request already suspended
- retrieving all roles not supported when JAX-RS security cont
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/e45e4ced964479b2.
Report an issue: GitHub.