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
Thrown at invocation time by JdkDynamicAopProxy.invoke when the proxy returned null for a method whose declared return type is a primitive (int, boolean, etc.). Because a primitive cannot hold null, the runtime detects the mismatch and aborts with AopInvocationException. This almost always points to an around-advice/MethodInterceptor that short-circuits proceed() and returns null.
Solutions
- Fix the advice: when intercepting a primitive-returning method, return the boxed default (0 for int, false for boolean) or ensure proceed() is actually called.
- If the advice legitimately cannot compute a value, change the proxied method's return type to the wrapper type (Integer instead of int) so null is valid.
- Make the fallback branch in the interceptor return the appropriate primitive default instead of null.
Example fix
// before
public class CacheInterceptor implements MethodInterceptor {
public Object invoke(MethodInvocation i) throws Throwable {
if (cacheMiss) return null; // breaks int return
return i.proceed();
}
}
// after
public class CacheInterceptor implements MethodInterceptor {
public Object invoke(MethodInvocation i) throws Throwable {
if (cacheMiss) {
Class<?> rt = i.getMethod().getReturnType();
return rt.isPrimitive() ? defaultFor(rt) : null;
}
return i.proceed();
}
} Defensive patterns
Strategy: validation
Validate before calling
// In an around-advice, before returning null:
Class<?> rt = invocation.getMethod().getReturnType();
if (rt.isPrimitive()) {
// cannot return null; return the boxed default
return defaultPrimitive(rt);
}
return null;
static Object defaultPrimitive(Class<?> p) {
if (p == boolean.class) return false;
if (p == byte.class) return (byte) 0;
if (p == short.class) return (short) 0;
if (p == int.class) return 0;
if (p == long.class) return 0L;
if (p == float.class) return 0f;
if (p == double.class) return 0d;
if (p == char.class) return '\0';
throw new IllegalStateException();
} Type guard
static boolean returnsPrimitive(Method m) { return m.getReturnType().isPrimitive(); } Try / catch
try { return proxy.someIntMethod(); }
catch (AopInvocationException e) {
if (e.getMessage().contains("primitive return type")) { /* fix advice to not return null */ }
throw e;
} Prevention
- Never return null from an interceptor wrapping a primitive-returning method.
- Prefer calling mi.proceed() in advice unless you intentionally short-circuit.
- Change primitive returns to boxed wrapper types if null is a legitimate sentinel.
When it happens
Trigger: A proxied method 'public int compute()' is intercepted by an advice (e.g. caching, fallback, or mis-written MethodInterceptor) that returns null without invoking proceed(), or whose proceed() returned null. Line 235-238 fires when retVal == null && returnType != void.class && returnType.isPrimitive().
Common situations: A caching interceptor returns null on a miss for a primitive-returning method; a circuit-breaker/fallback advice returns null; a @Around advice written to 'return null' on a guard path; mocking the wrong return type in tests of an AOP-proxied bean.
Related errors
- AOP configuration seems to be invalid: tried calling method
- Cannot create class proxy for TargetSource with null target…
- Could not generate CGLIB subclass of
- Invalid return type for async method (only Future and void…
- MethodInvocation is not a Spring ProxyMethodInvocation
AI-assisted analysis of spring-projects/spring-framework@69bf83ad71 (2026-08-09).
Data as JSON: /api/errors/916f7cf76cb4b841.
Report an issue: GitHub.
Appendix: source
Thrown at spring-aop/src/main/java/org/springframework/aop/framework/JdkDynamicAopProxy.java:236
// We need to create a method invocation...
MethodInvocation invocation =
new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
// Proceed to the joinpoint through the interceptor chain.
retVal = invocation.proceed();
}
// Massage return value if necessary.
Class<?> returnType = method.getReturnType();
if (retVal != null && retVal == target &&
returnType != Object.class && returnType.isInstance(proxy) &&
!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
// Special case: it returned "this" and the return type of the method
// is type-compatible. Note that we can't help if the target sets
// a reference to itself in another returned object.
retVal = proxy;
}
else if (retVal == 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(retVal) : CoroutinesUtils.awaitSingleOrNull(retVal, args[args.length - 1]);
}
return retVal;
}
finally {
if (target != null && !targetSource.isStatic()) {
// Must have come from TargetSource.
targetSource.releaseTarget(target);
}
if (setProxyContext) {
// Restore old proxy.
AopContext.setCurrentProxy(oldProxy);
}
}View on GitHub (pinned to 69bf83ad71)