spring-boot: Enabling Problem Details doesn't work with proxying

Using Spring Boot 3.0.2, combining Problem Details (e.g., spring.mvc.problemdetails.enabled=true) with proxying (e.g., due to AOP) doesn’t work since ProblemDetailsExceptionHandler classes (duplicated in both servlet and reactive) are final.

Use case

I want to perform certain operations (e.g., logging) on the Problem Details returned. For this purpose, I am intercepting ExceptionHandler return values using AspectJ.

Reproduction

Consider the following example trying to intercept ExceptionHandlers to log ErrorResponse details:

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.http.ResponseEntity;
import org.springframework.lang.Nullable;
import org.springframework.web.ErrorResponse;

@EnableAspectJAutoProxy
@WebMvcTest(value = ProblemTest.TestConfig.class, properties = "spring.mvc.problemdetails.enabled=true")
class ProblemTest {

    @Test
    void contextLoads() {}

    @Configuration
    static class TestConfig {

        @Bean
        ProblemLoggingAspect problemLoggingAspect() {
            return new ProblemLoggingAspect();
        }

    }

    @Aspect
    static class ProblemLoggingAspect {

        @AfterReturning(
                pointcut = "@annotation(org.springframework.web.bind.annotation.ExceptionHandler)",
                returning = "returnValue")
        public void exceptionHandlerIntercept(JoinPoint joinPoint, Object returnValue) {
            ErrorResponse errorResponse = errorResponse(joinPoint, returnValue);
            if (errorResponse != null) {
                System.out.format("problem detail: %s%n", errorResponse.getBody().getDetail());
            }
        }

        @Nullable
        private ErrorResponse errorResponse(JoinPoint ignored, Object returnValue) {
            if (returnValue instanceof ErrorResponse errorResponse) {
                return errorResponse;
            } else if (returnValue instanceof ResponseEntity<?> responseEntity &&
                    responseEntity.getBody() instanceof ErrorResponse errorResponse) {
                return errorResponse;
            }
            return null;
        }

    }

}

Running this test results in the following failure:

...
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'problemDetailsExceptionHandler' defined in class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$ProblemDetailsErrorHandlingConfiguration.class]: Could not generate CGLIB subclass of class org.springframework.boot.autoconfigure.web.servlet.ProblemDetailsExceptionHandler: Common causes of this problem include using a final class or a non-visible class
	...
Caused by: org.springframework.aop.framework.AopConfigException: Could not generate CGLIB subclass of class org.springframework.boot.autoconfigure.web.servlet.ProblemDetailsExceptionHandler: Common causes of this problem include using a final class or a non-visible class
	...
	at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.createProxy(AbstractAutoProxyCreator.java:464) ~[spring-aop-6.0.4.jar:6.0.4]
	at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.wrapIfNecessary(AbstractAutoProxyCreator.java:369) ~[spring-aop-6.0.4.jar:6.0.4]
	...
Caused by: java.lang.IllegalArgumentException: Cannot subclass final class org.springframework.boot.autoconfigure.web.servlet.ProblemDetailsExceptionHandler
	at org.springframework.cglib.proxy.Enhancer.generateClass(Enhancer.java:653) ~[spring-core-6.0.4.jar:6.0.4]
	at org.springframework.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:26) ~[spring-core-6.0.4.jar:6.0.4]
	...

Notes

ProblemTest succeeds against Spring Boot 2.x and, when spring.mvc.problemdetails.enabled=false, 3.x. It only fails on 3.x when spring.mvc.problemdetails.enabled=true.

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Comments: 17 (17 by maintainers)

Most upvoted comments

@rstoyanchev, @sbrannen, @bclozel, thanks so much for the prompt responses. I will locally try out removing final modifiers and updating the aspect point-cut, and share the outcome here. Please allow me some time for this check.