diff --git a/docs/supported-libraries.md b/docs/supported-libraries.md
index 6cddd284af7d..54a25f62542c 100644
--- a/docs/supported-libraries.md
+++ b/docs/supported-libraries.md
@@ -90,6 +90,7 @@ These are the supported libraries and frameworks:
| [Jedis](https://github.com/xetorthio/jedis) | 1.4+ | N/A | [Database Client Spans] |
| [JMS](https://javaee.github.io/javaee-spec/javadocs/javax/jms/package-summary.html) | 1.1+ | N/A | [Messaging Spans] |
| [Jodd Http](https://http.jodd.org/) | 4.2+ | N/A | [HTTP Client Spans], [HTTP Client Metrics] |
+| [JSON-RPC](https://github.com/briandilley/jsonrpc4j) | 1.3.3+ | N/A | [RPC Client Spans], [RPC Client Metrics], [RPC Server Spans], [RPC Server Metrics] |
| [JSP](https://javaee.github.io/javaee-spec/javadocs/javax/servlet/jsp/package-summary.html) | 2.3.x only | N/A | Controller Spans [3] |
| [Kotlin Coroutines](https://kotlinlang.org/docs/coroutines-overview.html) | 1.0+ | N/A | Context propagation |
| [Ktor](https://github.com/ktorio/ktor) | 1.0+ | [opentelemetry-ktor-1.0](../instrumentation/ktor/ktor-1.0/library),
[opentelemetry-ktor-2.0](../instrumentation/ktor/ktor-2.0/library),
[opentelemetry-ktor-3.0](../instrumentation/ktor/ktor-3.0/library) | [HTTP Client Spans], [HTTP Client Metrics], [HTTP Server Spans], [HTTP Server Metrics] |
diff --git a/instrumentation/jsonrpc4j-1.3/javaagent/build.gradle.kts b/instrumentation/jsonrpc4j-1.3/javaagent/build.gradle.kts
new file mode 100644
index 000000000000..0fa473ae49cd
--- /dev/null
+++ b/instrumentation/jsonrpc4j-1.3/javaagent/build.gradle.kts
@@ -0,0 +1,26 @@
+plugins {
+ id("otel.javaagent-instrumentation")
+}
+
+muzzle {
+ pass {
+ group.set("com.github.briandilley.jsonrpc4j")
+ module.set("jsonrpc4j")
+ versions.set("[1.3.3,)")
+ assertInverse.set(true)
+ }
+}
+
+dependencies {
+ implementation(project(":instrumentation:jsonrpc4j-1.3:library"))
+
+ library("com.github.briandilley.jsonrpc4j:jsonrpc4j:1.3.3")
+
+ testImplementation(project(":instrumentation:jsonrpc4j-1.3:testing"))
+}
+
+tasks {
+ test {
+ jvmArgs("-Dotel.javaagent.experimental.thread-propagation-debugger.enabled=false")
+ }
+}
diff --git a/instrumentation/jsonrpc4j-1.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_3/JsonRpcClientInstrumentation.java b/instrumentation/jsonrpc4j-1.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_3/JsonRpcClientInstrumentation.java
new file mode 100644
index 000000000000..5a985825c8db
--- /dev/null
+++ b/instrumentation/jsonrpc4j-1.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_3/JsonRpcClientInstrumentation.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.jsonrpc4j.v1_3;
+
+import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
+import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface;
+import static io.opentelemetry.javaagent.instrumentation.jsonrpc4j.v1_3.JsonRpcSingletons.CLIENT_INSTRUMENTER;
+import static net.bytebuddy.matcher.ElementMatchers.isMethod;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.returns;
+import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
+import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
+
+import io.opentelemetry.context.Context;
+import io.opentelemetry.context.Scope;
+import io.opentelemetry.instrumentation.jsonrpc4j.v1_3.SimpleJsonRpcRequest;
+import io.opentelemetry.instrumentation.jsonrpc4j.v1_3.SimpleJsonRpcResponse;
+import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
+import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
+import java.util.Map;
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+
+public class JsonRpcClientInstrumentation implements TypeInstrumentation {
+
+ @Override
+ public ElementMatcher classLoaderOptimization() {
+ return hasClassesNamed("com.googlecode.jsonrpc4j.IJsonRpcClient");
+ }
+
+ @Override
+ public ElementMatcher typeMatcher() {
+ // match JsonRpcHttpClient and JsonRpcRestClient
+ return implementsInterface(named("com.googlecode.jsonrpc4j.IJsonRpcClient"));
+ }
+
+ @Override
+ public void transform(TypeTransformer transformer) {
+ transformer.applyAdviceToMethod(
+ isMethod()
+ .and(named("invoke"))
+ .and(takesArguments(4))
+ .and(takesArgument(0, String.class))
+ .and(takesArgument(1, Object.class))
+ .and(takesArgument(2, named("java.lang.reflect.Type")))
+ .and(takesArgument(3, named("java.util.Map")))
+ .and(returns(Object.class)),
+ this.getClass().getName() + "$InvokeAdvice");
+ }
+
+ @SuppressWarnings("unused")
+ public static class InvokeAdvice {
+
+ @Advice.OnMethodEnter(suppress = Throwable.class)
+ public static void onEnter(
+ @Advice.Argument(0) String methodName,
+ @Advice.Argument(1) Object argument,
+ @Advice.Argument(3) Map extraHeaders,
+ @Advice.Local("otelContext") Context context,
+ @Advice.Local("otelScope") Scope scope) {
+ Context parentContext = Context.current();
+ SimpleJsonRpcRequest request = new SimpleJsonRpcRequest(methodName, argument);
+ if (!CLIENT_INSTRUMENTER.shouldStart(parentContext, request)) {
+ return;
+ }
+
+ context = CLIENT_INSTRUMENTER.start(parentContext, request);
+ scope = context.makeCurrent();
+ }
+
+ @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
+ public static void onExit(
+ @Advice.Argument(0) String methodName,
+ @Advice.Argument(1) Object argument,
+ @Advice.Argument(3) Map extraHeaders,
+ @Advice.Return Object result,
+ @Advice.Thrown Throwable throwable,
+ @Advice.Local("otelContext") Context context,
+ @Advice.Local("otelScope") Scope scope) {
+ if (scope == null) {
+ return;
+ }
+
+ scope.close();
+ CLIENT_INSTRUMENTER.end(
+ context,
+ new SimpleJsonRpcRequest(methodName, argument),
+ new SimpleJsonRpcResponse(result),
+ throwable);
+ }
+ }
+}
diff --git a/instrumentation/jsonrpc4j-1.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_3/JsonRpcInstrumentationModule.java b/instrumentation/jsonrpc4j-1.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_3/JsonRpcInstrumentationModule.java
new file mode 100644
index 000000000000..7905681868de
--- /dev/null
+++ b/instrumentation/jsonrpc4j-1.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_3/JsonRpcInstrumentationModule.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.jsonrpc4j.v1_3;
+
+import static java.util.Arrays.asList;
+
+import com.google.auto.service.AutoService;
+import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
+import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
+import java.util.List;
+
+@AutoService(InstrumentationModule.class)
+public class JsonRpcInstrumentationModule extends InstrumentationModule {
+ public JsonRpcInstrumentationModule() {
+ super("jsonrpc4j", "jsonrpc4j-1.3");
+ }
+
+ @Override
+ public List typeInstrumentations() {
+ return asList(
+ new JsonRpcServerInstrumentation(),
+ new JsonRpcClientInstrumentation(),
+ new JsonRpcProxyInstrumentation());
+ }
+}
diff --git a/instrumentation/jsonrpc4j-1.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_3/JsonRpcProxyInstrumentation.java b/instrumentation/jsonrpc4j-1.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_3/JsonRpcProxyInstrumentation.java
new file mode 100644
index 000000000000..4c894fe89679
--- /dev/null
+++ b/instrumentation/jsonrpc4j-1.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_3/JsonRpcProxyInstrumentation.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.jsonrpc4j.v1_3;
+
+import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
+import static net.bytebuddy.matcher.ElementMatchers.isMethod;
+import static net.bytebuddy.matcher.ElementMatchers.isPrivate;
+import static net.bytebuddy.matcher.ElementMatchers.isStatic;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+
+import com.googlecode.jsonrpc4j.IJsonRpcClient;
+import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
+import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
+import java.util.Map;
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+
+public class JsonRpcProxyInstrumentation implements TypeInstrumentation {
+
+ @Override
+ public ElementMatcher classLoaderOptimization() {
+ return hasClassesNamed("com.googlecode.jsonrpc4j.ProxyUtil");
+ }
+
+ @Override
+ public ElementMatcher typeMatcher() {
+ return named("com.googlecode.jsonrpc4j.ProxyUtil");
+ }
+
+ @Override
+ public void transform(TypeTransformer transformer) {
+ transformer.applyAdviceToMethod(
+ isMethod().and(isStatic()).and(isPrivate()).and(named("createClientProxy")),
+ this.getClass().getName() + "$CreateClientProxyAdvice");
+ }
+
+ @SuppressWarnings({"unused"})
+ public static class CreateClientProxyAdvice {
+
+ @Advice.OnMethodExit(suppress = Throwable.class)
+ public static void onExit(
+ @Advice.Argument(0) ClassLoader classLoader,
+ @Advice.Argument(1) Class proxyInterface,
+ @Advice.Argument(2) IJsonRpcClient client,
+ @Advice.Argument(3) Map extraHeaders,
+ @Advice.Return(readOnly = false) Object proxy) {
+ proxy =
+ JsonRpcSingletons.instrumentCreateClientProxy(
+ classLoader, proxyInterface, client, extraHeaders);
+ }
+ }
+}
diff --git a/instrumentation/jsonrpc4j-1.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_3/JsonRpcServerInstrumentation.java b/instrumentation/jsonrpc4j-1.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_3/JsonRpcServerInstrumentation.java
new file mode 100644
index 000000000000..da97523bca72
--- /dev/null
+++ b/instrumentation/jsonrpc4j-1.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_3/JsonRpcServerInstrumentation.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.jsonrpc4j.v1_3;
+
+import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
+import static io.opentelemetry.javaagent.instrumentation.jsonrpc4j.v1_3.JsonRpcSingletons.SERVER_INVOCATION_LISTENER;
+import static net.bytebuddy.matcher.ElementMatchers.isConstructor;
+import static net.bytebuddy.matcher.ElementMatchers.isMethod;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+
+import com.googlecode.jsonrpc4j.InvocationListener;
+import com.googlecode.jsonrpc4j.JsonRpcBasicServer;
+import com.googlecode.jsonrpc4j.MultipleInvocationListener;
+import io.opentelemetry.instrumentation.api.util.VirtualField;
+import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
+import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.matcher.ElementMatcher;
+
+public class JsonRpcServerInstrumentation implements TypeInstrumentation {
+
+ @Override
+ public ElementMatcher classLoaderOptimization() {
+ return hasClassesNamed("com.googlecode.jsonrpc4j.JsonRpcBasicServer");
+ }
+
+ @Override
+ public ElementMatcher typeMatcher() {
+ return named("com.googlecode.jsonrpc4j.JsonRpcBasicServer");
+ }
+
+ @Override
+ public void transform(TypeTransformer transformer) {
+ transformer.applyAdviceToMethod(
+ isConstructor(), this.getClass().getName() + "$ConstructorAdvice");
+
+ transformer.applyAdviceToMethod(
+ isMethod().and(named("setInvocationListener")),
+ this.getClass().getName() + "$SetInvocationListenerAdvice");
+ }
+
+ @SuppressWarnings("unused")
+ public static class ConstructorAdvice {
+
+ @Advice.OnMethodExit(suppress = Throwable.class)
+ public static void setInvocationListener(
+ @Advice.This JsonRpcBasicServer jsonRpcServer,
+ @Advice.FieldValue(value = "invocationListener", readOnly = false)
+ InvocationListener invocationListener) {
+ invocationListener = SERVER_INVOCATION_LISTENER;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public static class SetInvocationListenerAdvice {
+
+ @Advice.OnMethodEnter(suppress = Throwable.class)
+ public static void setInvocationListener(
+ @Advice.This JsonRpcBasicServer jsonRpcServer,
+ @Advice.Argument(value = 0, readOnly = false, typing = Assigner.Typing.DYNAMIC)
+ InvocationListener invocationListener) {
+ VirtualField instrumented =
+ VirtualField.find(JsonRpcBasicServer.class, Boolean.class);
+ if (!Boolean.TRUE.equals(instrumented.get(jsonRpcServer))) {
+ if (invocationListener == null) {
+ invocationListener = SERVER_INVOCATION_LISTENER;
+ } else if (invocationListener instanceof MultipleInvocationListener) {
+ ((MultipleInvocationListener) invocationListener)
+ .addInvocationListener(SERVER_INVOCATION_LISTENER);
+ } else {
+ invocationListener =
+ new MultipleInvocationListener(invocationListener, SERVER_INVOCATION_LISTENER);
+ }
+
+ instrumented.set(jsonRpcServer, true);
+ }
+ }
+ }
+}
diff --git a/instrumentation/jsonrpc4j-1.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_3/JsonRpcSingletons.java b/instrumentation/jsonrpc4j-1.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_3/JsonRpcSingletons.java
new file mode 100644
index 000000000000..ed3b6115d1cc
--- /dev/null
+++ b/instrumentation/jsonrpc4j-1.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_3/JsonRpcSingletons.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.jsonrpc4j.v1_3;
+
+import com.googlecode.jsonrpc4j.IJsonRpcClient;
+import com.googlecode.jsonrpc4j.InvocationListener;
+import com.googlecode.jsonrpc4j.ReflectionUtil;
+import io.opentelemetry.api.GlobalOpenTelemetry;
+import io.opentelemetry.context.Context;
+import io.opentelemetry.context.Scope;
+import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
+import io.opentelemetry.instrumentation.jsonrpc4j.v1_3.JsonRpcTelemetry;
+import io.opentelemetry.instrumentation.jsonrpc4j.v1_3.SimpleJsonRpcRequest;
+import io.opentelemetry.instrumentation.jsonrpc4j.v1_3.SimpleJsonRpcResponse;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Map;
+
+public final class JsonRpcSingletons {
+
+ public static final InvocationListener SERVER_INVOCATION_LISTENER;
+
+ public static final Instrumenter CLIENT_INSTRUMENTER;
+
+ static {
+ JsonRpcTelemetry telemetry = JsonRpcTelemetry.builder(GlobalOpenTelemetry.get()).build();
+
+ SERVER_INVOCATION_LISTENER = telemetry.newServerInvocationListener();
+ CLIENT_INSTRUMENTER = telemetry.getClientInstrumenter();
+ }
+
+ private JsonRpcSingletons() {}
+
+ @SuppressWarnings({"unchecked"})
+ public static T instrumentCreateClientProxy(
+ ClassLoader classLoader,
+ Class proxyInterface,
+ IJsonRpcClient client,
+ Map extraHeaders) {
+ return (T)
+ Proxy.newProxyInstance(
+ classLoader,
+ new Class>[] {proxyInterface},
+ new InvocationHandler() {
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+
+ Object arguments = ReflectionUtil.parseArguments(method, args);
+ String methodName = method.getName(); // todo
+
+ // before invoke
+ Context parentContext = Context.current();
+ SimpleJsonRpcRequest request = new SimpleJsonRpcRequest(method, args);
+ if (!CLIENT_INSTRUMENTER.shouldStart(parentContext, request)) {
+ return client.invoke(
+ methodName, arguments, method.getGenericReturnType(), extraHeaders);
+ }
+
+ Context context = CLIENT_INSTRUMENTER.start(parentContext, request);
+ Scope scope = context.makeCurrent();
+ try {
+ Object result =
+ client.invoke(
+ methodName, arguments, method.getGenericReturnType(), extraHeaders);
+ scope.close();
+ CLIENT_INSTRUMENTER.end(
+ context,
+ new SimpleJsonRpcRequest(method, args),
+ new SimpleJsonRpcResponse(result),
+ null);
+ return result;
+ } catch (Throwable t) {
+ // after invoke
+ scope.close();
+ CLIENT_INSTRUMENTER.end(context, request, null, t);
+ throw t;
+ }
+ }
+ });
+ }
+}
diff --git a/instrumentation/jsonrpc4j-1.3/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_3/AgentJsonRpcTest.java b/instrumentation/jsonrpc4j-1.3/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_3/AgentJsonRpcTest.java
new file mode 100644
index 000000000000..57ba6e371908
--- /dev/null
+++ b/instrumentation/jsonrpc4j-1.3/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_3/AgentJsonRpcTest.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.jsonrpc4j.v1_3;
+
+import com.googlecode.jsonrpc4j.JsonRpcBasicServer;
+import io.opentelemetry.instrumentation.jsonrpc4j.v1_3.AbstractJsonRpcTest;
+import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
+import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+class AgentJsonRpcTest extends AbstractJsonRpcTest {
+
+ @RegisterExtension
+ static final InstrumentationExtension testing = AgentInstrumentationExtension.create();
+
+ @Override
+ protected InstrumentationExtension testing() {
+ return testing;
+ }
+
+ @Override
+ protected JsonRpcBasicServer configureServer(JsonRpcBasicServer server) {
+ return server;
+ }
+}
diff --git a/instrumentation/jsonrpc4j-1.3/library/README.md b/instrumentation/jsonrpc4j-1.3/library/README.md
new file mode 100644
index 000000000000..17174b9bf00c
--- /dev/null
+++ b/instrumentation/jsonrpc4j-1.3/library/README.md
@@ -0,0 +1,39 @@
+# Library Instrumentation for jsonrpc4j 1.3.3+
+
+Provides OpenTelemetry instrumentation for [jsonrpc4j](https://github.com/briandilley/jsonrpc4j) server.
+
+## Quickstart
+
+### Add the following dependencies to your project
+
+Replace `OPENTELEMETRY_VERSION` with the [latest release](https://search.maven.org/search?q=g:io.opentelemetry.instrumentation%20AND%20a:opentelemetry-jsonrpc4j-1.3).
+
+For Maven, add the following to your `pom.xml` dependencies:
+
+```xml
+
+
+ io.opentelemetry.instrumentation
+ opentelemetry-jsonrpc4j-1.3
+ OPENTELEMETRY_VERSION
+
+
+```
+
+For Gradle, add the following to your dependencies:
+
+```groovy
+implementation("io.opentelemetry.instrumentation:opentelemetry-jsonrpc4j-1.3:OPENTELEMETRY_VERSION")
+```
+
+### Usage
+
+The instrumentation library provides the implementation of `InvocationListener` to provide OpenTelemetry-based spans and context propagation.
+
+```java
+// For server-side, attatch the invocation listener to your service.
+JsonRpcBasicServer configureServer(OpenTelemetry openTelemetry, JsonRpcBasicServer server) {
+ JsonRpcTelemetry jsonrpcTelemetry = JsonRpcTelemetry.create(openTelemetry);
+ return server.setInvocationListener(jsonrpcTelemetry.newServerInvocationListener());
+}
+```
diff --git a/instrumentation/jsonrpc4j-1.3/library/build.gradle.kts b/instrumentation/jsonrpc4j-1.3/library/build.gradle.kts
new file mode 100644
index 000000000000..345f9d27f44f
--- /dev/null
+++ b/instrumentation/jsonrpc4j-1.3/library/build.gradle.kts
@@ -0,0 +1,13 @@
+plugins {
+ id("otel.library-instrumentation")
+}
+
+val jacksonVersion = "2.13.3"
+
+dependencies {
+ library("com.github.briandilley.jsonrpc4j:jsonrpc4j:1.3.3")
+
+ library("com.fasterxml.jackson.core:jackson-databind:$jacksonVersion")
+
+ testImplementation(project(":instrumentation:jsonrpc4j-1.3:testing"))
+}
diff --git a/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcClientAttributesExtractor.java b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcClientAttributesExtractor.java
new file mode 100644
index 000000000000..5696bf2ee898
--- /dev/null
+++ b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcClientAttributesExtractor.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.jsonrpc4j.v1_3;
+
+import io.opentelemetry.api.common.AttributesBuilder;
+import io.opentelemetry.context.Context;
+import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
+import javax.annotation.Nullable;
+
+// Check https://opentelemetry.io/docs/specs/semconv/rpc/json-rpc/
+final class JsonRpcClientAttributesExtractor
+ implements AttributesExtractor {
+
+ @Override
+ public void onStart(
+ AttributesBuilder attributes, Context parentContext, SimpleJsonRpcRequest jsonRpcRequest) {
+ attributes.put("rpc.jsonrpc.version", "2.0");
+ }
+
+ @Override
+ public void onEnd(
+ AttributesBuilder attributes,
+ Context context,
+ SimpleJsonRpcRequest jsonRpcRequest,
+ @Nullable SimpleJsonRpcResponse jsonRpcResponse,
+ @Nullable Throwable error) {}
+}
diff --git a/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcClientAttributesGetter.java b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcClientAttributesGetter.java
new file mode 100644
index 000000000000..c85de10f4d5c
--- /dev/null
+++ b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcClientAttributesGetter.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.jsonrpc4j.v1_3;
+
+import io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcAttributesGetter;
+
+// Check
+// https://github.com/open-telemetry/semantic-conventions/blob/main/docs/rpc/rpc-metrics.md#attributes
+// Check https://opentelemetry.io/docs/specs/semconv/rpc/json-rpc/
+public enum JsonRpcClientAttributesGetter implements RpcAttributesGetter {
+ INSTANCE;
+
+ @Override
+ public String getSystem(SimpleJsonRpcRequest request) {
+ return "jsonrpc";
+ }
+
+ @Override
+ public String getService(SimpleJsonRpcRequest request) {
+ if (request.getMethod() != null) {
+ return request.getMethod().getDeclaringClass().getName();
+ }
+ return "NOT_AVAILABLE";
+ }
+
+ @Override
+ public String getMethod(SimpleJsonRpcRequest request) {
+ return request.getMethodName();
+ }
+}
diff --git a/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcClientSpanNameExtractor.java b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcClientSpanNameExtractor.java
new file mode 100644
index 000000000000..a4ab989eef58
--- /dev/null
+++ b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcClientSpanNameExtractor.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.jsonrpc4j.v1_3;
+
+import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor;
+import java.lang.reflect.Method;
+
+public class JsonRpcClientSpanNameExtractor implements SpanNameExtractor {
+ @Override
+ public String extract(SimpleJsonRpcRequest request) {
+ if (request.getMethod() == null) {
+ return request.getMethodName();
+ }
+ Method method = request.getMethod();
+ return String.format("%s/%s", method.getDeclaringClass().getName(), method.getName());
+ }
+}
diff --git a/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcRequest.java b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcRequest.java
new file mode 100644
index 000000000000..bfa0f49b0168
--- /dev/null
+++ b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcRequest.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.jsonrpc4j.v1_3;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import java.lang.reflect.Method;
+import java.util.List;
+
+public final class JsonRpcRequest {
+
+ private final Method method;
+ private final List arguments;
+
+ JsonRpcRequest(Method method, List arguments) {
+ this.method = method;
+ this.arguments = arguments;
+ }
+
+ public Method getMethod() {
+ return method;
+ }
+
+ public List getArguments() {
+ return arguments;
+ }
+}
diff --git a/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcRequestGetter.java b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcRequestGetter.java
new file mode 100644
index 000000000000..8f3fd51f9b27
--- /dev/null
+++ b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcRequestGetter.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.jsonrpc4j.v1_3;
+
+import io.opentelemetry.context.propagation.TextMapGetter;
+import java.util.ArrayList;
+import javax.annotation.Nullable;
+
+enum JsonRpcRequestGetter implements TextMapGetter {
+ INSTANCE;
+
+ @Override
+ public Iterable keys(JsonRpcRequest request) {
+ return new ArrayList<>();
+ }
+
+ @Override
+ @Nullable
+ public String get(@Nullable JsonRpcRequest request, String key) {
+ return null;
+ }
+}
diff --git a/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcResponse.java b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcResponse.java
new file mode 100644
index 000000000000..9279e492d324
--- /dev/null
+++ b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcResponse.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.jsonrpc4j.v1_3;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import java.lang.reflect.Method;
+import java.util.List;
+
+public final class JsonRpcResponse {
+ private final Method method;
+ private final List params;
+ private final Object result;
+
+ JsonRpcResponse(Method method, List params, Object result) {
+ this.method = method;
+ this.params = params;
+ this.result = result;
+ }
+
+ public Method getMethod() {
+ return method;
+ }
+
+ public List getParams() {
+ return params;
+ }
+
+ public Object getResult() {
+ return result;
+ }
+}
diff --git a/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcServerAttributesExtractor.java b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcServerAttributesExtractor.java
new file mode 100644
index 000000000000..36c46eaa2e1c
--- /dev/null
+++ b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcServerAttributesExtractor.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.jsonrpc4j.v1_3;
+
+import com.googlecode.jsonrpc4j.AnnotationsErrorResolver;
+import com.googlecode.jsonrpc4j.DefaultErrorResolver;
+import com.googlecode.jsonrpc4j.ErrorResolver;
+import com.googlecode.jsonrpc4j.MultipleErrorResolver;
+import io.opentelemetry.api.common.AttributeKey;
+import io.opentelemetry.api.common.AttributesBuilder;
+import io.opentelemetry.context.Context;
+import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
+import javax.annotation.Nullable;
+
+// Check https://opentelemetry.io/docs/specs/semconv/rpc/json-rpc/
+final class JsonRpcServerAttributesExtractor
+ implements AttributesExtractor {
+
+ private static final AttributeKey RPC_JSONRPC_ERROR_CODE =
+ AttributeKey.longKey("rpc.jsonrpc.error_code");
+
+ private static final AttributeKey RPC_JSONRPC_ERROR_MESSAGE =
+ AttributeKey.stringKey("rpc.jsonrpc.error_message");
+
+ @Override
+ public void onStart(
+ AttributesBuilder attributes, Context parentContext, JsonRpcRequest jsonRpcRequest) {
+ attributes.put("rpc.jsonrpc.version", "2.0");
+ }
+
+ @Override
+ public void onEnd(
+ AttributesBuilder attributes,
+ Context context,
+ JsonRpcRequest jsonRpcRequest,
+ @Nullable JsonRpcResponse jsonRpcResponse,
+ @Nullable Throwable error) {
+ // use the DEFAULT_ERROR_RESOLVER to extract error code and message
+ if (error != null) {
+ ErrorResolver errorResolver =
+ new MultipleErrorResolver(
+ AnnotationsErrorResolver.INSTANCE, DefaultErrorResolver.INSTANCE);
+ ErrorResolver.JsonError jsonError =
+ errorResolver.resolveError(
+ error, jsonRpcRequest.getMethod(), jsonRpcRequest.getArguments());
+ attributes.put(RPC_JSONRPC_ERROR_CODE, jsonError.code);
+ attributes.put(RPC_JSONRPC_ERROR_MESSAGE, jsonError.message);
+ } else {
+ attributes.put(RPC_JSONRPC_ERROR_CODE, ErrorResolver.JsonError.OK.code);
+ }
+ }
+}
diff --git a/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcServerAttributesGetter.java b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcServerAttributesGetter.java
new file mode 100644
index 000000000000..bd69490a944a
--- /dev/null
+++ b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcServerAttributesGetter.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.jsonrpc4j.v1_3;
+
+import io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcAttributesGetter;
+
+// Check
+// https://github.com/open-telemetry/semantic-conventions/blob/main/docs/rpc/rpc-metrics.md#attributes
+// Check https://opentelemetry.io/docs/specs/semconv/rpc/json-rpc/
+public enum JsonRpcServerAttributesGetter implements RpcAttributesGetter {
+ INSTANCE;
+
+ @Override
+ public String getSystem(JsonRpcRequest request) {
+ return "jsonrpc";
+ }
+
+ @Override
+ public String getService(JsonRpcRequest request) {
+ return request.getMethod().getDeclaringClass().getName();
+ }
+
+ @Override
+ public String getMethod(JsonRpcRequest request) {
+ return request.getMethod().getName();
+ }
+}
diff --git a/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcServerSpanNameExtractor.java b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcServerSpanNameExtractor.java
new file mode 100644
index 000000000000..0639a40cc891
--- /dev/null
+++ b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcServerSpanNameExtractor.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.jsonrpc4j.v1_3;
+
+import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor;
+import java.lang.reflect.Method;
+
+public class JsonRpcServerSpanNameExtractor implements SpanNameExtractor {
+ // Follow https://opentelemetry.io/docs/specs/semconv/rpc/rpc-spans/#span-name
+ @Override
+ public String extract(JsonRpcRequest request) {
+ Method method = request.getMethod();
+ return String.format("%s/%s", method.getDeclaringClass().getName(), method.getName());
+ }
+}
diff --git a/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcServerSpanStatusExtractor.java b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcServerSpanStatusExtractor.java
new file mode 100644
index 000000000000..27509950f1e7
--- /dev/null
+++ b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcServerSpanStatusExtractor.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.jsonrpc4j.v1_3;
+
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import io.opentelemetry.api.trace.StatusCode;
+import io.opentelemetry.instrumentation.api.instrumenter.SpanStatusBuilder;
+import io.opentelemetry.instrumentation.api.instrumenter.SpanStatusExtractor;
+import javax.annotation.Nullable;
+
+public enum JsonRpcServerSpanStatusExtractor
+ implements SpanStatusExtractor {
+ INSTANCE;
+
+ /** Extracts the status from the response and sets it to the {@code spanStatusBuilder}. */
+ @Override
+ public void extract(
+ SpanStatusBuilder spanStatusBuilder,
+ JsonRpcRequest jsonRpcRequest,
+ @Nullable JsonRpcResponse jsonRpcResponse,
+ @Nullable Throwable error) {
+ if (error == null) {
+ spanStatusBuilder.setStatus(StatusCode.OK);
+ }
+
+ // treat client invalid input as OK
+ if (error instanceof JsonParseException || error instanceof JsonMappingException) {
+ spanStatusBuilder.setStatus(StatusCode.OK);
+ }
+
+ spanStatusBuilder.setStatus(StatusCode.ERROR);
+ }
+}
diff --git a/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcTelemetry.java b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcTelemetry.java
new file mode 100644
index 000000000000..0c54be195e7d
--- /dev/null
+++ b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcTelemetry.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.jsonrpc4j.v1_3;
+
+import com.googlecode.jsonrpc4j.InvocationListener;
+import io.opentelemetry.api.OpenTelemetry;
+import io.opentelemetry.context.propagation.ContextPropagators;
+import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
+
+public final class JsonRpcTelemetry {
+ public static JsonRpcTelemetry create(OpenTelemetry openTelemetry) {
+ return builder(openTelemetry).build();
+ }
+
+ public static JsonRpcTelemetryBuilder builder(OpenTelemetry openTelemetry) {
+ return new JsonRpcTelemetryBuilder(openTelemetry);
+ }
+
+ private final Instrumenter serverInstrumenter;
+ private final Instrumenter clientInstrumenter;
+ private final ContextPropagators propagators;
+
+ JsonRpcTelemetry(
+ Instrumenter serverInstrumenter,
+ Instrumenter clientInstrumenter,
+ ContextPropagators propagators) {
+ this.serverInstrumenter = serverInstrumenter;
+ this.clientInstrumenter = clientInstrumenter;
+ this.propagators = propagators;
+ }
+
+ public InvocationListener newServerInvocationListener() {
+ return new OpenTelemetryJsonRpcInvocationListener(serverInstrumenter);
+ }
+
+ public Instrumenter getClientInstrumenter() {
+ return clientInstrumenter;
+ }
+
+ public ContextPropagators getPropagators() {
+ return propagators;
+ }
+}
diff --git a/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcTelemetryBuilder.java b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcTelemetryBuilder.java
new file mode 100644
index 000000000000..74fdaf810988
--- /dev/null
+++ b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcTelemetryBuilder.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.jsonrpc4j.v1_3;
+
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
+import io.opentelemetry.api.OpenTelemetry;
+import io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcClientAttributesExtractor;
+import io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcClientMetrics;
+import io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcServerAttributesExtractor;
+import io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcServerMetrics;
+import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
+import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
+import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder;
+import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor;
+import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor;
+import java.util.ArrayList;
+import java.util.List;
+
+public class JsonRpcTelemetryBuilder {
+
+ private static final String INSTRUMENTATION_NAME = "io.opentelemetry.jsonrpc4j-1.3";
+
+ private final OpenTelemetry openTelemetry;
+
+ private final List<
+ AttributesExtractor super SimpleJsonRpcRequest, ? super SimpleJsonRpcResponse>>
+ additionalClientExtractors = new ArrayList<>();
+ private final List>
+ additionalServerExtractors = new ArrayList<>();
+
+ JsonRpcTelemetryBuilder(OpenTelemetry openTelemetry) {
+ this.openTelemetry = openTelemetry;
+ }
+
+ /**
+ * Adds an extra client-only {@link AttributesExtractor} to invoke to set attributes to
+ * instrumented items. The {@link AttributesExtractor} will be executed after all default
+ * extractors.
+ */
+ @CanIgnoreReturnValue
+ public JsonRpcTelemetryBuilder addClientAttributeExtractor(
+ AttributesExtractor super SimpleJsonRpcRequest, ? super SimpleJsonRpcResponse>
+ attributesExtractor) {
+ additionalClientExtractors.add(attributesExtractor);
+ return this;
+ }
+
+ /**
+ * Adds an extra server-only {@link AttributesExtractor} to invoke to set attributes to
+ * instrumented items. The {@link AttributesExtractor} will be executed after all default
+ * extractors.
+ */
+ @CanIgnoreReturnValue
+ public JsonRpcTelemetryBuilder addServerAttributeExtractor(
+ AttributesExtractor super JsonRpcRequest, ? super JsonRpcResponse> attributesExtractor) {
+ additionalServerExtractors.add(attributesExtractor);
+ return this;
+ }
+
+ public JsonRpcTelemetry build() {
+ SpanNameExtractor clientSpanNameExtractor =
+ new JsonRpcClientSpanNameExtractor();
+ SpanNameExtractor serverSpanNameExtractor =
+ new JsonRpcServerSpanNameExtractor();
+
+ InstrumenterBuilder clientInstrumenterBuilder =
+ Instrumenter.builder(openTelemetry, INSTRUMENTATION_NAME, clientSpanNameExtractor);
+
+ InstrumenterBuilder serverInstrumenterBuilder =
+ Instrumenter.builder(openTelemetry, INSTRUMENTATION_NAME, serverSpanNameExtractor);
+
+ JsonRpcServerAttributesGetter serverRpcAttributesGetter =
+ JsonRpcServerAttributesGetter.INSTANCE;
+ JsonRpcClientAttributesGetter clientRpcAttributesGetter =
+ JsonRpcClientAttributesGetter.INSTANCE;
+
+ clientInstrumenterBuilder
+ .addAttributesExtractor(RpcClientAttributesExtractor.create(clientRpcAttributesGetter))
+ .addAttributesExtractors(additionalClientExtractors)
+ .addAttributesExtractor(new JsonRpcClientAttributesExtractor())
+ .addOperationMetrics(RpcClientMetrics.get());
+
+ serverInstrumenterBuilder
+ .setSpanStatusExtractor(JsonRpcServerSpanStatusExtractor.INSTANCE)
+ .addAttributesExtractor(RpcServerAttributesExtractor.create(serverRpcAttributesGetter))
+ .addAttributesExtractor(new JsonRpcServerAttributesExtractor())
+ .addAttributesExtractors(additionalServerExtractors)
+ .addOperationMetrics(RpcServerMetrics.get());
+
+ return new JsonRpcTelemetry(
+ serverInstrumenterBuilder.buildServerInstrumenter(JsonRpcRequestGetter.INSTANCE),
+ clientInstrumenterBuilder.buildInstrumenter(SpanKindExtractor.alwaysClient()),
+ openTelemetry.getPropagators());
+ }
+}
diff --git a/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/OpenTelemetryJsonRpcInvocationListener.java b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/OpenTelemetryJsonRpcInvocationListener.java
new file mode 100644
index 000000000000..a7337f224140
--- /dev/null
+++ b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/OpenTelemetryJsonRpcInvocationListener.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.jsonrpc4j.v1_3;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.googlecode.jsonrpc4j.InvocationListener;
+import io.opentelemetry.context.Context;
+import io.opentelemetry.context.Scope;
+import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
+import java.lang.reflect.Method;
+import java.util.List;
+
+public final class OpenTelemetryJsonRpcInvocationListener implements InvocationListener {
+
+ private final Instrumenter serverInstrumenter;
+
+ private static final ThreadLocal threadLocalContext = new ThreadLocal<>();
+ private static final ThreadLocal threadLocalScope = new ThreadLocal<>();
+
+ public OpenTelemetryJsonRpcInvocationListener(
+ Instrumenter serverInstrumenter) {
+ this.serverInstrumenter = serverInstrumenter;
+ }
+
+ /**
+ * This method will be invoked prior to a JSON-RPC service being invoked.
+ *
+ * @param method is the method that will be invoked.
+ * @param arguments are the arguments that will be passed to the method when it is invoked.
+ */
+ @Override
+ public void willInvoke(Method method, List arguments) {
+ Context parentContext = Context.current();
+ JsonRpcRequest request = new JsonRpcRequest(method, arguments);
+ if (!serverInstrumenter.shouldStart(parentContext, request)) {
+ return;
+ }
+
+ Context context = serverInstrumenter.start(parentContext, request);
+ threadLocalContext.set(context);
+ threadLocalScope.set(context.makeCurrent());
+ }
+
+ /**
+ * This method will be invoked after a JSON-RPC service has been invoked.
+ *
+ * @param method is the method that will was invoked.
+ * @param arguments are the arguments that were be passed to the method when it is invoked.
+ * @param result is the result of the method invocation. If an error arose, this value will be
+ * null.
+ * @param t is the throwable that was thrown from the invocation, if no error arose, this value
+ * will be null.
+ * @param duration is approximately the number of milliseconds that elapsed during which the
+ * method was invoked.
+ */
+ @Override
+ public void didInvoke(
+ Method method, List arguments, Object result, Throwable t, long duration) {
+ JsonRpcRequest request = new JsonRpcRequest(method, arguments);
+ JsonRpcResponse response = new JsonRpcResponse(method, arguments, result);
+ threadLocalScope.get().close();
+ serverInstrumenter.end(threadLocalContext.get(), request, response, t);
+ threadLocalContext.remove();
+ threadLocalScope.remove();
+ }
+}
diff --git a/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/SimpleJsonRpcRequest.java b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/SimpleJsonRpcRequest.java
new file mode 100644
index 000000000000..4d1a09d678a6
--- /dev/null
+++ b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/SimpleJsonRpcRequest.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.jsonrpc4j.v1_3;
+
+import java.lang.reflect.Method;
+
+public final class SimpleJsonRpcRequest {
+
+ private final String methodName;
+ private final Object argument;
+ private Method method;
+
+ public SimpleJsonRpcRequest(String methodName, Object argument) {
+ this.methodName = methodName;
+ this.argument = argument;
+ }
+
+ public SimpleJsonRpcRequest(Method method, Object argument) {
+ this.method = method;
+ this.methodName = method.getName();
+ this.argument = argument;
+ }
+
+ public String getMethodName() {
+ return methodName;
+ }
+
+ public Object getArgument() {
+ return argument;
+ }
+
+ public Method getMethod() {
+ return method;
+ }
+}
diff --git a/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/SimpleJsonRpcResponse.java b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/SimpleJsonRpcResponse.java
new file mode 100644
index 000000000000..e1ea2ac8666e
--- /dev/null
+++ b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/SimpleJsonRpcResponse.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.jsonrpc4j.v1_3;
+
+public final class SimpleJsonRpcResponse {
+
+ private final Object result;
+
+ public SimpleJsonRpcResponse(Object result) {
+ this.result = result;
+ }
+
+ public Object getResult() {
+ return result;
+ }
+}
diff --git a/instrumentation/jsonrpc4j-1.3/library/src/test/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/LibraryJsonRpcTest.java b/instrumentation/jsonrpc4j-1.3/library/src/test/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/LibraryJsonRpcTest.java
new file mode 100644
index 000000000000..63e017601813
--- /dev/null
+++ b/instrumentation/jsonrpc4j-1.3/library/src/test/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/LibraryJsonRpcTest.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.jsonrpc4j.v1_3;
+
+import com.googlecode.jsonrpc4j.JsonRpcBasicServer;
+import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
+import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+public class LibraryJsonRpcTest extends AbstractJsonRpcTest {
+
+ @RegisterExtension
+ static InstrumentationExtension testing = LibraryInstrumentationExtension.create();
+
+ @Override
+ protected InstrumentationExtension testing() {
+ return testing;
+ }
+
+ @Override
+ protected JsonRpcBasicServer configureServer(JsonRpcBasicServer server) {
+ server.setInvocationListener(
+ JsonRpcTelemetry.builder(testing.getOpenTelemetry()).build().newServerInvocationListener());
+ return server;
+ }
+}
diff --git a/instrumentation/jsonrpc4j-1.3/testing/build.gradle.kts b/instrumentation/jsonrpc4j-1.3/testing/build.gradle.kts
new file mode 100644
index 000000000000..35775d794a20
--- /dev/null
+++ b/instrumentation/jsonrpc4j-1.3/testing/build.gradle.kts
@@ -0,0 +1,13 @@
+plugins {
+ id("otel.java-conventions")
+}
+
+val jsonrpcVersion = "1.3.3"
+
+dependencies {
+ api(project(":testing-common"))
+
+ implementation("com.github.briandilley.jsonrpc4j:jsonrpc4j:$jsonrpcVersion")
+
+ implementation("com.fasterxml.jackson.core:jackson-databind:2.13.3")
+}
diff --git a/instrumentation/jsonrpc4j-1.3/testing/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/AbstractJsonRpcTest.java b/instrumentation/jsonrpc4j-1.3/testing/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/AbstractJsonRpcTest.java
new file mode 100644
index 000000000000..51c79dbd9071
--- /dev/null
+++ b/instrumentation/jsonrpc4j-1.3/testing/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/AbstractJsonRpcTest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.jsonrpc4j.v1_3;
+
+import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
+import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
+import static io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_JSONRPC_ERROR_CODE;
+import static io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_JSONRPC_VERSION;
+import static io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_METHOD;
+import static io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_SERVICE;
+import static io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_SYSTEM;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.googlecode.jsonrpc4j.JsonRpcBasicServer;
+import io.opentelemetry.api.trace.SpanKind;
+import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
+import io.opentelemetry.testing.internal.jackson.databind.JsonNode;
+import io.opentelemetry.testing.internal.jackson.databind.ObjectMapper;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+
+@SuppressWarnings("deprecation") // using deprecated semconv
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+public abstract class AbstractJsonRpcTest {
+
+ protected abstract InstrumentationExtension testing();
+
+ protected abstract JsonRpcBasicServer configureServer(JsonRpcBasicServer server);
+
+ @Test
+ void testServer() throws IOException {
+ CalculatorService calculator = new CalculatorServiceImpl();
+ JsonRpcBasicServer server =
+ configureServer(new JsonRpcBasicServer(calculator, CalculatorService.class));
+
+ JsonNode response =
+ testing()
+ .runWithSpan(
+ "parent",
+ () -> {
+ InputStream inputStream =
+ new ByteArrayInputStream(
+ "{\"jsonrpc\":\"2.0\",\"method\":\"add\",\"params\":[1,2],\"id\":1}"
+ .getBytes(UTF_8));
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ server.handleRequest(inputStream, outputStream);
+
+ // Read the JsonNode from the InputStream
+ ObjectMapper objectMapper = new ObjectMapper();
+ return objectMapper.readTree(
+ new ByteArrayInputStream(outputStream.toByteArray()));
+ });
+
+ assertThat(response.get("result").asInt()).isEqualTo(3);
+
+ testing()
+ .waitAndAssertTraces(
+ trace ->
+ trace.hasSpansSatisfyingExactly(
+ span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(),
+ span ->
+ span.hasName(
+ "io.opentelemetry.instrumentation.jsonrpc4j.v1_3.CalculatorService/add")
+ .hasKind(SpanKind.SERVER)
+ .hasParent(trace.getSpan(0))
+ .hasAttributesSatisfyingExactly(
+ equalTo(RPC_SYSTEM, "jsonrpc"),
+ equalTo(RPC_JSONRPC_VERSION, "2.0"),
+ equalTo(
+ RPC_SERVICE,
+ "io.opentelemetry.instrumentation.jsonrpc4j.v1_3.CalculatorService"),
+ equalTo(RPC_METHOD, "add"),
+ equalTo(RPC_JSONRPC_ERROR_CODE, 0L))));
+ testing()
+ .waitAndAssertMetrics(
+ "io.opentelemetry.jsonrpc4j-1.3",
+ "rpc.server.duration",
+ metrics ->
+ metrics.anySatisfy(
+ metric ->
+ assertThat(metric)
+ .hasUnit("ms")
+ .hasHistogramSatisfying(
+ histogram ->
+ histogram.hasPointsSatisfying(
+ point ->
+ point.hasAttributesSatisfying(
+ equalTo(RPC_METHOD, "add"),
+ equalTo(
+ RPC_SERVICE,
+ "io.opentelemetry.instrumentation.jsonrpc4j.v1_3.CalculatorService"),
+ equalTo(RPC_SYSTEM, "jsonrpc"))))));
+ }
+}
diff --git a/instrumentation/jsonrpc4j-1.3/testing/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/CalculatorService.java b/instrumentation/jsonrpc4j-1.3/testing/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/CalculatorService.java
new file mode 100644
index 000000000000..8821b7d2b858
--- /dev/null
+++ b/instrumentation/jsonrpc4j-1.3/testing/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/CalculatorService.java
@@ -0,0 +1,15 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.jsonrpc4j.v1_3;
+
+import com.googlecode.jsonrpc4j.JsonRpcService;
+
+@JsonRpcService("/calculator")
+public interface CalculatorService {
+ int add(int a, int b) throws Throwable;
+
+ int subtract(int a, int b) throws Throwable;
+}
diff --git a/instrumentation/jsonrpc4j-1.3/testing/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/CalculatorServiceImpl.java b/instrumentation/jsonrpc4j-1.3/testing/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/CalculatorServiceImpl.java
new file mode 100644
index 000000000000..c01b9c99996c
--- /dev/null
+++ b/instrumentation/jsonrpc4j-1.3/testing/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/CalculatorServiceImpl.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.jsonrpc4j.v1_3;
+
+public class CalculatorServiceImpl implements CalculatorService {
+ @Override
+ public int add(int a, int b) {
+ return a + b;
+ }
+
+ @Override
+ public int subtract(int a, int b) {
+ return a - b;
+ }
+}
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 0e4cda313a8d..5a7294e49e90 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -345,6 +345,9 @@ include(":instrumentation:jsf:jsf-mojarra-3.0:javaagent")
include(":instrumentation:jsf:jsf-myfaces-1.2:javaagent")
include(":instrumentation:jsf:jsf-myfaces-3.0:javaagent")
include(":instrumentation:jsp-2.3:javaagent")
+include(":instrumentation:jsonrpc4j-1.3:javaagent")
+include(":instrumentation:jsonrpc4j-1.3:library")
+include(":instrumentation:jsonrpc4j-1.3:testing")
include(":instrumentation:kafka:kafka-clients:kafka-clients-0.11:bootstrap")
include(":instrumentation:kafka:kafka-clients:kafka-clients-0.11:javaagent")
include(":instrumentation:kafka:kafka-clients:kafka-clients-0.11:testing")