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 CglibAopProxy.processReturnType when an advice chain (or the target) returned null for a method whose declared return type is a primitive (int, boolean, etc.). Java cannot unbox null to a primitive, so rather than letting an automatic NullPointerException escape from an opaque generated subclass, Spring raises a descriptive AopInvocationException. void methods are exempt.

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

Solutions

  1. In the @Around advice, never return null for primitive-returning methods; return the primitive default (0, false, 0.0) or the result of proceeding().
  2. If short-circuiting, detect the primitive return type via pjp.getMethod().getReturnType().isPrimitive() and return the appropriate default (e.g. 0/false).
  3. Ensure the advice actually invokes proceeding once when it should delegate.

Example fix

// before
@Around("execution(int *.count(..))")
public Object around(ProceedingJoinPoint pjp) {
  if (cacheMiss) return null;  // throws for int return
  return pjp.proceed();
}

// after
@Around("execution(int *.count(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
  if (cacheMiss) return 0;  // primitive default
  return pjp.proceed();
}
Defensive patterns

Strategy: validation

Validate before calling

Object result = ...; // from advice or proceed()
Class<?> rt = method.getReturnType();
if (result == null && rt != void.class && rt.isPrimitive()) {
  result = defaultPrimitiveValue(rt); // 0, false, 0.0
}

Type guard

static boolean isPrimitiveReturn(Method m) {
  Class<?> rt = m.getReturnType();
  return rt != void.class && rt.isPrimitive();
}
static Object defaultPrimitiveValue(Class<?> primitive) {
  if (primitive == boolean.class) return false;
  if (primitive == long.class)    return 0L;
  if (primitive == float.class)   return 0f;
  if (primitive == double.class)  return 0d;
  if (primitive == char.class)    return '\0';
  return 0; // int, short, byte
}

Prevention

When it happens

Trigger: An Around advice/interceptor that returns null for a method declared to return int/long/boolean/etc.; the target method returns null (only possible if bytecode was manipulated or via unusual reflection); a @Around advice that forgets to call proceed() or returns null deliberately.

Common situations: Writing a @Around aspect that does 'return null;' on a short-circuit path for a primitive-returning method; caching advice returning null on a miss for a primitive getter; mocking/testing advice that doesn't preserve return-type semantics.

Related errors


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