{"record":{"id":"28bb4ceb8628d56f","repo":"quarkusio/quarkus","slug":"no-template-extension-methods-declared-on-class","errorCode":null,"errorMessage":"No template extension methods declared on <class>; a template extension method must be static, non-private and must not return void","messagePattern":"No template extension methods declared on <class>; a template extension method must be static, non-private and must not return void","errorType":"exception","errorClass":"java.lang.IllegalStateException","httpStatus":null,"severity":"error","filePath":"extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java","lineNumber":1849,"sourceCode":"                    // Filter out non-static, synthetic, private and void methods\n                    continue;\n                }\n                if ((namespace == null || namespace.isEmpty()) && method.parameterTypes().isEmpty()) {\n                    // Filter out methods with no params for non-namespace extensions\n                    continue;\n                }\n                if (methods.containsKey(method)) {\n                    // Skip methods annotated with @TemplateExtension - method-level annotation takes precedence\n                    skippedMethodLevelAnnotation = true;\n                    continue;\n                }\n                found.add(method);\n                LOGGER.debugf(\"Found template extension method %s declared on %s\", method,\n                        method.declaringClass().name());\n            }\n\n            if (found.isEmpty() && !skippedMethodLevelAnnotation) {\n                throw new IllegalStateException(\"No template extension methods declared on \" + entry.getKey()\n                        + \"; a template extension method must be static, non-private and must not return void\");\n            }\n            for (MethodInfo method : found) {\n                produceExtensionMethod(index, extensionMethods, method, entry.getValue());\n            }\n        }\n    }\n\n    private void produceExtensionMethod(IndexView index, BuildProducer<TemplateExtensionMethodBuildItem> extensionMethods,\n            MethodInfo method, AnnotationInstance extensionAnnotation) {\n        // Analyze matchName and priority so that it could be used during validation\n        byte matchers = 0;\n        String matchName = null;\n        AnnotationValue matchNameValue = extensionAnnotation.value(ExtensionMethodGenerator.MATCH_NAME);\n        if (matchNameValue != null) {\n            matchName = matchNameValue.asString();\n            matchers++;\n        }","sourceCodeStart":1831,"sourceCodeEnd":1867,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java#L1831-L1867","documentation":"Thrown when a class is annotated with @TemplateExtension (on the class, no specific methods listed) but Qute finds no valid template extension methods inside it. A valid extension method must be static, non-private, and must not return void.","triggerScenarios":"Annotating a class with @TemplateExtension but its only candidate methods are instance (non-static) methods, private methods, or void-returning methods; the matchName/value/condition attributes on @TemplateExtension on individual methods disqualify all methods so `found` stays empty while no method-level annotation was seen (skippedMethodLevelAnnotation false).","commonSituations":"Copying an extension helper class into a CDI-aware project where methods became instance methods; accidentally marking the extension method private; writing a void mutating method expecting it to work as an extension.","solutions":["Make the extension method static and at least package-private (prefer public), with a non-void return type","Add the first parameter as the extended type, e.g. public static String shout(String self)","If the class should host no extensions, remove the @TemplateExtension annotation","Use @TemplateExtension on individual methods if class-level discovery does not fit"],"exampleFix":"// before\n@TemplateExtension\npublic class StringExt {\n    private void trimAll(String s) { } // private + void\n}\n\n// after\n@TemplateExtension\npublic class StringExt {\n    public static String trimAll(String self) {\n        return self.strip();\n    }\n}","handlingStrategy":"validation","validationCode":"// Validate an extension class before annotating it with @TemplateExtension\nboolean valid = Modifier.isStatic(m.getModifiers())\n    && !Modifier.isPrivate(m.getModifiers())\n    && m.getReturnType() != void.class;","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Follow the canonical signature: public static ReturnType ext(BaseType self, ...)","Never mark extension methods private or void-returning","Only put @TemplateExtension on classes meant to host extensions","Review the Qute extension method rules when upgrading Quarkus versions"],"tags":["quarkus","qute","template-extension-method","build-time-validation"],"backgroundTag":"invalid-template-extension-method","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"}