{"record":{"id":"f302ac8245a473e5","repo":"quarkusio/quarkus","slug":"oncancel-was-already-called","errorCode":null,"errorMessage":"onCancel was already called","messagePattern":"onCancel was already called","errorType":"exception","errorClass":"java.lang.IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/MultiInvoker.java","lineNumber":118,"sourceCode":"        }\n\n        private void cancel() {\n            Runnable action = onCancel.getAndSet(CLEARED);\n            if (action != null && action != CLEARED) {\n                action.run();\n            }\n        }\n\n        public void onCancel(Runnable onCancel) {\n            if (this.onCancel.compareAndSet(null, onCancel)) {\n                // this was a first set\n            } else if (this.onCancel.get() == CLEARED) {\n                // already cleared\n                if (onCancel != null)\n                    onCancel.run();\n            } else {\n                // it was already set\n                throw new IllegalArgumentException(\"onCancel was already called\");\n            }\n        }\n    }\n\n    @Override\n    public <R> Multi<R> method(String name, Entity<?> entity, GenericType<R> responseType) {\n        return method(name, entity, responseType, false);\n    }\n\n    public <R> Multi<R> method(String name, Entity<?> entity, GenericType<R> responseType,\n            boolean wrapAsRestMultiResponse) {\n        AsyncInvokerImpl invoker = (AsyncInvokerImpl) invocationBuilder.rx();\n        CompletableFuture<BasicRestResponse> restResponseFuture = wrapAsRestMultiResponse ? new CompletableFuture<>() : null;\n        // FIXME: backpressure setting?\n        Multi<R> multi = Multi.createFrom().emitter(emitter -> {\n            MultiRequest<R> multiRequest = new MultiRequest<>(emitter);\n            RestClientRequestContext restClientRequestContext = invoker.performRequestInternal(name, entity, responseType,\n                    false);","sourceCodeStart":100,"sourceCodeEnd":136,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/MultiInvoker.java#L100-L136","documentation":"This IllegalArgumentException is thrown by the client's Multi invoker when a second onCancel callback is registered after one was already provided for a subscription (e.g. an SSE/LongStream Multi). The library stores at most one cancel callback per subscription; registering a second means the developer has subscribed the same Multi instance twice or set the callback twice.","triggerScenarios":"Calling withOnCancel (via registerForSse) twice on the same MultiInvoker/Multi instance — typically by subscribing the same Multi more than once, or passing a new onCancel after it was already set and not cleared.","commonSituations":"Reusing a single Multi returned by restClient.sse()/LongStream across multiple subscriptions; re-subscribing after a previous subscription completed or was cancelled; accidentally calling the registration API directly twice in custom client wiring.","solutions":["Create a fresh Multi (call the client method again) for each subscription instead of reusing one instance","Cancel/unsubscribe from the previous subscription before registering a new onCancel","Refactor to use a single subscription and fan out results internally (e.g. Multi.createBy().concatenating or a shared broadcast)"],"exampleFix":"// before\nMulti<String> multi = client.sse();\nmulti.subscribe().with(...);\nmulti.subscribe().with(...); // throws: onCancel was already called\n// after\nclient.sse().subscribe().with(...);\nclient.sse().subscribe().with(...); // new instance each call","handlingStrategy":"try-catch","validationCode":"if (multi == null || alreadySubscribed.get()) {\n    throw new IllegalStateException(\"Cannot register onCancel: Multi already subscribed\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    multi.subscribe().with(onItem, onFailure);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().contains(\"onCancel was already called\")) {\n        multi = client.sse(); // recreate and resubscribe\n        multi.subscribe().with(onItem, onFailure);\n    } else throw e;\n}","preventionTips":["Never reuse a Multi instance for multiple subscriptions; call the client method again","Track subscription state before registering callbacks","Use AtomicBoolean guards around registration logic"],"tags":["rest-client-reactive","multi","sse","illegal-argument"],"backgroundTag":"multi-already-subscribed","analyzedSha":"e1c734241f34c7919086ceb4c9262b4a58f6de44","analyzedAt":"2026-09-05T17:01:29.979Z","contentChangedAt":"2026-09-05T17:01:29.979Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}