quarkusio/quarkus · error · IllegalStateException

Non-static @Scheduled methods may not be declared on abstrac

Error message

Non-static @Scheduled methods may not be declared on abstract classes and interfaces: %s() declared on %s

What it means

@Scheduled methods must be either static or declared on a concrete (non-abstract, non-interface) class so the scheduler can instantiate/invoke them. During index scanning, collectScheduledMethods rejects non-static methods found on abstract classes or interfaces at build time.

Source

Thrown at extensions/scheduler/deployment/src/main/java/io/quarkus/scheduler/deployment/SchedulerProcessor.java:204

        Map<MethodInfo, List<AnnotationInstance>> staticScheduledMethods = new HashMap<>();
        List<AnnotationInstance> schedules = new ArrayList<>(
                beanArchives.getIndex().getAnnotations(SchedulerDotNames.SCHEDULED_NAME));
        for (AnnotationInstance annotationInstance : beanArchives.getIndex().getAnnotations(SchedulerDotNames.SCHEDULES_NAME)) {
            for (AnnotationInstance scheduledInstance : annotationInstance.value().asNestedArray()) {
                // We need to set the target of the containing instance
                schedules.add(AnnotationInstance.create(scheduledInstance.name(), annotationInstance.target(),
                        scheduledInstance.values()));
            }
        }
        for (AnnotationInstance annotationInstance : schedules) {
            if (annotationInstance.target().kind() != METHOD) {
                continue; // This should never happen as the annotation has @Target(METHOD)
            }
            MethodInfo method = annotationInstance.target().asMethod();
            ClassInfo declaringClass = method.declaringClass();
            if (!Modifier.isStatic(method.flags())
                    && (Modifier.isAbstract(declaringClass.flags()) || declaringClass.isInterface())) {
                throw new IllegalStateException(String.format(
                        "Non-static @Scheduled methods may not be declared on abstract classes and interfaces: %s() declared on %s",
                        method.name(), declaringClass.name()));
            }
            if (Modifier.isStatic(method.flags()) && !KotlinUtil.isSuspendMethod(method)) {
                List<AnnotationInstance> methodSchedules = staticScheduledMethods.get(method);
                if (methodSchedules == null) {
                    methodSchedules = new ArrayList<>();
                    staticScheduledMethods.put(method, methodSchedules);
                }
                methodSchedules.add(annotationInstance);
            }
        }

        for (Entry<MethodInfo, List<AnnotationInstance>> e : staticScheduledMethods.entrySet()) {
            MethodInfo method = e.getKey();
            scheduledBusinessMethods.produce(new ScheduledBusinessMethodItem(null, method, e.getValue(),
                    transformedAnnotations.hasAnnotation(method, SchedulerDotNames.NON_BLOCKING),
                    transformedAnnotations.hasAnnotation(method, SchedulerDotNames.RUN_ON_VIRTUAL_THREAD)));

View on GitHub (pinned to e1c734241f)

Solutions

  1. Move the @Scheduled method to a concrete class with a CDI scope
  2. Make the method static if it must live on an abstract type
  3. Remove @Scheduled from interfaces; schedule in concrete implementations instead
  4. Have each concrete subclass declare its own @Scheduled method

Example fix

// before
public interface Jobs {
    @Scheduled(every = "10s")
    void run(); // non-static on interface
}
// after
@ApplicationScoped
public class Jobs {
    @Scheduled(every = "10s")
    void run() { ... }
}
Defensive patterns

Strategy: validation

Validate before calling

static boolean isLegalScheduledMethod(Method m) {
    int cls = m.getDeclaringClass().getModifiers();
    return java.lang.reflect.Modifier.isStatic(m.getModifiers())
        || (!java.lang.reflect.Modifier.isAbstract(cls) && !m.getDeclaringClass().isInterface());
}

Prevention

When it happens

Trigger: Declaring a non-static @Scheduled method inside an interface or an abstract class that is not overridden by a concrete bean at build time.

Common situations: Putting scheduled job signatures in a shared interface expecting subclasses to inherit scheduling; base abstract class with a @Scheduled template method; refactoring a concrete scheduled class into an abstract hierarchy.

Related errors


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