{"record":{"id":"a76a9ee12d8c9171","repo":"quarkusio/quarkus","slug":"calling-a-method-annotated-with-transactional-fro","errorCode":null,"errorMessage":"Calling a method annotated with @Transactional from a method annotated with @WithSessionOnDemand is not supported. Use either @Transactional or @WithSessionOnDemand/@WithSession/@WithTransaction, but not both, throughout your whole application.","messagePattern":"Calling a method annotated with @Transactional from a method annotated with @WithSessionOnDemand is not supported\\. Use either @Transactional or @WithSessionOnDemand/@WithSession/@WithTransaction, but not both, throughout your whole application\\.","errorType":"exception","errorClass":"UnsupportedOperationException","httpStatus":null,"severity":"error","filePath":"extensions/reactive-transactions/runtime/src/main/java/io/quarkus/reactive/transaction/runtime/TransactionalInterceptorBase.java","lineNumber":272,"sourceCode":"                        \"For reactive methods running on the event loop, @Transactional can only be used if the method returns a `Uni`. Found '\"\n                                + result.getClass().getName() + \"' instead.\");\n            }\n        } catch (Exception e) {\n            return Uni.createFrom().failure(e);\n        }\n    }\n\n    public static boolean reactiveInterceptorShouldRun() {\n        boolean condition = Context.isOnEventLoopThread();\n        LOG.tracef(\"Transactional interceptor should run: %s\", condition);\n        return condition;\n    }\n\n    protected void validateLegacyPanacheAnnotations() {\n        // We are running on the retrieved context, however, the method also switch the safety flag.\n        Context ignored = vertxContext();\n        if (ContextLocals.get(SESSION_ON_DEMAND_KEY).isPresent()) {\n            throw new UnsupportedOperationException(\n                    \"Calling a method annotated with @Transactional from a method annotated with @WithSessionOnDemand is not supported. \"\n                            + \"Use either @Transactional or @WithSessionOnDemand/@WithSession/@WithTransaction, \"\n                            + \"but not both, throughout your whole application.\");\n        }\n\n        if (ContextLocals.get(WITH_TRANSACTION_METHOD_KEY).isPresent()) {\n            throw new UnsupportedOperationException(\n                    \"Calling a method annotated with @Transactional from a method annotated with @WithTransaction is not supported. \"\n                            + \"Use either @Transactional or @WithSessionOnDemand/@WithSession/@WithTransaction, \"\n                            + \"but not both, throughout your whole application.\");\n        }\n\n        if (ContextLocals.get(REACTIVE_TRANSACTIONAL_METHOD_KEY).isPresent()) {\n            throw new UnsupportedOperationException(\n                    \"Calling a method annotated with @Transactional from a method annotated with @ReactiveTransactional is not supported. \"\n                            + \"Use either @Transactional or @WithSessionOnDemand/@WithSession/@WithTransaction, \"\n                            + \"but not both, throughout your whole application.\");\n        }","sourceCodeStart":254,"sourceCodeEnd":290,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/extensions/reactive-transactions/runtime/src/main/java/io/quarkus/reactive/transaction/runtime/TransactionalInterceptorBase.java#L254-L290","documentation":"The reactive @Transactional interceptor detects a leftover @WithSessionOnDemand marker in the Vertx context locals, meaning a legacy Panache session annotation is active up the call stack. Mixing legacy Panache session annotations with plain @Transactional is unsupported, so it throws UnsupportedOperationException.","triggerScenarios":"Calling a method annotated @Transactional from a method annotated @WithSessionOnDemand (the flag SESSION_ON_DEMAND_KEY is present in the context).","commonSituations":"Old Panache code using @WithSession/@WithSessionOnDemand gradually migrated to @Transactional, leaving mixed annotations in one call chain.","solutions":["Remove @WithSessionOnDemand from the calling method and rely on @Transactional (or vice versa).","Standardize the whole application on @Transactional/@ReactiveTransactional for transactional reactive methods.","Grep the codebase for @WithSessionOnDemand/@WithSession/@WithTransaction and remove overlaps with @Transactional."],"exampleFix":"// before\n@WithSessionOnDemand\npublic Uni<Item> load() { return service.find(); }\n@Transactional public Uni<Item> find() { ... }\n// after\n@Transactional\npublic Uni<Item> load() { return service.find(); }\n@Transactional\npublic Uni<Item> find() { ... }","handlingStrategy":"validation","validationCode":"// detect mixed transactional annotations before runtime\nvoid checkAnnotations(Class<?> bean) {\n    for (Method m : bean.getDeclaredMethods()) {\n        boolean legacy = m.isAnnotationPresent(WithSessionOnDemand.class);\n        boolean tx = m.isAnnotationPresent(Transactional.class);\n        if (legacy && tx) throw new IllegalStateException(\"Mixed @WithSessionOnDemand + @Transactional in \" + bean);\n    }\n}","typeGuard":null,"tryCatchPattern":"try {\n    return txService.find(id);\n} catch (UnsupportedOperationException e) {\n    log.error(\"Incompatible tx annotations in call chain: {}\", e.getMessage());\n    throw e;\n}","preventionTips":["Pick one style (Transactional/@ReactiveTransactional) per application","Grep for @WithSession/@WithSessionOnDemand/@WithTransaction during migration","Write ArchUnit tests banning legacy Panache annotations alongside @Transactional","Review call graphs, not just individual methods, when migrating"],"tags":["quarkus","reactive-transactions","panache","annotation-conflict"],"backgroundTag":"transactional-annotation-conflict","analyzedSha":"e1c734241f34c7919086ceb4c9262b4a58f6de44","analyzedAt":"2026-09-05T17:01:29.979Z","contentChangedAt":"2026-09-05T17:01:29.979Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}