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

  1. Use a normal (non result-replace) interceptor type for void/primitive/constructor targets
  2. Change the target method to one returning an object or array if replacement semantics are required
  3. 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

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


AI-assisted analysis of pinpoint-apm/pinpoint@744c3d3075 (2026-09-07). Data as JSON: /api/errors/b9c3803b876060b5. Report an issue: GitHub.