pinpoint-apm/pinpoint · error · InstrumentException
result-replace interceptor requires an object or array retur
Error message
result-replace interceptor requires an object or array return type.
What it means
InstrumentException thrown by ASMMethod.addInterceptor0 when an InterceptorType.RESULT_REPLACE interceptor is added to a method whose return type is not an object/array type (void, primitive, or a constructor). Result-replace bytecode rewriting swaps the value on the stack, which only exists for reference return types, so the engine rejects the combination up front.
Source
Thrown at agent-module/profiler/src/main/java/com/navercorp/pinpoint/profiler/instrument/ASMMethod.java:271
if (this.methodNode.hasInterceptor()) {
logger.warn("Skip adding interceptor. 'already intercepted method' class={}, interceptor={}", this.declaringClass.getName(), interceptorClass.getName());
return;
}
if (this.methodNode.isAbstract() || this.methodNode.isNative()) {
logger.info("Skip adding interceptor. 'abstract or native method' class={}, interceptor={}", this.declaringClass.getName(), interceptorClass.getName());
return;
}
int apiId = 0;
final InterceptorType interceptorType = interceptorDefinition.getInterceptorType();
if (interceptorType == InterceptorType.API_ID_AWARE || interceptorType == InterceptorType.ASYNC_CONTEXT_API_ID_AWARE) {
apiId = this.engineComponent.cacheApi(this.descriptor);
}
if (interceptorType == InterceptorType.RESULT_REPLACE && !this.methodNode.hasObjectOrArrayReturnType()) {
// constructors and void/primitive returns have no reference return value to replace.
throw new InstrumentException("result-replace interceptor requires an object or array return type."
+ " class=" + this.declaringClass.getName() + ", method=" + this.methodNode.getName() + this.methodNode.getDesc()
+ ", interceptor=" + interceptorClass.getName());
}
// add before interceptor.
if (isBeforeInterceptor(captureType) && interceptorDefinition.getBeforeMethod() != null) {
this.methodNode.addBeforeInterceptor(interceptorHolder, interceptorDefinition, apiId);
this.declaringClass.setModified(true);
} else {
if (isDebug) {
logger.debug("Skip adding before interceptorDefinition because the interceptorDefinition doesn't have before method: {}", interceptorClass.getName());
}
}
// add after interface.
if (isAfterInterceptor(captureType) && interceptorDefinition.getAfterMethod() != null) {
this.methodNode.addAfterInterceptor(interceptorHolder, interceptorDefinition, apiId);
this.declaringClass.setModified(true);View on GitHub (pinned to 744c3d3075)
Solutions
- Use a normal (non result-replace) interceptor type for void/primitive/constructor targets
- Change the target method to one returning an object or array if replacement semantics are required
- Adjust the interceptorType in the plugin's interceptor definition (e.g. to plain/around) so no return value is replaced
Example fix
// before addScopedInterceptor(ResultReplaceInterceptor.class, "traceObj", InterceptorType.RESULT_REPLACE); // after addScopedInterceptor(ResultReplaceInterceptor.class, "traceObj", InterceptorType.AROUND); // method returns void
Defensive patterns
Strategy: validation
Validate before calling
if (interceptorType == InterceptorType.RESULT_REPLACE
&& (method.getReturnType().equals(void.class) || method.getReturnType().isPrimitive())) {
throw new IllegalArgumentException("RESULT_REPLACE not allowed for " + method);
} Type guard
boolean supportsResultReplace(Method method) {
Class<?> r = method.getReturnType();
return !r.isPrimitive() && r != void.class && r != Void.class;
} Try / catch
try {
method.addInterceptor(interceptorClass);
} catch (InstrumentException e) {
if (e.getMessage().contains("result-replace interceptor requires")) {
logger.error("use a non RESULT_REPLACE interceptor for this method", e);
}
throw e;
} Prevention
- Only mark interceptors RESULT_REPLACE for methods returning objects or arrays
- Check constructors and void setters before reusing interceptor templates
- Add plugin unit tests that transform the exact target method signatures
When it happens
Trigger: Adding a result-replace interceptor to a void method, a primitive-returning method, or a constructor; plugin metadata mislabeling an interceptor as RESULT_REPLACE for such a method.
Common situations: Instrumenting constructors or getters returning int/boolean with a replace-semantics interceptor; copy-pasted plugin code reusing a RESULT_REPLACE template for the wrong method.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- invocation fail, className=
- not found 'set' method, className=
- unsupported Interceptor Type. ${interceptorClazz.getName()}
- invalid Type
- ${after} method not found. ${Arrays.toString(afterParamList)
AI-assisted analysis of pinpoint-apm/pinpoint@744c3d3075 (2026-09-07).
Data as JSON: /api/errors/b9c3803b876060b5.
Report an issue: GitHub.