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

  1. Track broadcaster lifecycle and create a new SseBroadcaster after close() if reuse is needed.
  2. Check closed state before broadcasting (guard with a flag or synchronize around close/broadcast).
  3. Move close() to application shutdown (or @PreDestroy) instead of per-request completion if reuse is intended.
  4. 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

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


AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05). Data as JSON: /api/errors/e45e4ced964479b2. Report an issue: GitHub.