{"record":{"id":"b9c3803b876060b5","repo":"pinpoint-apm/pinpoint","slug":"result-replace-interceptor-requires-an-object-or-a","errorCode":null,"errorMessage":"result-replace interceptor requires an object or array return type.","messagePattern":"result-replace interceptor requires an object or array return type\\.","errorType":"exception","errorClass":"InstrumentException","httpStatus":null,"severity":"error","filePath":"agent-module/profiler/src/main/java/com/navercorp/pinpoint/profiler/instrument/ASMMethod.java","lineNumber":271,"sourceCode":"        if (this.methodNode.hasInterceptor()) {\n            logger.warn(\"Skip adding interceptor. 'already intercepted method' class={}, interceptor={}\", this.declaringClass.getName(), interceptorClass.getName());\n            return;\n        }\n\n        if (this.methodNode.isAbstract() || this.methodNode.isNative()) {\n            logger.info(\"Skip adding interceptor. 'abstract or native method' class={}, interceptor={}\", this.declaringClass.getName(), interceptorClass.getName());\n            return;\n        }\n\n        int apiId = 0;\n        final InterceptorType interceptorType = interceptorDefinition.getInterceptorType();\n        if (interceptorType == InterceptorType.API_ID_AWARE || interceptorType == InterceptorType.ASYNC_CONTEXT_API_ID_AWARE) {\n            apiId = this.engineComponent.cacheApi(this.descriptor);\n        }\n\n        if (interceptorType == InterceptorType.RESULT_REPLACE && !this.methodNode.hasObjectOrArrayReturnType()) {\n            // constructors and void/primitive returns have no reference return value to replace.\n            throw new InstrumentException(\"result-replace interceptor requires an object or array return type.\"\n                    + \" class=\" + this.declaringClass.getName() + \", method=\" + this.methodNode.getName() + this.methodNode.getDesc()\n                    + \", interceptor=\" + interceptorClass.getName());\n        }\n\n        // add before interceptor.\n        if (isBeforeInterceptor(captureType) && interceptorDefinition.getBeforeMethod() != null) {\n            this.methodNode.addBeforeInterceptor(interceptorHolder, interceptorDefinition, apiId);\n            this.declaringClass.setModified(true);\n        } else {\n            if (isDebug) {\n                logger.debug(\"Skip adding before interceptorDefinition because the interceptorDefinition doesn't have before method: {}\", interceptorClass.getName());\n            }\n        }\n\n        // add after interface.\n        if (isAfterInterceptor(captureType) && interceptorDefinition.getAfterMethod() != null) {\n            this.methodNode.addAfterInterceptor(interceptorHolder, interceptorDefinition, apiId);\n            this.declaringClass.setModified(true);","sourceCodeStart":253,"sourceCodeEnd":289,"githubUrl":"https://github.com/pinpoint-apm/pinpoint/blob/744c3d3075e595656abb1ae331ad2c0e4c9eb996/agent-module/profiler/src/main/java/com/navercorp/pinpoint/profiler/instrument/ASMMethod.java#L253-L289","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\naddScopedInterceptor(ResultReplaceInterceptor.class, \"traceObj\", InterceptorType.RESULT_REPLACE);\n// after\naddScopedInterceptor(ResultReplaceInterceptor.class, \"traceObj\", InterceptorType.AROUND); // method returns void","handlingStrategy":"validation","validationCode":"if (interceptorType == InterceptorType.RESULT_REPLACE\n        && (method.getReturnType().equals(void.class) || method.getReturnType().isPrimitive())) {\n    throw new IllegalArgumentException(\"RESULT_REPLACE not allowed for \" + method);\n}","typeGuard":"boolean supportsResultReplace(Method method) {\n    Class<?> r = method.getReturnType();\n    return !r.isPrimitive() && r != void.class && r != Void.class;\n}","tryCatchPattern":"try {\n    method.addInterceptor(interceptorClass);\n} catch (InstrumentException e) {\n    if (e.getMessage().contains(\"result-replace interceptor requires\")) {\n        logger.error(\"use a non RESULT_REPLACE interceptor for this method\", e);\n    }\n    throw e;\n}","preventionTips":["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"],"tags":["instrumentation","interceptor","type-mismatch"],"backgroundTag":"invalid-argument-value","analyzedSha":"744c3d3075e595656abb1ae331ad2c0e4c9eb996","analyzedAt":"2026-09-07T18:48:45.289Z","contentChangedAt":"2026-09-07T18:48:45.289Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}