spring-projects/spring-framework · error · AopInvocationException

Null return value from advice does not match primitive retur

Error message

Null return value from advice does not match primitive return type for: {method}

What it means

Thrown by JdkDynamicAopProxy.invoke for the JDK-proxy counterpart of error 76: after the interceptor chain proceeded, the return value is null but the method's declared return type is a primitive (and not void). Because a JDK proxy cannot assign null to a primitive return, Spring throws AopInvocationException with a descriptive message rather than letting a generic NullPointerException leak through the generated InvocationHandler. This is the JDK-proxy mirror of the CGLIB processReturnType check.

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 e8729d0438)

Solutions

  1. Never return null from advice for primitive-returning methods; return the type's default value (0 for numeric, false for boolean).
  2. Guard with method.getReturnType().isPrimitive() inside the advice and substitute the appropriate default.
  3. Ensure proceed() is invoked and its result returned when the advice should delegate.

Example fix

// before
@Around("execution(boolean *.isActive(..))")
public Object around(ProceedingJoinPoint pjp) {
  if (featureDisabled) return null;  // throws: primitive boolean
  return pjp.proceed();
}

// after
@Around("execution(boolean *.isActive(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
  if (featureDisabled) return false;
  return pjp.proceed();
}
Defensive patterns

Strategy: validation

Validate before calling

Object result = ...;
Class<?> rt = method.getReturnType();
if (result == null && rt != void.class && rt.isPrimitive()) {
  result = defaultPrimitiveValue(rt);
}
static Object defaultPrimitiveValue(Class<?> p) {
  if (p == boolean.class) return false;
  if (p == long.class) return 0L;
  if (p == float.class) return 0f;
  if (p == double.class) return 0d;
  if (p == char.class) return '\0';
  return 0;
}

Type guard

static boolean isPrimitiveReturn(Method m) {
  Class<?> rt = m.getReturnType();
  return rt != void.class && rt.isPrimitive();
}

Prevention

When it happens

Trigger: An Around advice returning null for a primitive-returning method on an interface-proxied bean (proxyTargetClass=false or interface-only target); the advice chain short-circuits and returns null; a MethodInterceptor returns null where the interface declares a primitive return.

Common situations: Writing @Around caching/null-handling advice for interface methods returning boolean/int/long; testing mocks that return null for primitive returns; switching from CGLIB to JDK proxy and the same null-return bug surfaces on the JDK path.

Related errors


AI-assisted analysis of spring-projects/spring-framework@e8729d0438 (2026-08-04). Data as JSON: /data/errors/fe6ef4106173d23f.json. Report an issue: GitHub.