quarkusio/quarkus · error

Template extension method declared on must be static:

Error message

Template extension method declared on  must be static: 

What it means

Qute template extension methods (value resolvers exposed via @TemplateExtension or namespace extension methods) must be static. ExtensionMethodGenerator.validate() throws IllegalStateException during build-time bytecode generation when the annotated method is an instance method, since Qute calls the method statically without a receiver instance.

Solutions

  1. Add the static modifier to the extension method.
  2. If the method needs instance state, make it static and pass required values as parameters, or use a different resolver approach (e.g. a ValueResolver/template data).
  3. Ensure the class containing the method is indexed (not needed for static fix, but the class must be part of the app).
  4. Rebuild with ./mvnw install after the change; the error occurs during augmentation.

Example fix

// before
@TemplateExtension
String shout(String s) { return s.toUpperCase() + "!"; }
// after
@TemplateExtension
static String shout(String s) { return s.toUpperCase() + "!"; }
Defensive patterns

Strategy: validation

Validate before calling

// check before build
for (MethodInfo m : extensionMethods) {
    if (!Modifier.isStatic(m.flags())) {
        throw new IllegalStateException("@TemplateExtension method must be static: " + m);
    }
}

Try / catch

try {
    quarkusBuild augmentation...
} catch (IllegalStateException e) {
    if (e.getMessage().contains("must be static")) {
        // add 'static' to the annotated extension method
    }
}

Prevention

When it happens

Trigger: Declaring a template extension method as non-static — e.g. @TemplateExtension on an instance method of a CDI bean or helper class — then running Quarkus augmentation; building templates with a namespace method that isn't static.

Common situations: Migrating an older extension style to @TemplateExtension and forgetting 'static'; adding @TemplateExtension to a regular bean method; copying an example but dropping the static modifier.

Related errors


AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05). Data as JSON: /api/errors/eff9f6e9334029aa. Report an issue: GitHub.

Appendix: source

Thrown at independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/ExtensionMethodGenerator.java:81

    public static final DotName TEMPLATE_EXTENSION = DotName.createSimple(TemplateExtension.class.getName());
    public static final DotName TEMPLATE_ATTRIBUTE = DotName.createSimple(TemplateExtension.TemplateAttribute.class.getName());
    public static final String SUFFIX = "_Extension" + ValueResolverGenerator.SUFFIX;
    public static final String NAMESPACE_SUFFIX = "_Namespace" + SUFFIX;

    public static final String MATCH_NAME = "matchName";
    public static final String MATCH_NAMES = "matchNames";
    public static final String MATCH_REGEX = "matchRegex";
    public static final String PRIORITY = "priority";
    public static final String NAMESPACE = "namespace";
    public static final String PATTERN = "pattern";

    public ExtensionMethodGenerator(IndexView index, ClassOutput classOutput) {
        super(index, classOutput);
    }

    public static void validate(MethodInfo method, String namespace) {
        if (!Modifier.isStatic(method.flags())) {
            throw new IllegalStateException(
                    "Template extension method declared on " + method.declaringClass().name() + "  must be static: " + method);
        }
        if (method.returnType().kind() == Kind.VOID) {
            throw new IllegalStateException("Template extension method declared on " + method.declaringClass().name()
                    + " must not return void: " + method);
        }
        if (Modifier.isPrivate(method.flags())) {
            throw new IllegalStateException("Template extension method declared on " + method.declaringClass().name()
                    + " must not be private: " + method);
        }
    }

    /**
     *
     * @param method
     * @param matchName
     * @param matchNames
     * @param matchRegex

View on GitHub (pinned to e1c734241f)