{"record":{"id":"305d943a618667d0","repo":"quarkusio/quarkus","slug":"for-reactive-methods-running-on-the-event-loop-t","errorCode":null,"errorMessage":"For reactive methods running on the event loop, @Transactional can only be used if the method returns a `Uni`. Found '\" + result.getClass().getName() + \"' instead.","messagePattern":"For reactive methods running on the event loop, @Transactional can only be used if the method returns a `Uni`\\. Found '\" \\+ result\\.getClass\\(\\)\\.getName\\(\\) \\+ \"' instead\\.","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"extensions/reactive-transactions/runtime/src/main/java/io/quarkus/reactive/transaction/runtime/TransactionalInterceptorBase.java","lineNumber":253,"sourceCode":"                    // Note: Mutiny wraps checked exceptions in CompletionException, so they appear as RuntimeException here\n                    return actualRollback(connection.transaction(), exception);\n                });\n    }\n\n    private Uni<Void> actualRollback(Transaction transaction, Throwable exception) {\n        return toUni(transaction.rollback())\n                .onFailure().invoke(() -> LOG.tracef(\"Failed to rollback transaction: %s\", transaction))\n                .invoke(() -> LOG.tracef(\"Transaction rolled back: %s due to exception %s\", transaction, exception));\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public static Uni<Object> proceedUni(InvocationContext context) {\n        try {\n            Object result = context.proceed();\n            if (result instanceof Uni<?> uniResult) {\n                return (Uni<Object>) uniResult;\n            } else {\n                throw new IllegalStateException(\n                        \"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()) {","sourceCodeStart":235,"sourceCodeEnd":271,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/extensions/reactive-transactions/runtime/src/main/java/io/quarkus/reactive/transaction/runtime/TransactionalInterceptorBase.java#L235-L271","documentation":"A reactive @Transactional interceptor intercepted a method that runs on the event loop, but the method did not return a Uni, so the interceptor cannot attach transaction steps to the reactive chain. It throws IllegalStateException naming the actual return type found.","triggerScenarios":"Annotating a @Transactional (non-@ReactiveTransactional path) method running on the event loop whose return type is CompletionStage, Uni-based wrapper other than Uni, void, or a plain object instead of Uni<Object>.","commonSituations":"Adding @Transactional to a Panache repository method returning CompletionStage; forgetting @ReactiveTransactional semantics and returning a raw entity; copying blocking @Transactional usage into a reactive service.","solutions":["Change the method return type to Uni<T> (or Uni<Void> for void methods).","If the method is truly blocking, move it off the event loop (@RunOnVirtualThread or worker execution) and use blocking @Transactional.","Drop the @Transactional annotation if no transaction is required."],"exampleFix":"// before\n@Transactional\npublic CompletionStage<Item> find(Long id) { ... }\n// after\n@Transactional\npublic Uni<Item> find(Long id) { ... }","handlingStrategy":"type-guard","validationCode":"// before calling a @Transactional reactive method\nif (method.getReturnType() != Uni.class && !Modifier.isStatic(method.getModifiers()))\n    throw new IllegalStateException(\"@Transactional reactive method must return Uni: \" + method);","typeGuard":"static boolean returnsUni(java.lang.reflect.Method m) { return Uni.class == m.getReturnType(); }","tryCatchPattern":"try {\n    return service.find(id).await().indefinitely();\n} catch (IllegalStateException e) {\n    log.error(\"@Transactional method must return Uni: {}\", e.getMessage());\n    throw e;\n}","preventionTips":["Make every @Transactional reactive method return Uni (Uni<Void> instead of void)","Use @ReactiveTransactional for Uni-returning methods","Keep blocking methods off the event loop and use blocking @Transactional","Add annotation-processor or ArchUnit checks for return types"],"tags":["quarkus","reactive-transactions","uni","return-type"],"backgroundTag":"reactive-transaction-uni-required","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"}