spring-projects/spring-framework · error · AopInvocationException
Null return value from advice does not match primitive…
Error message
Null return value from advice does not match primitive return type for: {} What it means
CglibAopProxy.processReturnType() (line 423) inspects each invocation's return. If the target method declares a primitive return type (e.g. int) but the advice chain yields null (an Around advice returned null, or the target returned null via a boxed path), Spring throws AopInvocationException at line 435 because unboxing null to a primitive is impossible and would otherwise produce a confusing NPE.
Solutions
- In the @Around advice, never return null when the proxied method returns a primitive - return a proper default (0, false, etc.) or the result of proceed()
- Ensure proceeding.joinPoint.proceed() is always invoked and its result returned for primitive-returning methods
- Unit-test the advice against a primitive-returning method to catch null returns early
Example fix
// before
@Around("...")
public Object cache(ProceedingJoinPoint pjp) {
if (cacheMiss) return null; // method returns int -> boom
return pjp.proceed();
}
// after
@Around("...")
public Object cache(ProceedingJoinPoint pjp) throws Throwable {
if (cacheMiss) return 0; // match primitive return type
return pjp.proceed();
} Defensive patterns
Strategy: validation
Validate before calling
// Inside @Around, guard null returns for primitive methods:
Object result = pjp.proceed();
if (result == null && pjp.getSignature() instanceof MethodSignature ms
&& ms.getReturnType().isPrimitive()) {
return Defaults.defaultValue(ms.getReturnType());
}
return result; Type guard
private static boolean returnsPrimitive(Method m) {
return m != null && m.getReturnType().isPrimitive();
} Prevention
- Never return null from @Around advice for primitive-returning methods
- Always return proceed() (or a primitive-compatible default) in advice
When it happens
Trigger: An @Around (or other advice that controls the return) returns null for a joinpoint whose method has a primitive return type; the target method returns null through a proxy path that loses autoboxing.
Common situations: An @Around advice that returns null on some branch (e.g. cache miss returning null, or short-circuit) for a method declared to return int/boolean/long; caching/aspect that forgets to call proceed() and returns null.
Related errors
- Advice object [ ] is neither a supported subinterface of…
- Both 'constructorArgs' and 'constructorArgTypes' need to be…
- Could not generate CGLIB subclass of
- MethodInvocation is not a Spring ProxyMethodInvocation
- Need to invoke method
AI-assisted analysis of spring-projects/spring-framework@69bf83ad71 (2026-08-09).
Data as JSON: /api/errors/b2840068ed749344.
Report an issue: GitHub.
Appendix: source
Thrown at spring-aop/src/main/java/org/springframework/aop/framework/CglibAopProxy.java:435
/**
* Process a return value. Wraps a return of {@code this} if necessary to be the
* {@code proxy} and also verifies that {@code null} is not returned as a primitive.
* Also takes care of the conversion from {@code Mono} to Kotlin Coroutines if needed.
*/
private static @Nullable Object processReturnType(
Object proxy, @Nullable Object target, Method method, Object[] arguments, @Nullable Object returnValue) {
// Massage return value if necessary
if (returnValue != null && returnValue == target &&
!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
// Special case: it returned "this". Note that we can't help
// if the target sets a reference to itself in another returned object.
returnValue = proxy;
}
Class<?> returnType = method.getReturnType();
if (returnValue == null && returnType != void.class && returnType.isPrimitive()) {
throw new AopInvocationException(
"Null return value from advice does not match primitive return type for: " + method);
}
if (COROUTINES_REACTOR_PRESENT && KotlinDetector.isSuspendingFunction(method)) {
return COROUTINES_FLOW_CLASS_NAME.equals(new MethodParameter(method, -1).getParameterType().getName()) ?
CoroutinesUtils.asFlow(returnValue) :
CoroutinesUtils.awaitSingleOrNull(returnValue, arguments[arguments.length - 1]);
}
return returnValue;
}
/**
* Serializable replacement for CGLIB's NoOp interface.
* Public to allow use elsewhere in the framework.
*/
public static class SerializableNoOp implements NoOp, Serializable {
}
View on GitHub (pinned to 69bf83ad71)