{"id":"fe6ef4106173d23f","repo":"spring-projects/spring-framework","slug":"null-return-value-from-advice-does-not-match-primi-fe6ef4","errorCode":null,"errorMessage":"Null return value from advice does not match primitive return type for: {method}","messagePattern":"Null return value from advice does not match primitive return type for: (.+?)","errorType":"exception","errorClass":"AopInvocationException","httpStatus":null,"severity":"error","filePath":"spring-aop/src/main/java/org/springframework/aop/framework/JdkDynamicAopProxy.java","lineNumber":236,"sourceCode":"\t\t\t\t// We need to create a method invocation...\n\t\t\t\tMethodInvocation invocation =\n\t\t\t\t\t\tnew ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);\n\t\t\t\t// Proceed to the joinpoint through the interceptor chain.\n\t\t\t\tretVal = invocation.proceed();\n\t\t\t}\n\n\t\t\t// Massage return value if necessary.\n\t\t\tClass<?> returnType = method.getReturnType();\n\t\t\tif (retVal != null && retVal == target &&\n\t\t\t\t\treturnType != Object.class && returnType.isInstance(proxy) &&\n\t\t\t\t\t!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {\n\t\t\t\t// Special case: it returned \"this\" and the return type of the method\n\t\t\t\t// is type-compatible. Note that we can't help if the target sets\n\t\t\t\t// a reference to itself in another returned object.\n\t\t\t\tretVal = proxy;\n\t\t\t}\n\t\t\telse if (retVal == null && returnType != void.class && returnType.isPrimitive()) {\n\t\t\t\tthrow new AopInvocationException(\n\t\t\t\t\t\t\"Null return value from advice does not match primitive return type for: \" + method);\n\t\t\t}\n\t\t\tif (COROUTINES_REACTOR_PRESENT && KotlinDetector.isSuspendingFunction(method)) {\n\t\t\t\treturn COROUTINES_FLOW_CLASS_NAME.equals(new MethodParameter(method, -1).getParameterType().getName()) ?\n\t\t\t\t\t\tCoroutinesUtils.asFlow(retVal) : CoroutinesUtils.awaitSingleOrNull(retVal, args[args.length - 1]);\n\t\t\t}\n\t\t\treturn retVal;\n\t\t}\n\t\tfinally {\n\t\t\tif (target != null && !targetSource.isStatic()) {\n\t\t\t\t// Must have come from TargetSource.\n\t\t\t\ttargetSource.releaseTarget(target);\n\t\t\t}\n\t\t\tif (setProxyContext) {\n\t\t\t\t// Restore old proxy.\n\t\t\t\tAopContext.setCurrentProxy(oldProxy);\n\t\t\t}\n\t\t}","sourceCodeStart":218,"sourceCodeEnd":254,"githubUrl":"https://github.com/spring-projects/spring-framework/blob/e8729d043887bf0d0baf91e062e909b56eb2b708/spring-aop/src/main/java/org/springframework/aop/framework/JdkDynamicAopProxy.java#L218-L254","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Never return null from advice for primitive-returning methods; return the type's default value (0 for numeric, false for boolean).","Guard with method.getReturnType().isPrimitive() inside the advice and substitute the appropriate default.","Ensure proceed() is invoked and its result returned when the advice should delegate."],"exampleFix":"// before\n@Around(\"execution(boolean *.isActive(..))\")\npublic Object around(ProceedingJoinPoint pjp) {\n  if (featureDisabled) return null;  // throws: primitive boolean\n  return pjp.proceed();\n}\n\n// after\n@Around(\"execution(boolean *.isActive(..))\")\npublic Object around(ProceedingJoinPoint pjp) throws Throwable {\n  if (featureDisabled) return false;\n  return pjp.proceed();\n}","handlingStrategy":"validation","validationCode":"Object result = ...;\nClass<?> rt = method.getReturnType();\nif (result == null && rt != void.class && rt.isPrimitive()) {\n  result = defaultPrimitiveValue(rt);\n}\nstatic Object defaultPrimitiveValue(Class<?> p) {\n  if (p == boolean.class) return false;\n  if (p == long.class) return 0L;\n  if (p == float.class) return 0f;\n  if (p == double.class) return 0d;\n  if (p == char.class) return '\\0';\n  return 0;\n}","typeGuard":"static boolean isPrimitiveReturn(Method m) {\n  Class<?> rt = m.getReturnType();\n  return rt != void.class && rt.isPrimitive();\n}","tryCatchPattern":null,"preventionTips":["Return a primitive default (0/false) instead of null from advice on primitive-returning interface methods.","Detect primitive return types in the advice and branch.","Unit-test advice against boolean/int-returning interface methods."],"tags":["spring-aop","jdk-proxy","advice","primitive","return-value"],"analyzedSha":"e8729d043887bf0d0baf91e062e909b56eb2b708","analyzedAt":"2026-08-04T19:07:39.725Z","schemaVersion":2}