{"record":{"id":"36c05c812648f8a1","repo":"quarkusio/quarkus","slug":"permissionchecker-method-s-has-return-type-s","errorCode":null,"errorMessage":"@PermissionChecker method '%s' has return type '%s', but only supported return types are 'boolean' and 'Uni<Boolean>'. ","messagePattern":"@PermissionChecker method '(.+?)' has return type '(.+?)', but only supported return types are 'boolean' and 'Uni<Boolean>'\\. ","errorType":"exception","errorClass":"java.lang.RuntimeException","httpStatus":null,"severity":"error","filePath":"extensions/security/deployment/src/main/java/io/quarkus/security/deployment/PermissionSecurityChecks.java","lineNumber":143,"sourceCode":"        private static Map<String, PermissionCheckerMetadata> getPermissionCheckers(IndexView index) {\n            int permissionCheckerIndex = 0; // this ensures generated QuarkusPermission name is unique\n            var permissionCheckers = new HashMap<String, PermissionCheckerMetadata>();\n            for (var annotationInstance : index.getAnnotations(PERMISSION_CHECKER_NAME)) {\n                var checkerMethod = annotationInstance.target().asMethod();\n                if (Modifier.isPrivate(checkerMethod.flags())) {\n                    // we generate QuarkusPermission in the same package as where the @PermissionChecker is detected\n                    // so the checker method must be either public or package-private\n                    throw new RuntimeException(\"Private method '\" + toString(checkerMethod)\n                            + \"' cannot be annotated with the @PermissionChecker annotation\");\n                }\n                if (Modifier.isStatic(checkerMethod.flags())) {\n                    // checkers must be CDI bean member methods for now, so the checker method must not be static\n                    throw new RuntimeException(\"Static method '\" + toString(checkerMethod)\n                            + \"' cannot be annotated with the @PermissionChecker annotation\");\n                }\n                boolean isReactive = isUniBoolean(checkerMethod);\n                if (!isReactive && !isPrimitiveBoolean(checkerMethod)) {\n                    throw new RuntimeException((\"@PermissionChecker method '%s' has return type '%s', but only \" +\n                            \"supported return types are 'boolean' and 'Uni<Boolean>'. \")\n                            .formatted(toString(checkerMethod), checkerMethod.returnType().name()));\n                }\n\n                var permissionName = annotationInstance.value().asString();\n                if (permissionName.isBlank()) {\n                    throw new IllegalArgumentException(\n                            \"@PermissionChecker annotation placed on the '%s' attribute 'value' must not be blank\"\n                                    .formatted(toString(checkerMethod)));\n                }\n                boolean isBlocking = checkerMethod.hasDeclaredAnnotation(BLOCKING);\n                if (isBlocking && isReactive) {\n                    throw new IllegalArgumentException(\"\"\"\n                            @PermissionChecker annotation instance placed on the '%s' returns 'Uni<Boolean>' and is\n                            annotated with the @Blocking annotation; if you need to block, please return 'boolean'\n                            \"\"\".formatted(toString(checkerMethod)));\n                }\n","sourceCodeStart":125,"sourceCodeEnd":161,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/PermissionSecurityChecks.java#L125-L161","documentation":"A @PermissionChecker method must return either a primitive boolean (synchronous check) or Uni<Boolean> (reactive check). Any other return type cannot be interpreted by the permission runtime, so the build fails with this message naming the actual type.","triggerScenarios":"Annotating a method whose return type is e.g. Boolean (boxed), Optional<Boolean>, CompletionStage<Boolean>, Uni<Boolean> with generics mismatch (isUniBoolean checks the exact type/signature), int, or void with @PermissionChecker.","commonSituations":"Returning boxed Boolean from a helper that previously could return null; migrating a synchronous checker to async and accidentally using CompletionStage<Boolean> instead of Uni<Boolean>; Kotlin developers returning Boolean? .","solutions":["Change the return type to primitive boolean for synchronous checks","Change the return type to Uni<Boolean> for reactive checks (SmallRye Mutiny)","If using CompletionStage, convert it: Uni.createFrom().completionStage(...)","If returning null/optional semantics, restructure to return boolean with explicit false"],"exampleFix":"// before\n@PermissionChecker(\"can-edit\")\nBoolean canEdit(Document doc) { return doc != null && doc.ownerId != null; }\n// after\n@PermissionChecker(\"can-edit\")\nboolean canEdit(Document doc) { return doc != null && doc.ownerId != null; }\n// reactive alternative\n@PermissionChecker(\"can-edit\")\nUni<Boolean> canEditAsync(Document doc) { return authService.can(doc.ownerId).map(o -> o != null); }","handlingStrategy":"validation","validationCode":"for (Method m : SecurityChecks.class.getDeclaredMethods()) {\n    if (m.isAnnotationPresent(PermissionChecker.class)) {\n        Class<?> r = m.getReturnType();\n        if (r != boolean.class && !io.smallrye.mutiny.Uni.class.equals(r)) {\n            throw new IllegalStateException(\"@PermissionChecker \" + m.getName()\n                + \" must return boolean or Uni<Boolean>, got: \" + r);\n        }\n    }\n}","typeGuard":"boolean hasValidCheckerReturnType(java.lang.reflect.Method m) {\n    Class<?> r = m.getReturnType();\n    return r == boolean.class || io.smallrye.mutiny.Uni.class.equals(r);\n}","tryCatchPattern":"try {\n    appBootstrap();\n} catch (RuntimeException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"only supported return types are 'boolean' and 'Uni<Boolean>'\")) {\n        throw new IllegalStateException(\"Change @PermissionChecker return type to boolean or Uni<Boolean>\", e);\n    }\n    throw e;\n}","preventionTips":["Use primitive boolean, never boxed Boolean/Optional","Use Uni<Boolean> (Mutiny), not CompletionStage, for async checks","Type-check @PermissionChecker methods in review and convention tests","Follow io.quarkus.security.PermissionChecker documentation examples"],"tags":["security","build-time","return-type","mutiny"],"backgroundTag":"invalid-permission-checker-return-type","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"}