{"record":{"id":"b6bf76c239d53f88","repo":"quarkusio/quarkus","slug":"calling-a-method-annotated-with-transactional-fro-b6bf76","errorCode":null,"errorMessage":"Calling a method annotated with @Transactional from a method annotated with @ReactiveTransactional 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 @ReactiveTransactional 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":286,"sourceCode":"    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        }\n    }\n\n    /**\n     *\n     * @return the current vertx duplicated context\n     * @throws IllegalStateException If no vertx context is found or is not a safe context as mandated by the\n     *         {@link VertxContextSafetyToggle}\n     */\n    private static Context vertxContext() {\n        Context context = Vertx.currentContext();\n        if (context != null) {\n            VertxContextSafetyToggle.validateContextIfExists(ERROR_MSG, ERROR_MSG);\n            return context;\n        } else {","sourceCodeStart":268,"sourceCodeEnd":304,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/extensions/reactive-transactions/runtime/src/main/java/io/quarkus/reactive/transaction/runtime/TransactionalInterceptorBase.java#L268-L304","documentation":"The @Transactional interceptor detects a REACTIVE_TRANSACTIONAL_METHOD_KEY flag, meaning a @ReactiveTransactional method is up the call stack. Mixing the legacy @ReactiveTransactional annotation with plain @Transactional is unsupported and throws UnsupportedOperationException.","triggerScenarios":"Calling a @Transactional-annotated method from a method annotated with @ReactiveTransactional (the two interceptors cannot safely nest).","commonSituations":"Code migrated partially from @ReactiveTransactional to @Transactional; developers assuming both are equivalent and combining them.","solutions":["Use @Transactional consistently on both methods (the reactive interceptor handles Uni-returning methods).","Alternatively keep @ReactiveTransactional on both, but never nest one inside the other.","Add an architecture test to enforce a single transactional annotation style."],"exampleFix":"// before\n@ReactiveTransactional\npublic Uni<Item> outer() { return inner(); }\n@Transactional\npublic Uni<Item> inner() { ... }\n// after\n@Transactional\npublic Uni<Item> outer() { return inner(); }\n@Transactional\npublic Uni<Item> inner() { ... }","handlingStrategy":"validation","validationCode":"void check(Class<?> bean) {\n    for (Method m : bean.getDeclaredMethods())\n        if (m.isAnnotationPresent(ReactiveTransactional.class) && callsTransactionalMethods(bean, m))\n            throw new IllegalStateException(\"@ReactiveTransactional wrapping @Transactional: \" + m);\n}","typeGuard":null,"tryCatchPattern":"try {\n    return service.outer();\n} catch (UnsupportedOperationException e) {\n    log.error(\"Do not nest @ReactiveTransactional with @Transactional: {}\", e.getMessage());\n    throw e;\n}","preventionTips":["Standardize on @Transactional for reactive Uni methods","Never nest @ReactiveTransactional and @Transactional in one call chain","Add architecture tests enforcing one annotation style","During migration, migrate whole call chains at once"],"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"}