quarkusio/quarkus · error · RuntimeException

A build step must be a non-static method: %s

Error message

A build step must be a non-static method: %s

What it means

ExtensionLoader.loadStepsFromClass rejects any @BuildStep-annotated method that is static, throwing 'A build step must be a non-static method: <method>'. Build steps are instantiated and invoked reflectively on the processor instance, so static methods cannot be build steps. This is a programming error in an extension's build-time processor.

Source

Thrown at core/deployment/src/main/java/io/quarkus/deployment/ExtensionLoader.java:433

            }
        }

        // get class-level configuration, if any
        final BuildSteps buildSteps = clazz.getAnnotation(BuildSteps.class);
        final Class<? extends BooleanSupplier>[] classOnlyIf = buildSteps == null ? EMPTY_BOOLEAN_SUPPLIER_CLASS_ARRAY
                : buildSteps.onlyIf();
        final Class<? extends BooleanSupplier>[] classOnlyIfNot = buildSteps == null ? EMPTY_BOOLEAN_SUPPLIER_CLASS_ARRAY
                : buildSteps.onlyIfNot();

        // now iterate the methods
        final List<Method> methods = nonAbstractBuildStepMethods(clazz);
        final Map<String, List<Method>> nameToMethods = methods.stream().collect(Collectors.groupingBy(Method::getName));

        MethodHandles.Lookup lookup = MethodHandles.publicLookup();
        for (Method method : methods) {
            final BuildStep buildStep = method.getAnnotation(BuildStep.class);
            if (Modifier.isStatic(method.getModifiers())) {
                throw new RuntimeException("A build step must be a non-static method: " + method);
            }
            if (!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
                method.setAccessible(true);
            }
            final Class<? extends BooleanSupplier>[] onlyIf = buildStep.onlyIf();
            final Class<? extends BooleanSupplier>[] onlyIfNot = buildStep.onlyIfNot();
            final Parameter[] methodParameters = method.getParameters();
            final Record recordAnnotation = method.getAnnotation(Record.class);
            final boolean isRecorder = recordAnnotation != null;
            final boolean identityComparison = !isRecorder || recordAnnotation.useIdentityComparisonForParameters();
            if (isRecorder) {
                boolean recorderFound = false;
                for (Class<?> p : method.getParameterTypes()) {
                    if (isRecorder(p)) {
                        recorderFound = true;
                        break;
                    }
                }

View on GitHub (pinned to e1c734241f)

Solutions

  1. Remove the static modifier from the @BuildStep method
  2. Move the logic to a private instance helper method and call it from the non-static @BuildStep method
  3. If a static utility is genuinely needed, keep @BuildStep as a thin non-static wrapper that delegates to it

Example fix

// before
@BuildStep
static void produceThing(BuildProducer<MyBuildItem> producer) { ... }
// after
@BuildStep
void produceThing(BuildProducer<MyBuildItem> producer) { ... }
Defensive patterns

Strategy: type-guard

Validate before calling

// Scan processor classes for static @BuildStep methods before registering
for (Method m : clazz.getDeclaredMethods()) {
    if (m.isAnnotationPresent(BuildStep.class) && Modifier.isStatic(m.getModifiers())) {
        throw new IllegalStateException("@BuildStep must not be static: " + m);
    }
}

Type guard

boolean isValidBuildStep(Method m) {
    return m.isAnnotationPresent(BuildStep.class) && !Modifier.isStatic(m.getModifiers());
}

Try / catch

try {
    ExtensionLoader.loadStepsFrom(classLoader, bsf);
} catch (RuntimeException e) {
    throw new IllegalStateException("Remove static modifier on the @BuildStep method named in: " + e.getMessage(), e);
}

Prevention

When it happens

Trigger: An extension author annotates a static method with @BuildStep inside a class listed in META-INF/quarkus-build-steps.list; during loadStepsFromClass the check Modifier.isStatic(method.getModifiers()) fails and throws.

Common situations: Writing a new build step and following the habit of static utility methods; refactoring an existing build step to static to silence an IDE warning; copying a method into a helper class while keeping @BuildStep.

Related errors


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