quarkusio/quarkus · error · IllegalStateException

@ExceptionHandler methods in @ControllerAdvice must be publi

Error message

@ExceptionHandler methods in @ControllerAdvice must be public instance methods

What it means

Quarkus scans @ControllerAdvice classes for @ExceptionHandler methods and generates ExceptionMapper classes for them via bytecode recording. Only public, non-static instance methods can be wrapped into JAX-RS mappers, so a method that is non-public or static causes a build-time failure.

Source

Thrown at extensions/spring-web/core/deployment/src/main/java/io/quarkus/spring/web/deployment/SpringWebProcessor.java:216

            BuildProducer<ReflectiveClassBuildItem> reflectiveClassProducer,
            BuildProducer<UnremovableBeanBuildItem> unremovableBeanProducer, IndexView index, ClassOutput classOutput,
            TypesUtil typesUtil, boolean isResteasyClassic) {

        AnnotationInstance controllerAdviceInstance = getSingleControllerAdviceInstance(index);
        if (controllerAdviceInstance == null) {
            return;
        }

        ClassInfo controllerAdvice = controllerAdviceInstance.target().asClass();
        List<MethodInfo> methods = controllerAdvice.methods();
        for (MethodInfo method : methods) {
            AnnotationInstance exceptionHandlerInstance = method.annotation(EXCEPTION_HANDLER);
            if (exceptionHandlerInstance == null) {
                continue;
            }

            if (!Modifier.isPublic(method.flags()) || Modifier.isStatic(method.flags())) {
                throw new IllegalStateException(
                        "@ExceptionHandler methods in @ControllerAdvice must be public instance methods");
            }

            DotName returnTypeDotName = method.returnType().name();
            if (DISALLOWED_EXCEPTION_CONTROLLER_RETURN_TYPES.contains(returnTypeDotName)) {
                throw new IllegalStateException(
                        "@ExceptionHandler methods in @ControllerAdvice classes can only have void, ResponseEntity or POJO return types");
            }

            if (!RESPONSE_ENTITY.equals(returnTypeDotName)) {
                reflectiveClassProducer.produce(
                        ReflectiveClassBuildItem.builder(returnTypeDotName.toString()).methods().fields().build());
            }

            // we need to generate one JAX-RS ExceptionMapper per Exception type
            Type[] handledExceptionTypes = exceptionHandlerInstance.value().asClassArray();
            for (Type handledExceptionType : handledExceptionTypes) {
                reflectiveClassProducer.produce(

View on GitHub (pinned to e1c734241f)

Solutions

  1. Make the @ExceptionHandler method public and remove the static modifier.
  2. If it doesn't need Spring request state, move static logic into a helper called from a public instance handler.
  3. Split unrelated handlers into separate @ControllerAdvice classes if visibility rules conflict.

Example fix

// before
@ExceptionHandler(IllegalArgumentException.class)
static ResponseEntity<String> handle(IllegalArgumentException e) { ... }

// after
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<String> handle(IllegalArgumentException e) { ... }
Defensive patterns

Strategy: validation

Validate before calling

// audit advice methods before build
@ControllerAdvice
public class Advice {
  @ExceptionHandler(MyEx.class)
  public ResponseEntity<?> handle(MyEx e) { ... } // must be public, non-static
}

Prevention

When it happens

Trigger: Declaring an @ExceptionHandler method inside a @ControllerAdvice class that is private/protected/package-private, or declared static.

Common situations: Copying Spring code where visibility was relaxed; making a helper handler static because it uses no state; IDE-generated handler methods with default visibility.

Related errors


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