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
- Remove the static modifier from the @BuildStep method
- Move the logic to a private instance helper method and call it from the non-static @BuildStep method
- 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
- Never mark @BuildStep methods static — build steps are invoked on processor instances
- Use an ArchUnit/test rule to forbid static @BuildStep methods in your extension
- Review IDE refactorings that add static to annotated methods
- Check extension behavior by running its deployment-module tests before releasing
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
- Failed to load steps from %s
- A generic type is not allowed here; try creating a subclass
- Build item class must be leaf (final) types: %s
- Cannot construct empty build items
- Extension passed an invalid shutdown handler
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/2d1954a8c042f127.
Report an issue: GitHub.