diff --git a/docs/supported-libraries.md b/docs/supported-libraries.md
index c7881b6a3a1f..fbc8dea595d2 100644
--- a/docs/supported-libraries.md
+++ b/docs/supported-libraries.md
@@ -174,17 +174,26 @@ These are the supported libraries and frameworks:
These are the application servers that the smoke tests are run against:
-| Application server | Version | JVM | OS |
-| ------------------------------------------------------------------------------------- | --------------------------- | ----------------- | ------------------------------------- |
-| [Jetty](https://www.eclipse.org/jetty/) | 9.4.x, 10.0.x, 11.0.x | OpenJDK 8, 11, 17 | [`ubuntu-latest`], [`windows-latest`] |
-| [Payara](https://www.payara.fish/) | 5.0.x, 5.1.x | OpenJDK 8, 11 | [`ubuntu-latest`], [`windows-latest`] |
-| [Tomcat](http://tomcat.apache.org/) | 7.0.x | OpenJDK 8 | [`ubuntu-latest`], [`windows-latest`] |
-| [Tomcat](http://tomcat.apache.org/) | 7.0.x, 8.5.x, 9.0.x, 10.0.x | OpenJDK 8, 11, 17 | [`ubuntu-latest`], [`windows-latest`] |
-| [TomEE](https://tomee.apache.org/) | 7.x, 8.x | OpenJDK 8, 11, 17 | [`ubuntu-latest`], [`windows-latest`] |
-| [Open Liberty](https://openliberty.io/) | 21.x, 22.x, 23.x | OpenJDK 8, 11, 17 | [`ubuntu-latest`], [`windows-latest`] |
-| [Websphere Traditional](https://www.ibm.com/uk-en/cloud/websphere-application-server) | 8.5.5.x, 9.0.x | IBM JDK 8 | Red Hat Enterprise Linux 8.4 |
-| [WildFly](https://www.wildfly.org/) | 13.x | OpenJDK 8 | [`ubuntu-latest`], [`windows-latest`] |
-| [WildFly](https://www.wildfly.org/) | 17.x, 21.x, 25.x | OpenJDK 8, 11, 17 | [`ubuntu-latest`], [`windows-latest`] |
+| Application server | Version | JVM | OS |
+|---------------------------------------------------------------------------------------|------------------------------------------|------------------------------------------------|---------------------------------------|
+| [Jetty](https://www.eclipse.org/jetty/) | 9.4.53 | OpenJDK 8, 11, 17, 21
OpenJ9 8, 11, 17, 21 | [`ubuntu-latest`], [`windows-latest`] |
+| [Jetty](https://www.eclipse.org/jetty/) | 10.0.19, 11.0.19 | OpenJDK 11, 17, 21
OpenJ9 11, 17, 21 | [`ubuntu-latest`], [`windows-latest`] |
+| [Jetty](https://www.eclipse.org/jetty/) | 12.0.6 | OpenJDK 17, 21
OpenJ9 17, 21 | [`ubuntu-latest`], [`windows-latest`] |
+| [Open Liberty](https://openliberty.io/) | 20.0.0.12 | OpenJDK 8, 11
OpenJ9 8, 11 | [`ubuntu-latest`], [`windows-latest`] |
+| [Open Liberty](https://openliberty.io/) | 21.0.0.12, 22.0.0.12 | OpenJDK 8, 11, 17
OpenJ9 8, 11, 17 | [`ubuntu-latest`], [`windows-latest`] |
+| [Open Liberty](https://openliberty.io/) | 23.0.0.12 | OpenJDK 8, 11, 17, 20
OpenJ9 8, 11, 17, 20 | [`ubuntu-latest`], [`windows-latest`] |
+| [Payara](https://www.payara.fish/) | 5.2020.6, 5.2021.8 | OpenJDK 8, 11
OpenJ9 8, 11 | [`ubuntu-latest`], [`windows-latest`] |
+| [Payara](https://www.payara.fish/) | 6.2023.12 | OpenJDK 11, 17
OpenJ9 11, 17, 21 | [`ubuntu-latest`], [`windows-latest`] |
+| [Tomcat](http://tomcat.apache.org/) | 7.0.109 | OpenJDK 8
OpenJ9 8 | [`ubuntu-latest`], [`windows-latest`] |
+| [Tomcat](http://tomcat.apache.org/) | 8.5.98, 9.0.85 | OpenJDK 8, 11, 17, 21
OpenJ9 8, 11, 17, 21 | [`ubuntu-latest`], [`windows-latest`] |
+| [Tomcat](http://tomcat.apache.org/) | 10.1.18 | OpenJDK 11, 17, 21
OpenJ9 11, 17, 21 | [`ubuntu-latest`], [`windows-latest`] |
+| [TomEE](https://tomee.apache.org/) | 7.0.9, 7.1.4 | OpenJDK 8
OpenJ9 8 | [`ubuntu-latest`], [`windows-latest`] |
+| [TomEE](https://tomee.apache.org/) | 8.0.16 | OpenJDK 8, 11, 17, 21
OpenJ9 8, 11, 17, 21 | [`ubuntu-latest`], [`windows-latest`] |
+| [TomEE](https://tomee.apache.org/) | 9.1.2 | OpenJDK 11, 17, 21
OpenJ9 11, 17, 21 | [`ubuntu-latest`], [`windows-latest`] |
+| [Websphere Traditional](https://www.ibm.com/uk-en/cloud/websphere-application-server) | 8.5.5.22, 9.0.5.14 | IBM JDK 8 | Red Hat Enterprise Linux 8.4 |
+| [WildFly](https://www.wildfly.org/) | 13.0.0.Final | OpenJDK 8
OpenJ9 8 | [`ubuntu-latest`], [`windows-latest`] |
+| [WildFly](https://www.wildfly.org/) | 17.0.1.Final, 21.0.0.Final | OpenJDK 8, 11, 17, 21
OpenJ9 8, 11, 17, 21 | [`ubuntu-latest`], [`windows-latest`] |
+| [WildFly](https://www.wildfly.org/) | 28.0.1.Final, 29.0.1.Final, 30.0.1.Final | OpenJDK 11, 17, 21
OpenJ9 11, 17, 21 | [`ubuntu-latest`], [`windows-latest`] |
[`ubuntu-latest`]: https://github.com/actions/runner-images#available-images
[`windows-latest`]: https://github.com/actions/runner-images#available-images
@@ -193,10 +202,10 @@ These are the application servers that the smoke tests are run against:
These are the JVMs and operating systems that the integration tests are run against:
-| JVM | Versions | OS |
-| ----------------------------------------------------------------------------------------- | --------- | ------------------------------------- |
-| [OpenJDK (Eclipse Temurin)](https://adoptium.net/) | 8, 11, 17 | [`ubuntu-latest`], [`windows-latest`] |
-| [OpenJ9 (IBM Semeru Runtimes)](https://developer.ibm.com/languages/java/semeru-runtimes/) | 8, 11, 17 | [`ubuntu-latest`] |
+| JVM | Versions | OS |
+| ----------------------------------------------------------------------------------------- |---------------| ------------------------------------- |
+| [OpenJDK (Eclipse Temurin)](https://adoptium.net/) | 8, 11, 17, 21 | [`ubuntu-latest`], [`windows-latest`] |
+| [OpenJ9 (IBM Semeru Runtimes)](https://developer.ibm.com/languages/java/semeru-runtimes/) | 8, 11, 17 | [`ubuntu-latest`] |
## Disabled instrumentations
diff --git a/instrumentation/jetty/jetty-11.0/javaagent/build.gradle.kts b/instrumentation/jetty/jetty-11.0/javaagent/build.gradle.kts
index 3c172eb92ee8..6e133bb309de 100644
--- a/instrumentation/jetty/jetty-11.0/javaagent/build.gradle.kts
+++ b/instrumentation/jetty/jetty-11.0/javaagent/build.gradle.kts
@@ -18,6 +18,7 @@ dependencies {
bootstrap(project(":instrumentation:servlet:servlet-common:bootstrap"))
testInstrumentation(project(":instrumentation:jetty:jetty-8.0:javaagent"))
+ testInstrumentation(project(":instrumentation:jetty:jetty-12.0:javaagent"))
// jetty-servlet does not exist in jetty 12, so we don't need to explicitly pin it to 11.+
testLibrary("org.eclipse.jetty:jetty-servlet:11.0.0")
diff --git a/instrumentation/jetty/jetty-11.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jetty/v11_0/JettyHandlerTest.java b/instrumentation/jetty/jetty-11.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jetty/v11_0/JettyHandlerTest.java
index eb6358daa522..f8face51649a 100644
--- a/instrumentation/jetty/jetty-11.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jetty/v11_0/JettyHandlerTest.java
+++ b/instrumentation/jetty/jetty-11.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jetty/v11_0/JettyHandlerTest.java
@@ -40,7 +40,7 @@
import org.eclipse.jetty.server.handler.ErrorHandler;
import org.junit.jupiter.api.extension.RegisterExtension;
-public class JettyHandlerTest extends AbstractHttpServerTest {
+class JettyHandlerTest extends AbstractHttpServerTest {
@RegisterExtension
static final InstrumentationExtension testing = HttpServerInstrumentationExtension.forAgent();
@@ -62,25 +62,17 @@ protected void handleErrorPage(
private static final TestHandler testHandler = new TestHandler();
@Override
- protected Server setupServer() {
+ protected Server setupServer() throws Exception {
Server server = new Server(port);
server.setHandler(testHandler);
server.addBean(errorHandler);
- try {
- server.start();
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
+ server.start();
return server;
}
@Override
- protected void stopServer(Server server) {
- try {
- server.stop();
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
+ protected void stopServer(Server server) throws Exception {
+ server.stop();
}
@Override
diff --git a/instrumentation/jetty/jetty-12.0/javaagent/build.gradle.kts b/instrumentation/jetty/jetty-12.0/javaagent/build.gradle.kts
new file mode 100644
index 000000000000..4c97e1e4d052
--- /dev/null
+++ b/instrumentation/jetty/jetty-12.0/javaagent/build.gradle.kts
@@ -0,0 +1,27 @@
+plugins {
+ id("otel.javaagent-instrumentation")
+}
+
+muzzle {
+ pass {
+ group.set("org.eclipse.jetty")
+ module.set("jetty-server")
+ versions.set("[12,)")
+ }
+}
+
+dependencies {
+ library("org.eclipse.jetty:jetty-server:12.0.0")
+
+ bootstrap(project(":instrumentation:servlet:servlet-common:bootstrap"))
+ implementation(project(":instrumentation:servlet:servlet-common:javaagent"))
+
+ testInstrumentation(project(":instrumentation:jetty:jetty-8.0:javaagent"))
+ testInstrumentation(project(":instrumentation:jetty:jetty-11.0:javaagent"))
+
+ testLibrary("org.eclipse.jetty.ee10:jetty-ee10-servlet:12.0.0")
+}
+
+otelJava {
+ minJavaVersionSupported.set(JavaVersion.VERSION_17)
+}
diff --git a/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12Helper.java b/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12Helper.java
new file mode 100644
index 000000000000..d17aba9c6d00
--- /dev/null
+++ b/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12Helper.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.jetty.v12_0;
+
+import io.opentelemetry.context.Context;
+import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
+import io.opentelemetry.javaagent.bootstrap.servlet.AppServerBridge;
+import io.opentelemetry.javaagent.instrumentation.servlet.ServletHelper;
+import javax.annotation.Nullable;
+import org.eclipse.jetty.server.HttpStream;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+
+public class Jetty12Helper {
+ private final Instrumenter instrumenter;
+
+ Jetty12Helper(Instrumenter instrumenter) {
+ this.instrumenter = instrumenter;
+ }
+
+ public boolean shouldStart(Context parentContext, Request request) {
+ return instrumenter.shouldStart(parentContext, request);
+ }
+
+ public Context start(Context parentContext, Request request, Response response) {
+ Context context = instrumenter.start(parentContext, request);
+ request.addFailureListener(throwable -> end(context, request, response, throwable));
+ // detect request completion
+ // https://github.com/jetty/jetty.project/blob/52d94174e2c7a6e794c6377dcf9cd3ed0b9e1806/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/EventsHandler.java#L75
+ request.addHttpStreamWrapper(
+ stream ->
+ new HttpStream.Wrapper(stream) {
+ @Override
+ public void succeeded() {
+ end(context, request, response, null);
+ super.succeeded();
+ }
+
+ @Override
+ public void failed(Throwable throwable) {
+ end(context, request, response, throwable);
+ super.failed(throwable);
+ }
+ });
+
+ return context;
+ }
+
+ public void end(Context context, Request request, Response response, @Nullable Throwable error) {
+ if (error == null) {
+ error = AppServerBridge.getException(context);
+ }
+ if (error == null) {
+ error = (Throwable) request.getAttribute(ServletHelper.ASYNC_EXCEPTION_ATTRIBUTE);
+ }
+
+ instrumenter.end(context, request, response, error);
+ }
+}
diff --git a/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12HttpAttributesGetter.java b/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12HttpAttributesGetter.java
new file mode 100644
index 000000000000..456fae9e75b3
--- /dev/null
+++ b/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12HttpAttributesGetter.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.jetty.v12_0;
+
+import io.opentelemetry.instrumentation.api.semconv.http.HttpServerAttributesGetter;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.List;
+import javax.annotation.Nullable;
+import org.eclipse.jetty.http.HttpURI;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+
+class Jetty12HttpAttributesGetter implements HttpServerAttributesGetter {
+
+ @Override
+ public String getHttpRequestMethod(Request request) {
+ return request.getMethod();
+ }
+
+ @Override
+ public List getHttpRequestHeader(Request request, String name) {
+ return request.getHeaders().getValuesList(name);
+ }
+
+ @Override
+ public Integer getHttpResponseStatusCode(
+ Request request, Response response, @Nullable Throwable error) {
+ if (!response.isCommitted() && error != null) {
+ return 500;
+ }
+ return response.getStatus();
+ }
+
+ @Override
+ public List getHttpResponseHeader(Request request, Response response, String name) {
+ return response.getHeaders().getValuesList(name);
+ }
+
+ @Override
+ @Nullable
+ public String getUrlScheme(Request request) {
+ HttpURI uri = request.getHttpURI();
+ return uri == null ? null : uri.getScheme();
+ }
+
+ @Nullable
+ @Override
+ public String getUrlPath(Request request) {
+ HttpURI uri = request.getHttpURI();
+ return uri == null ? null : uri.getPath();
+ }
+
+ @Nullable
+ @Override
+ public String getUrlQuery(Request request) {
+ HttpURI uri = request.getHttpURI();
+ return uri == null ? null : uri.getQuery();
+ }
+
+ @Nullable
+ @Override
+ public String getNetworkProtocolName(Request request, @Nullable Response unused) {
+ String protocol = request.getConnectionMetaData().getProtocol();
+ if (protocol != null && protocol.startsWith("HTTP/")) {
+ return "http";
+ }
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public String getNetworkProtocolVersion(Request request, @Nullable Response unused) {
+ String protocol = request.getConnectionMetaData().getProtocol();
+ if (protocol.startsWith("HTTP/")) {
+ return protocol.substring("HTTP/".length());
+ }
+ return null;
+ }
+
+ @Override
+ @Nullable
+ public InetSocketAddress getNetworkPeerInetSocketAddress(
+ Request request, @Nullable Response unused) {
+ SocketAddress address = request.getConnectionMetaData().getRemoteSocketAddress();
+ return address instanceof InetSocketAddress ? (InetSocketAddress) address : null;
+ }
+
+ @Nullable
+ @Override
+ public InetSocketAddress getNetworkLocalInetSocketAddress(
+ Request request, @Nullable Response unused) {
+ SocketAddress address = request.getConnectionMetaData().getLocalSocketAddress();
+ return address instanceof InetSocketAddress ? (InetSocketAddress) address : null;
+ }
+}
diff --git a/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12IgnoredTypesConfigurer.java b/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12IgnoredTypesConfigurer.java
new file mode 100644
index 000000000000..24b5d7d45bf2
--- /dev/null
+++ b/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12IgnoredTypesConfigurer.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.jetty.v12_0;
+
+import com.google.auto.service.AutoService;
+import io.opentelemetry.javaagent.extension.ignore.IgnoredTypesBuilder;
+import io.opentelemetry.javaagent.extension.ignore.IgnoredTypesConfigurer;
+import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
+
+@AutoService(IgnoredTypesConfigurer.class)
+public class Jetty12IgnoredTypesConfigurer implements IgnoredTypesConfigurer {
+
+ @Override
+ public void configure(IgnoredTypesBuilder builder, ConfigProperties config) {
+ // handling pipelined request sends HttpConnection instance (implements Runnable) to executor
+ // while scope from the previous request is still active
+ builder.ignoreTaskClass("org.eclipse.jetty.server.internal.HttpConnection");
+ }
+}
diff --git a/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12InstrumentationModule.java b/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12InstrumentationModule.java
new file mode 100644
index 000000000000..7986a86bce78
--- /dev/null
+++ b/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12InstrumentationModule.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.jetty.v12_0;
+
+import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
+import static java.util.Collections.singletonList;
+
+import com.google.auto.service.AutoService;
+import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
+import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
+import java.util.List;
+import net.bytebuddy.matcher.ElementMatcher;
+
+@AutoService(InstrumentationModule.class)
+public class Jetty12InstrumentationModule extends InstrumentationModule {
+
+ public Jetty12InstrumentationModule() {
+ super("jetty", "jetty-12.0");
+ }
+
+ @Override
+ public ElementMatcher.Junction classLoaderMatcher() {
+ return hasClassesNamed("org.eclipse.jetty.server.Request$Handler");
+ }
+
+ @Override
+ public List typeInstrumentations() {
+ return singletonList(new Jetty12ServerInstrumentation());
+ }
+}
diff --git a/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12ResponseMutator.java b/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12ResponseMutator.java
new file mode 100644
index 000000000000..9a99adc1efa4
--- /dev/null
+++ b/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12ResponseMutator.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.jetty.v12_0;
+
+import io.opentelemetry.javaagent.bootstrap.http.HttpServerResponseMutator;
+import org.eclipse.jetty.server.Response;
+
+public enum Jetty12ResponseMutator implements HttpServerResponseMutator {
+ INSTANCE;
+
+ @Override
+ public void appendHeader(Response response, String name, String value) {
+ response.getHeaders().add(name, value);
+ }
+}
diff --git a/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12ServerInstrumentation.java b/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12ServerInstrumentation.java
new file mode 100644
index 000000000000..b0efc09e4e76
--- /dev/null
+++ b/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12ServerInstrumentation.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.jetty.v12_0;
+
+import static io.opentelemetry.javaagent.instrumentation.jetty.v12_0.Jetty12Singletons.helper;
+import static net.bytebuddy.matcher.ElementMatchers.isPublic;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
+
+import io.opentelemetry.context.Context;
+import io.opentelemetry.context.Scope;
+import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge;
+import io.opentelemetry.javaagent.bootstrap.http.HttpServerResponseCustomizerHolder;
+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.matcher.ElementMatcher;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+
+class Jetty12ServerInstrumentation implements TypeInstrumentation {
+
+ @Override
+ public ElementMatcher typeMatcher() {
+ return named("org.eclipse.jetty.server.Server");
+ }
+
+ @Override
+ public void transform(TypeTransformer transformer) {
+ transformer.applyAdviceToMethod(
+ named("handle")
+ .and(takesArgument(0, named("org.eclipse.jetty.server.Request")))
+ .and(takesArgument(1, named("org.eclipse.jetty.server.Response")))
+ .and(takesArgument(2, named("org.eclipse.jetty.util.Callback")))
+ .and(isPublic()),
+ this.getClass().getName() + "$HandlerAdvice");
+ }
+
+ @SuppressWarnings("unused")
+ public static class HandlerAdvice {
+
+ @Advice.OnMethodEnter(suppress = Throwable.class)
+ public static void onEnter(
+ @Advice.This Object source,
+ @Advice.Argument(0) Request request,
+ @Advice.Argument(1) Response response,
+ @Advice.Local("otelContext") Context context,
+ @Advice.Local("otelScope") Scope scope) {
+
+ Context parentContext = Java8BytecodeBridge.currentContext();
+ if (!helper().shouldStart(parentContext, request)) {
+ return;
+ }
+
+ context = helper().start(parentContext, request, response);
+ scope = context.makeCurrent();
+
+ HttpServerResponseCustomizerHolder.getCustomizer()
+ .customize(context, response, Jetty12ResponseMutator.INSTANCE);
+ }
+
+ @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
+ public static void stopSpan(
+ @Advice.Argument(0) Request request,
+ @Advice.Argument(1) Response response,
+ @Advice.Thrown Throwable throwable,
+ @Advice.Local("otelContext") Context context,
+ @Advice.Local("otelScope") Scope scope) {
+ if (scope == null) {
+ return;
+ }
+ scope.close();
+ if (throwable != null) {
+ helper().end(context, request, response, throwable);
+ }
+ }
+ }
+}
diff --git a/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12Singletons.java b/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12Singletons.java
new file mode 100644
index 000000000000..d51e70378a14
--- /dev/null
+++ b/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12Singletons.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.jetty.v12_0;
+
+import io.opentelemetry.api.GlobalOpenTelemetry;
+import io.opentelemetry.instrumentation.api.incubator.semconv.http.HttpExperimentalAttributesExtractor;
+import io.opentelemetry.instrumentation.api.incubator.semconv.http.HttpServerExperimentalMetrics;
+import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
+import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder;
+import io.opentelemetry.instrumentation.api.semconv.http.HttpServerAttributesExtractor;
+import io.opentelemetry.instrumentation.api.semconv.http.HttpServerMetrics;
+import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRoute;
+import io.opentelemetry.instrumentation.api.semconv.http.HttpSpanNameExtractor;
+import io.opentelemetry.instrumentation.api.semconv.http.HttpSpanStatusExtractor;
+import io.opentelemetry.javaagent.bootstrap.internal.CommonConfig;
+import io.opentelemetry.javaagent.bootstrap.servlet.AppServerBridge;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+
+public final class Jetty12Singletons {
+ private static final String INSTRUMENTATION_NAME = "io.opentelemetry.jetty-12.0";
+
+ private static final Instrumenter INSTRUMENTER;
+
+ static {
+ Jetty12HttpAttributesGetter httpAttributesGetter = new Jetty12HttpAttributesGetter();
+
+ InstrumenterBuilder builder =
+ Instrumenter.builder(
+ GlobalOpenTelemetry.get(),
+ INSTRUMENTATION_NAME,
+ HttpSpanNameExtractor.builder(httpAttributesGetter)
+ .setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods())
+ .build())
+ .setSpanStatusExtractor(HttpSpanStatusExtractor.create(httpAttributesGetter))
+ .addAttributesExtractor(
+ HttpServerAttributesExtractor.builder(httpAttributesGetter)
+ .setCapturedRequestHeaders(CommonConfig.get().getServerRequestHeaders())
+ .setCapturedResponseHeaders(CommonConfig.get().getServerResponseHeaders())
+ .setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods())
+ .build())
+ .addContextCustomizer(
+ HttpServerRoute.builder(httpAttributesGetter)
+ .setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods())
+ .build())
+ .addContextCustomizer(
+ (context, request, attributes) ->
+ new AppServerBridge.Builder()
+ .captureServletAttributes()
+ .recordException()
+ .init(context))
+ .addOperationMetrics(HttpServerMetrics.get());
+ if (CommonConfig.get().shouldEmitExperimentalHttpServerTelemetry()) {
+ builder
+ .addAttributesExtractor(HttpExperimentalAttributesExtractor.create(httpAttributesGetter))
+ .addOperationMetrics(HttpServerExperimentalMetrics.get());
+ }
+ INSTRUMENTER = builder.buildServerInstrumenter(Jetty12TextMapGetter.INSTANCE);
+ }
+
+ private static final Jetty12Helper HELPER = new Jetty12Helper(INSTRUMENTER);
+
+ public static Jetty12Helper helper() {
+ return HELPER;
+ }
+
+ private Jetty12Singletons() {}
+}
diff --git a/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12TextMapGetter.java b/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12TextMapGetter.java
new file mode 100644
index 000000000000..59e9248eb9b5
--- /dev/null
+++ b/instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12TextMapGetter.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.jetty.v12_0;
+
+import io.opentelemetry.context.propagation.TextMapGetter;
+import org.eclipse.jetty.server.Request;
+
+enum Jetty12TextMapGetter implements TextMapGetter {
+ INSTANCE;
+
+ @Override
+ public Iterable keys(Request carrier) {
+ return carrier.getHeaders().getFieldNamesCollection();
+ }
+
+ @Override
+ public String get(Request carrier, String key) {
+ return carrier.getHeaders().get(key);
+ }
+}
diff --git a/instrumentation/jetty/jetty-12.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12HandlerTest.java b/instrumentation/jetty/jetty-12.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12HandlerTest.java
new file mode 100644
index 000000000000..7087b84d2d87
--- /dev/null
+++ b/instrumentation/jetty/jetty-12.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12HandlerTest.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.jetty.v12_0;
+
+import static io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions.DEFAULT_HTTP_ATTRIBUTES;
+import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS;
+import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR;
+import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION;
+import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.INDEXED_CHILD;
+import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.NOT_FOUND;
+import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM;
+import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT;
+import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import com.google.common.collect.Sets;
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.trace.SpanKind;
+import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
+import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpServerTest;
+import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension;
+import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions;
+import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint;
+import io.opentelemetry.sdk.testing.assertj.SpanDataAssert;
+import io.opentelemetry.semconv.SemanticAttributes;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.Callback;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+class Jetty12HandlerTest extends AbstractHttpServerTest {
+
+ @RegisterExtension
+ static final InstrumentationExtension testing = HttpServerInstrumentationExtension.forAgent();
+
+ private final TestHandler testHandler = new TestHandler();
+
+ @Override
+ protected Server setupServer() throws Exception {
+ Server server = new Server(port);
+ server.setHandler(testHandler);
+ server.start();
+ return server;
+ }
+
+ @Override
+ protected void stopServer(Server server) throws Exception {
+ server.stop();
+ }
+
+ @Override
+ protected void configure(HttpServerTestOptions options) {
+ options.setHttpAttributes(
+ unused ->
+ Sets.difference(
+ DEFAULT_HTTP_ATTRIBUTES, Collections.singleton(SemanticAttributes.HTTP_ROUTE)));
+ options.setExpectedException(new IllegalStateException(EXCEPTION.getBody()));
+ options.setHasResponseCustomizer(endpoint -> endpoint != EXCEPTION);
+ }
+
+ @Override
+ protected SpanDataAssert assertResponseSpan(
+ SpanDataAssert span, String method, ServerEndpoint endpoint) {
+ if (endpoint == REDIRECT) {
+ span.satisfies(spanData -> assertThat(spanData.getName()).endsWith(".sendRedirect"));
+ } else if (endpoint == ERROR) {
+ span.satisfies(spanData -> assertThat(spanData.getName()).endsWith(".sendError"));
+ }
+ span.hasKind(SpanKind.INTERNAL).hasAttributesSatisfying(Attributes::isEmpty);
+ return span;
+ }
+
+ private void handleRequest(Request request, Response response) {
+ ServerEndpoint endpoint = ServerEndpoint.forPath(request.getHttpURI().getPath());
+ controller(
+ endpoint,
+ () -> {
+ try {
+ response(request, response, endpoint);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ return null;
+ });
+ }
+
+ private void response(Request request, Response response, ServerEndpoint endpoint)
+ throws IOException {
+ if (SUCCESS.equals(endpoint)) {
+ response.setStatus(endpoint.getStatus());
+ response.write(true, StandardCharsets.UTF_8.encode(endpoint.getBody()), Callback.NOOP);
+ } else if (QUERY_PARAM.equals(endpoint)) {
+ response.setStatus(endpoint.getStatus());
+ response.write(
+ true, StandardCharsets.UTF_8.encode(request.getHttpURI().getQuery()), Callback.NOOP);
+ } else if (REDIRECT.equals(endpoint)) {
+ response.setStatus(endpoint.getStatus());
+ response.getHeaders().add("Location", "http://localhost:" + port + endpoint.getBody());
+ } else if (ERROR.equals(endpoint)) {
+ response.setStatus(endpoint.getStatus());
+ response.write(true, StandardCharsets.UTF_8.encode(endpoint.getBody()), Callback.NOOP);
+ } else if (CAPTURE_HEADERS.equals(endpoint)) {
+ response.getHeaders().add("X-Test-Response", request.getHeaders().get("X-Test-Request"));
+ response.setStatus(endpoint.getStatus());
+ response.write(true, StandardCharsets.UTF_8.encode(endpoint.getBody()), Callback.NOOP);
+ } else if (EXCEPTION.equals(endpoint)) {
+ throw new IllegalStateException(endpoint.getBody());
+ } else if (INDEXED_CHILD.equals(endpoint)) {
+ INDEXED_CHILD.collectSpanAttributes(
+ name -> Request.extractQueryParameters(request).getValue(name));
+ response.setStatus(endpoint.getStatus());
+ response.write(true, StandardCharsets.UTF_8.encode(endpoint.getBody()), Callback.NOOP);
+ } else {
+ response.setStatus(NOT_FOUND.getStatus());
+ response.write(true, StandardCharsets.UTF_8.encode(NOT_FOUND.getBody()), Callback.NOOP);
+ }
+ }
+
+ private class TestHandler extends Handler.Abstract {
+
+ @Override
+ public boolean handle(Request baseRequest, Response response, Callback callback) {
+ handleRequest(baseRequest, response);
+
+ callback.succeeded();
+ return true;
+ }
+ }
+}
diff --git a/instrumentation/jetty/jetty-8.0/javaagent/build.gradle.kts b/instrumentation/jetty/jetty-8.0/javaagent/build.gradle.kts
index dbdb36ea17c0..49d4649d3603 100644
--- a/instrumentation/jetty/jetty-8.0/javaagent/build.gradle.kts
+++ b/instrumentation/jetty/jetty-8.0/javaagent/build.gradle.kts
@@ -23,6 +23,7 @@ dependencies {
testInstrumentation(project(":instrumentation:servlet:servlet-javax-common:javaagent"))
testInstrumentation(project(":instrumentation:jetty:jetty-11.0:javaagent"))
+ testInstrumentation(project(":instrumentation:jetty:jetty-12.0:javaagent"))
testLibrary("org.eclipse.jetty:jetty-servlet:8.0.0.v20110901")
diff --git a/instrumentation/jetty/jetty-8.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jetty/v8_0/JettyHandlerTest.java b/instrumentation/jetty/jetty-8.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jetty/v8_0/JettyHandlerTest.java
index c532d9309b98..912875c09cd3 100644
--- a/instrumentation/jetty/jetty-8.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jetty/v8_0/JettyHandlerTest.java
+++ b/instrumentation/jetty/jetty-8.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jetty/v8_0/JettyHandlerTest.java
@@ -40,7 +40,7 @@
import org.eclipse.jetty.server.handler.ErrorHandler;
import org.junit.jupiter.api.extension.RegisterExtension;
-public class JettyHandlerTest extends AbstractHttpServerTest {
+class JettyHandlerTest extends AbstractHttpServerTest {
@RegisterExtension
static final InstrumentationExtension testing = HttpServerInstrumentationExtension.forAgent();
@@ -62,25 +62,17 @@ protected void handleErrorPage(
private static final TestHandler testHandler = new TestHandler();
@Override
- protected Server setupServer() {
+ protected Server setupServer() throws Exception {
Server server = new Server(port);
server.setHandler(testHandler);
server.addBean(errorHandler);
- try {
- server.start();
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
+ server.start();
return server;
}
@Override
- protected void stopServer(Server server) {
- try {
- server.stop();
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
+ protected void stopServer(Server server) throws Exception {
+ server.stop();
}
@Override
diff --git a/instrumentation/servlet/servlet-5.0/javaagent/build.gradle.kts b/instrumentation/servlet/servlet-5.0/javaagent/build.gradle.kts
index c1ccbd8d351e..0f7d1f12a242 100644
--- a/instrumentation/servlet/servlet-5.0/javaagent/build.gradle.kts
+++ b/instrumentation/servlet/servlet-5.0/javaagent/build.gradle.kts
@@ -17,7 +17,9 @@ dependencies {
compileOnly("jakarta.servlet:jakarta.servlet-api:5.0.0")
- testImplementation(project(":instrumentation:servlet:servlet-common:bootstrap"))
+ testInstrumentation(project(":instrumentation:jetty:jetty-11.0:javaagent"))
+
+ testImplementation(project(":instrumentation:servlet:servlet-5.0:testing"))
testLibrary("org.eclipse.jetty:jetty-server:11.0.0")
testLibrary("org.eclipse.jetty:jetty-servlet:11.0.0")
diff --git a/instrumentation/servlet/servlet-5.0/javaagent/src/test/groovy/JettyServlet5Test.groovy b/instrumentation/servlet/servlet-5.0/javaagent/src/test/groovy/JettyServlet5Test.groovy
index f9825014e63f..d71f8ab8eab6 100644
--- a/instrumentation/servlet/servlet-5.0/javaagent/src/test/groovy/JettyServlet5Test.groovy
+++ b/instrumentation/servlet/servlet-5.0/javaagent/src/test/groovy/JettyServlet5Test.groovy
@@ -3,6 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
+import io.opentelemetry.javaagent.instrumentation.servlet.v5_0.RequestDispatcherServlet
import jakarta.servlet.Servlet
import jakarta.servlet.ServletException
import jakarta.servlet.http.HttpServletRequest
@@ -10,6 +11,8 @@ import org.eclipse.jetty.server.Server
import org.eclipse.jetty.server.handler.ErrorHandler
import org.eclipse.jetty.servlet.ServletContextHandler
import spock.lang.IgnoreIf
+import test.AbstractServlet5Test
+import test.TestServlet5
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.AUTH_REQUIRED
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS
@@ -120,6 +123,13 @@ class JettyServlet5TestAsync extends JettyServlet5Test {
boolean errorEndpointUsesSendError() {
false
}
+
+ @Override
+ String getMetricsInstrumentationName() {
+ // with async requests the span is started in one instrumentation (server instrumentation)
+ // but ended from another (servlet instrumentation)
+ "io.opentelemetry.servlet-5.0"
+ }
}
@IgnoreIf({ !jvm.java11Compatible })
@@ -129,6 +139,13 @@ class JettyServlet5TestFakeAsync extends JettyServlet5Test {
Class servlet() {
TestServlet5.FakeAsync
}
+
+ @Override
+ String getMetricsInstrumentationName() {
+ // with async requests the span is started in one instrumentation (server instrumentation)
+ // but ended from another (servlet instrumentation)
+ "io.opentelemetry.servlet-5.0"
+ }
}
@IgnoreIf({ !jvm.java11Compatible })
@@ -220,6 +237,13 @@ class JettyServlet5TestDispatchImmediate extends JettyDispatchTest {
addServlet(context, "/dispatch" + INDEXED_CHILD.path, TestServlet5.DispatchImmediate)
addServlet(context, "/dispatch/recursive", TestServlet5.DispatchRecursive)
}
+
+ @Override
+ String getMetricsInstrumentationName() {
+ // with async requests the span is started in one instrumentation (server instrumentation)
+ // but ended from another (servlet instrumentation)
+ "io.opentelemetry.servlet-5.0"
+ }
}
@IgnoreIf({ !jvm.java11Compatible })
@@ -251,6 +275,13 @@ class JettyServlet5TestDispatchAsync extends JettyDispatchTest {
boolean errorEndpointUsesSendError() {
false
}
+
+ @Override
+ String getMetricsInstrumentationName() {
+ // with async requests the span is started in one instrumentation (server instrumentation)
+ // but ended from another (servlet instrumentation)
+ "io.opentelemetry.servlet-5.0"
+ }
}
abstract class JettyDispatchTest extends JettyServlet5Test {
diff --git a/instrumentation/servlet/servlet-5.0/javaagent/src/test/groovy/JettyServletHandlerTest.groovy b/instrumentation/servlet/servlet-5.0/javaagent/src/test/groovy/JettyServletHandlerTest.groovy
index 126deeb24b67..98eba9d1ed2c 100644
--- a/instrumentation/servlet/servlet-5.0/javaagent/src/test/groovy/JettyServletHandlerTest.groovy
+++ b/instrumentation/servlet/servlet-5.0/javaagent/src/test/groovy/JettyServletHandlerTest.groovy
@@ -13,6 +13,8 @@ import org.eclipse.jetty.server.Server
import org.eclipse.jetty.server.handler.ErrorHandler
import org.eclipse.jetty.servlet.ServletHandler
import spock.lang.IgnoreIf
+import test.AbstractServlet5Test
+import test.TestServlet5
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION
diff --git a/instrumentation/servlet/servlet-5.0/javaagent/src/test/groovy/TomcatServlet5Test.groovy b/instrumentation/servlet/servlet-5.0/javaagent/src/test/groovy/TomcatServlet5Test.groovy
index e59fdb615ba1..51375320b93e 100644
--- a/instrumentation/servlet/servlet-5.0/javaagent/src/test/groovy/TomcatServlet5Test.groovy
+++ b/instrumentation/servlet/servlet-5.0/javaagent/src/test/groovy/TomcatServlet5Test.groovy
@@ -5,6 +5,7 @@
import io.opentelemetry.instrumentation.test.asserts.TraceAssert
import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint
+import io.opentelemetry.javaagent.instrumentation.servlet.v5_0.RequestDispatcherServlet
import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpResponse
import jakarta.servlet.Servlet
import jakarta.servlet.ServletException
@@ -20,6 +21,8 @@ import org.apache.tomcat.JarScanFilter
import org.apache.tomcat.JarScanType
import spock.lang.Shared
import spock.lang.Unroll
+import test.AbstractServlet5Test
+import test.TestServlet5
import java.nio.file.Files
import java.util.concurrent.TimeUnit
diff --git a/instrumentation/servlet/servlet-5.0/jetty12-testing/build.gradle.kts b/instrumentation/servlet/servlet-5.0/jetty12-testing/build.gradle.kts
new file mode 100644
index 000000000000..610d0ef50601
--- /dev/null
+++ b/instrumentation/servlet/servlet-5.0/jetty12-testing/build.gradle.kts
@@ -0,0 +1,24 @@
+plugins {
+ id("otel.javaagent-testing")
+}
+
+dependencies {
+ library("org.eclipse.jetty.ee10:jetty-ee10-servlet:12.0.6")
+
+ testInstrumentation(project(":instrumentation:servlet:servlet-5.0:javaagent"))
+ testInstrumentation(project(":instrumentation:jetty:jetty-8.0:javaagent"))
+ testInstrumentation(project(":instrumentation:jetty:jetty-11.0:javaagent"))
+ testInstrumentation(project(":instrumentation:jetty:jetty-12.0:javaagent"))
+
+ testImplementation(project(":instrumentation:servlet:servlet-5.0:testing"))
+}
+
+otelJava {
+ minJavaVersionSupported.set(JavaVersion.VERSION_17)
+}
+
+tasks {
+ withType().configureEach {
+ jvmArgs("-Dotel.instrumentation.servlet.experimental.capture-request-parameters=test-parameter")
+ }
+}
diff --git a/instrumentation/servlet/servlet-5.0/jetty12-testing/src/test/groovy/Jetty12Servlet5Test.groovy b/instrumentation/servlet/servlet-5.0/jetty12-testing/src/test/groovy/Jetty12Servlet5Test.groovy
new file mode 100644
index 000000000000..545c1005e761
--- /dev/null
+++ b/instrumentation/servlet/servlet-5.0/jetty12-testing/src/test/groovy/Jetty12Servlet5Test.groovy
@@ -0,0 +1,241 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import io.opentelemetry.javaagent.instrumentation.servlet.v5_0.RequestDispatcherServlet
+import jakarta.servlet.Servlet
+import jakarta.servlet.ServletException
+import org.eclipse.jetty.ee10.servlet.ServletContextHandler
+import org.eclipse.jetty.server.Request
+import org.eclipse.jetty.server.Response
+import org.eclipse.jetty.server.Server
+import org.eclipse.jetty.util.Callback
+import test.AbstractServlet5Test
+import test.TestServlet5
+
+import java.nio.charset.StandardCharsets
+
+import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.AUTH_REQUIRED
+import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS
+import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_PARAMETERS
+import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR
+import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION
+import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.INDEXED_CHILD
+import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM
+import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT
+import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS
+
+abstract class Jetty12Servlet5Test extends AbstractServlet5Test