Merge remote-tracking branch 'origin/3.x' into 'origin/3.1'

Signed-off-by: Maxim Nesen <maxim.nesen@oracle.com>
diff --git a/NOTICE.md b/NOTICE.md
index 9c76708..48af210 100644
--- a/NOTICE.md
+++ b/NOTICE.md
@@ -95,7 +95,7 @@
 * Project: http://www.kineticjs.com, https://github.com/ericdrowell/KineticJS

 * Copyright: Eric Rowell

 

-org.objectweb.asm Version 9.4

+org.objectweb.asm Version 9.5

 * License: Modified BSD (https://asm.ow2.io/license.html)

 * Copyright (c) 2000-2011 INRIA, France Telecom. All rights reserved.

 

diff --git a/archetypes/jersey-heroku-webapp/src/main/resources/archetype-resources/pom.xml b/archetypes/jersey-heroku-webapp/src/main/resources/archetype-resources/pom.xml
index a781334..1cd8f78 100644
--- a/archetypes/jersey-heroku-webapp/src/main/resources/archetype-resources/pom.xml
+++ b/archetypes/jersey-heroku-webapp/src/main/resources/archetype-resources/pom.xml
@@ -112,7 +112,7 @@
 
     <properties>
         <jersey.version>${project.version}</jersey.version>
-        <jetty.version>11.0.14</jetty.version>
+        <jetty.version>11.0.15</jetty.version>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <surefire.mvn.plugin.version>3.0.0-M7</surefire.mvn.plugin.version>
     </properties>
diff --git a/bom/pom.xml b/bom/pom.xml
index feb6cba..9dd6ad4 100644
--- a/bom/pom.xml
+++ b/bom/pom.xml
@@ -90,6 +90,11 @@
             </dependency>
             <dependency>
                 <groupId>org.glassfish.jersey.connectors</groupId>
+                <artifactId>jersey-jetty-http2-connector</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.glassfish.jersey.connectors</groupId>
                 <artifactId>jersey-jdk-connector</artifactId>
                 <version>${project.version}</version>
             </dependency>
diff --git a/connectors/jetty-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyConnector.java b/connectors/jetty-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyConnector.java
index 3ede8a9..825a231 100644
--- a/connectors/jetty-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyConnector.java
+++ b/connectors/jetty-connector/src/main/java/org/glassfish/jersey/jetty/connector/JettyConnector.java
@@ -46,6 +46,7 @@
 
 import javax.net.ssl.SSLContext;
 
+import jakarta.ws.rs.ext.RuntimeDelegate;
 import org.eclipse.jetty.client.HttpClientTransport;
 import org.eclipse.jetty.client.HttpRequest;
 import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
@@ -254,7 +255,6 @@
 
     @Override
     public ClientResponse apply(final ClientRequest jerseyRequest) throws ProcessingException {
-        applyUserAgentHeader(jerseyRequest.getHeaders());
         final Request jettyRequest = translateRequest(jerseyRequest);
         final Map<String, String> clientHeadersSnapshot = writeOutBoundHeaders(jerseyRequest.getHeaders(), jettyRequest);
         final ContentProvider entity = getBytesProvider(jerseyRequest);
@@ -338,34 +338,17 @@
         return request;
     }
 
-    /**
-     * Re-write User-agent header set by Jetty; Jersey already sets this in its request (incl. Jetty version)
-     * it shall be propagated to Jetty client before HttpRequest instance is created.
-     * HttpRequest takes User Agent header from client then.
-     *
-     * @param headers - map of Jersey headers
-     */
-    private void applyUserAgentHeader(final MultivaluedMap<String, Object> headers) {
-        if (headers.containsKey(HttpHeaders.USER_AGENT)) {
-            final Map<String, String> stringHeaders =
-                    HeaderUtils.asStringHeadersSingleValue(headers, configuration);
-            client.setUserAgentField(
-                    new HttpField(HttpHeader.USER_AGENT,
-                            HttpHeader.USER_AGENT.name(),
-                            stringHeaders.get(HttpHeaders.USER_AGENT))
-            );
-        }
-    }
-
     private Map<String, String> writeOutBoundHeaders(final MultivaluedMap<String, Object> headers, final Request request) {
         final Map<String, String> stringHeaders = HeaderUtils.asStringHeadersSingleValue(headers, configuration);
 
-         if (request instanceof HttpRequest) {
-             final HttpRequest httpRequest = (HttpRequest) request;
-             for (final Map.Entry<String, String> e : stringHeaders.entrySet()) {
-                 httpRequest.addHeader(new HttpField(e.getKey(), e.getValue()));
-             }
-         }
+        // remove User-agent header set by Jetty; Jersey already sets this in its request (incl. Jetty version)
+        request.headers(httpFields -> httpFields.remove(HttpHeader.USER_AGENT));
+        if (request instanceof HttpRequest) {
+            final HttpRequest httpRequest = (HttpRequest) request;
+            for (final Map.Entry<String, String> e : stringHeaders.entrySet()) {
+                httpRequest.addHeader(new HttpField(e.getKey(), e.getValue()));
+            }
+        }
         return stringHeaders;
     }
 
@@ -422,7 +405,6 @@
 
     @Override
     public Future<?> apply(final ClientRequest jerseyRequest, final AsyncConnectorCallback callback) {
-        applyUserAgentHeader(jerseyRequest.getHeaders());
         final Request jettyRequest = translateRequest(jerseyRequest);
         final Map<String, String> clientHeadersSnapshot = writeOutBoundHeaders(jerseyRequest.getHeaders(), jettyRequest);
         final ContentProvider entity = getStreamProvider(jerseyRequest);
diff --git a/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/NettyConnector.java b/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/NettyConnector.java
index 5a776b6..4b4f16f 100644
--- a/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/NettyConnector.java
+++ b/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/NettyConnector.java
@@ -87,7 +87,7 @@
 import org.glassfish.jersey.client.spi.AsyncConnectorCallback;
 import org.glassfish.jersey.client.spi.Connector;
 import org.glassfish.jersey.message.internal.OutboundMessageContext;
-import org.glassfish.jersey.netty.connector.internal.JerseyChunkedInput;
+import org.glassfish.jersey.netty.connector.internal.NettyEntityWriter;
 
 /**
  * Netty connector implementation.
@@ -394,27 +394,34 @@
                         }
                     };
                 ch.closeFuture().addListener(closeListener);
-                if (jerseyRequest.getLengthLong() == -1) {
-                    HttpUtil.setTransferEncodingChunked(nettyRequest, true);
-                } else {
-                    nettyRequest.headers().add(HttpHeaderNames.CONTENT_LENGTH, jerseyRequest.getLengthLong());
+
+                final NettyEntityWriter entityWriter = NettyEntityWriter.getInstance(jerseyRequest, ch);
+                switch (entityWriter.getType()) {
+                    case CHUNKED:
+                        HttpUtil.setTransferEncodingChunked(nettyRequest, true);
+                        break;
+                    case PRESET:
+                        nettyRequest.headers().set(HttpHeaderNames.CONTENT_LENGTH, jerseyRequest.getLengthLong());
+                        break;
+//                  case DELAYED:
+//                      // Set later after the entity is "written"
+//                      break;
                 }
 
                 // Send the HTTP request.
-                ch.writeAndFlush(nettyRequest);
+                entityWriter.writeAndFlush(nettyRequest);
 
-                final JerseyChunkedInput jerseyChunkedInput = new JerseyChunkedInput(ch);
                 jerseyRequest.setStreamProvider(new OutboundMessageContext.StreamProvider() {
                     @Override
                     public OutputStream getOutputStream(int contentLength) throws IOException {
-                        return jerseyChunkedInput;
+                        return entityWriter.getOutputStream();
                     }
                 });
 
                 if (HttpUtil.isTransferEncodingChunked(nettyRequest)) {
-                    ch.write(new HttpChunkedInput(jerseyChunkedInput));
+                    entityWriter.write(new HttpChunkedInput(entityWriter.getChunkedInput()));
                 } else {
-                    ch.write(jerseyChunkedInput);
+                    entityWriter.write(entityWriter.getChunkedInput());
                 }
 
                 executorService.execute(new Runnable() {
@@ -425,19 +432,28 @@
 
                         try {
                             jerseyRequest.writeEntity();
+
+                            if (entityWriter.getType() == NettyEntityWriter.Type.DELAYED) {
+                                replaceHeaders(jerseyRequest, nettyRequest.headers()); // WriterInterceptor changes
+                                nettyRequest.headers().set(HttpHeaderNames.CONTENT_LENGTH, entityWriter.getLength());
+                                entityWriter.flush();
+                            }
+
                         } catch (IOException e) {
                             responseDone.completeExceptionally(e);
                         }
                     }
                 });
 
-                ch.flush();
+                if (entityWriter.getType() != NettyEntityWriter.Type.DELAYED) {
+                    entityWriter.flush();
+                }
             } else {
                 // Send the HTTP request.
                 ch.writeAndFlush(nettyRequest);
             }
 
-        } catch (InterruptedException e) {
+        } catch (IOException | InterruptedException e) {
             responseDone.completeExceptionally(e);
         }
     }
@@ -511,4 +527,11 @@
         }
         return headers;
     }
+
+    private static HttpHeaders replaceHeaders(ClientRequest jerseyRequest, HttpHeaders headers) {
+        for (final Map.Entry<String, List<String>> e : jerseyRequest.getStringHeaders().entrySet()) {
+            headers.set(e.getKey(), e.getValue());
+        }
+        return headers;
+    }
 }
diff --git a/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/internal/NettyEntityWriter.java b/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/internal/NettyEntityWriter.java
new file mode 100644
index 0000000..a9e7040
--- /dev/null
+++ b/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/internal/NettyEntityWriter.java
@@ -0,0 +1,255 @@
+/*
+ * 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.netty.connector.internal;
+
+import io.netty.channel.Channel;
+import io.netty.handler.stream.ChunkedInput;
+import org.glassfish.jersey.client.ClientProperties;
+import org.glassfish.jersey.client.ClientRequest;
+import org.glassfish.jersey.client.RequestEntityProcessing;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * The Entity Writer is used to write entity in Netty. One implementation is delayed,
+ * so that the complete message length can be set to Content-Length header.
+ */
+public interface NettyEntityWriter {
+
+    /**
+     * Type of the entity writer. {@code CHUNKED} is used for chunked data. {@code PRESET} is for buffered data, but the
+     * content length was pre-set by the customer. {@code DELAYED} is for buffered data where the content-length is unknown.
+     * The headers must not be written before the entity is provided by MessageBodyWriter to know the exact length.
+     */
+    enum Type {
+        CHUNKED,
+        PRESET,
+        DELAYED
+    }
+
+    /**
+     * Writes the Object to the channel
+     * @param object object to be written
+     */
+    void write(Object object);
+
+    /**
+     * Writes the Object to the channel and flush.
+     * @param object object to be written
+     */
+    void writeAndFlush(Object object);
+
+    /**
+     * Flushes the writen objects. Can throw IOException.
+     * @throws IOException
+     */
+    void flush() throws IOException;
+
+    /**
+     * Get the netty Chunked Input to be written.
+     * @return The Chunked input instance
+     */
+    ChunkedInput getChunkedInput();
+
+    /**
+     * Get the {@link OutputStream} used to write an entity
+     * @return the OutputStream to write an entity
+     */
+    OutputStream getOutputStream();
+
+    /**
+     * Get the length of the entity written to the {@link OutputStream}
+     * @return
+     */
+    long getLength();
+
+    /**
+     * Return Type of
+     * @return
+     */
+    Type getType();
+
+    static NettyEntityWriter getInstance(ClientRequest clientRequest, Channel channel) {
+        final long lengthLong = clientRequest.getLengthLong();
+        final RequestEntityProcessing entityProcessing = clientRequest.resolveProperty(
+                ClientProperties.REQUEST_ENTITY_PROCESSING, RequestEntityProcessing.class);
+
+        if ((entityProcessing == null && lengthLong == -1) || entityProcessing == RequestEntityProcessing.CHUNKED) {
+            return new DirectEntityWriter(channel, Type.CHUNKED);
+        } else if (lengthLong != -1) {
+            return new DirectEntityWriter(channel, Type.PRESET);
+        } else {
+            return new DelayedEntityWriter(channel, Type.DELAYED);
+        }
+    }
+
+    class DirectEntityWriter implements NettyEntityWriter {
+        private final Channel channel;
+        private final JerseyChunkedInput stream;
+        private final Type type;
+
+        public DirectEntityWriter(Channel channel, Type type) {
+            this.channel = channel;
+            stream = new JerseyChunkedInput(channel);
+            this.type = type;
+        }
+
+        @Override
+        public void write(Object object) {
+            channel.write(object);
+        }
+
+        @Override
+        public void writeAndFlush(Object object) {
+            channel.writeAndFlush(object);
+        }
+
+        @Override
+        public void flush() {
+            channel.flush();
+        }
+
+        @Override
+        public ChunkedInput getChunkedInput() {
+            return stream;
+        }
+
+        @Override
+        public OutputStream getOutputStream() {
+            return stream;
+        }
+
+        @Override
+        public long getLength() {
+            return stream.progress();
+        }
+
+        @Override
+        public Type getType() {
+            return type;
+        }
+    }
+
+    class DelayedEntityWriter implements NettyEntityWriter {
+        private final List<Runnable> delayedOps;
+        private final DirectEntityWriter writer;
+        private final DelayedOutputStream outputStream;
+
+        private boolean flushed = false;
+        private boolean closed = false;
+
+        public DelayedEntityWriter(Channel channel, Type type) {
+            this.writer = new DirectEntityWriter(channel, type);
+            this.delayedOps = new LinkedList<>();
+            this.outputStream = new DelayedOutputStream();
+        }
+
+
+        @Override
+        public void write(Object object) {
+            if (!flushed) {
+                delayedOps.add(() -> writer.write(object));
+            } else {
+                writer.write(object);
+            }
+        }
+
+        @Override
+        public void writeAndFlush(Object object) {
+            if (!flushed) {
+                delayedOps.add(() -> writer.writeAndFlush(object));
+            } else {
+                writer.writeAndFlush(object);
+            }
+        }
+
+        @Override
+        public void flush() throws IOException {
+            _flush();
+            if (!closed) {
+                closed = true;
+                writer.getOutputStream().close(); // Jersey automatically closes DelayedOutputStream not this one!
+            }
+            writer.flush();
+        }
+
+        private void _flush() throws IOException {
+            if (!flushed) {
+                flushed = true;
+                for (Runnable runnable : delayedOps) {
+                    runnable.run();
+                }
+
+                if (outputStream.b != null) {
+                    writer.getOutputStream().write(outputStream.b, outputStream.off, outputStream.len);
+                }
+            }
+        }
+
+        @Override
+        public ChunkedInput getChunkedInput() {
+            return writer.getChunkedInput();
+        }
+
+        @Override
+        public OutputStream getOutputStream() {
+            return outputStream;
+        }
+
+
+        @Override
+        public long getLength() {
+            return outputStream.len - outputStream.off;
+        }
+
+        @Override
+        public Type getType() {
+            return writer.getType();
+        }
+
+        private class DelayedOutputStream extends OutputStream {
+            private byte[] b;
+            private int off;
+            private int len;
+
+            @Override
+            public void write(int b) throws IOException {
+                write(new byte[]{(byte) (b & 0xFF)}, 0, 1);
+            }
+
+            @Override
+            public void write(byte[] b) throws IOException {
+                write(b, 0, b.length);
+            }
+
+            @Override
+            public void write(byte[] b, int off, int len) throws IOException {
+                if (!flushed && this.b == null) {
+                    this.b = b;
+                    this.off = off;
+                    this.len = len;
+                } else {
+                    DelayedEntityWriter.this._flush();
+                    writer.getOutputStream().write(b, off, len);
+                }
+            }
+        }
+    }
+}
diff --git a/connectors/netty-connector/src/test/java/org/glassfish/jersey/netty/connector/BufferedTest.java b/connectors/netty-connector/src/test/java/org/glassfish/jersey/netty/connector/BufferedTest.java
new file mode 100644
index 0000000..a01c04d
--- /dev/null
+++ b/connectors/netty-connector/src/test/java/org/glassfish/jersey/netty/connector/BufferedTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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.netty.connector;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.client.ClientProperties;
+import org.glassfish.jersey.client.RequestEntityProcessing;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.WebApplicationException;
+import jakarta.ws.rs.client.ClientRequestContext;
+import jakarta.ws.rs.client.ClientRequestFilter;
+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.MediaType;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.ext.WriterInterceptor;
+import jakarta.ws.rs.ext.WriterInterceptorContext;
+import java.io.IOException;
+
+public class BufferedTest extends JerseyTest {
+
+    private static String HEADER_1 = "First";
+    private static String HEADER_2 = "Second";
+    private static String HEADER_3 = "Third";
+    private static String ENTITY = "entity";
+
+
+    @Path("/buffered")
+    public static class BufferedTestResource {
+        @POST
+        public String post(@Context HttpHeaders headers, String entity) {
+            System.out.println("Remote");
+            String ret = headers.getHeaderString(HEADER_1)
+                    + headers.getHeaderString(HEADER_2)
+                    + headers.getHeaderString(HEADER_3)
+                    + entity;
+            System.out.println(ret);
+            return ret;
+        }
+    }
+
+    @Override
+    protected Application configure() {
+        return new ResourceConfig(BufferedTestResource.class);
+    }
+
+    @Override
+    protected void configureClient(ClientConfig config) {
+        config.connectorProvider(new NettyConnectorProvider())
+                .property(ClientProperties.REQUEST_ENTITY_PROCESSING, RequestEntityProcessing.BUFFERED);
+    }
+
+    @Test
+    public void test() {
+        try (Response r = target("buffered")
+                .register(new ClientRequestFilter() {
+                    @Override
+                    public void filter(ClientRequestContext requestContext) throws IOException {
+                        requestContext.setEntity(ENTITY);
+                        requestContext.getHeaders().add(HEADER_2, HEADER_2);
+                    }
+                })
+                .register(new WriterInterceptor() {
+                    @Override
+                    public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException {
+                        context.getHeaders().add(HEADER_3, HEADER_3);
+                        context.proceed();
+                    }
+                })
+                .request()
+                .header(HEADER_1, HEADER_1)
+                .post(Entity.entity("ENTITY", MediaType.TEXT_PLAIN_TYPE))) {
+            String response = r.readEntity(String.class);
+            Assertions.assertEquals(HEADER_1 + HEADER_2 + HEADER_3 + ENTITY, response);
+        }
+    }
+}
diff --git a/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainerProvider.java b/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainerProvider.java
index f4faac6..4b80825 100644
--- a/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainerProvider.java
+++ b/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainerProvider.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 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
@@ -31,18 +31,27 @@
  */
 public final class JettyHttpContainerProvider implements ContainerProvider {
 
+    public static final String HANDLER_NAME = "org.eclipse.jetty.server.Handler";
     @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
-                && ("org.eclipse.jetty.server.Handler".equalsIgnoreCase(type.getCanonicalName())
-                        || JettyHttpContainer.class == type)
-        ) {
+        if (type != null && (HANDLER_NAME.equalsIgnoreCase(type.getCanonicalName()) || JettyHttpContainer.class == type)) {
             return type.cast(new JettyHttpContainer(application));
         }
         return null;
     }
 
+    public <T> T createContainer(final Class<T> type, final Application application,
+                                 Object parentContext) 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 JettyHttpContainer(application, parentContext));
+        }
+        return null;
+    }
+
 }
diff --git a/containers/jetty-http2/pom.xml b/containers/jetty-http2/pom.xml
new file mode 100644
index 0000000..ce2e199
--- /dev/null
+++ b/containers/jetty-http2/pom.xml
@@ -0,0 +1,108 @@
+<?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>
+
+    <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-server</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-util</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty.http2</groupId>
+            <artifactId>http2-server</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-alpn-conscrypt-server</artifactId>
+        </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>
+
+</project>
diff --git a/containers/jetty-http2/src/main/java/org/glassfish/jersey/jetty/http2/JettyHttp2ContainerFactory.java b/containers/jetty-http2/src/main/java/org/glassfish/jersey/jetty/http2/JettyHttp2ContainerFactory.java
new file mode 100644
index 0000000..068ea4c
--- /dev/null
+++ b/containers/jetty-http2/src/main/java/org/glassfish/jersey/jetty/http2/JettyHttp2ContainerFactory.java
@@ -0,0 +1,218 @@
+/*
+ * 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.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() {
+
+    }
+
+    /**
+     * 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/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..a402b27
--- /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;
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..9807184
--- /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 11.
\ No newline at end of file
diff --git a/containers/pom.xml b/containers/pom.xml
index cc799ff..a778a77 100644
--- a/containers/pom.xml
+++ b/containers/pom.xml
@@ -41,6 +41,7 @@
         <module>jersey-servlet-core</module>
         <module>jersey-servlet</module>
         <module>jetty-http</module>
+        <module>jetty-http2</module>
         <module>jetty-servlet</module>
         <module>netty-http</module>
         <module>simple-http</module>
diff --git a/core-common/src/main/java/org/glassfish/jersey/internal/util/collection/GuardianStringKeyMultivaluedMap.java b/core-common/src/main/java/org/glassfish/jersey/internal/util/collection/GuardianStringKeyMultivaluedMap.java
index 7e9e9f5..358a329 100644
--- a/core-common/src/main/java/org/glassfish/jersey/internal/util/collection/GuardianStringKeyMultivaluedMap.java
+++ b/core-common/src/main/java/org/glassfish/jersey/internal/util/collection/GuardianStringKeyMultivaluedMap.java
@@ -16,13 +16,17 @@
 
 package org.glassfish.jersey.internal.util.collection;
 
+import jakarta.ws.rs.core.MediaType;
 import jakarta.ws.rs.core.MultivaluedMap;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
+import java.util.ListIterator;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.function.Consumer;
 
 /**
  * The {@link MultivaluedMap} wrapper that is able to set guards observing changes of values represented by a key.
@@ -35,6 +39,10 @@
     private final MultivaluedMap<String, V> inner;
     private final Map<String, Boolean> guards = new HashMap<>();
 
+    private static boolean isMutable(Object mutable) {
+        return !String.class.isInstance(mutable) && !MediaType.class.isInstance(mutable);
+    }
+
     public GuardianStringKeyMultivaluedMap(MultivaluedMap<String, V> inner) {
         this.inner = inner;
     }
@@ -53,7 +61,11 @@
 
     @Override
     public V getFirst(String key) {
-        return inner.getFirst(key);
+        V first = inner.getFirst(key);
+        if (isMutable(key)) {
+            observe(key);
+        }
+        return first;
     }
 
     @Override
@@ -101,7 +113,15 @@
 
     @Override
     public List<V> get(Object key) {
-        return inner.get(key);
+        final List<V> innerList = inner.get(key);
+        if (innerList != null) {
+            for (Map.Entry<String, Boolean> guard : guards.entrySet()) {
+                if (guard.getKey().equals(key)) {
+                    return new GuardianList(innerList, guard);
+                }
+            }
+        }
+        return innerList;
     }
 
     @Override
@@ -139,11 +159,13 @@
 
     @Override
     public Collection<List<V>> values() {
+        observeAll();
         return inner.values();
     }
 
     @Override
     public Set<Entry<String, List<V>>> entrySet() {
+        observeAll();
         return inner.entrySet();
     }
 
@@ -208,4 +230,225 @@
     public int hashCode() {
         return Objects.hash(inner, guards);
     }
+
+    private static class MutableGuardian<V> {
+        protected final Map.Entry<String, Boolean> guard;
+
+        private MutableGuardian(Entry<String, Boolean> guard) {
+            this.guard = guard;
+        }
+
+        protected V guardMutable(V mutable) {
+            if (isMutable(mutable)) {
+                guard.setValue(true);
+            }
+            return mutable;
+        }
+    }
+
+    private static class GuardianList<V> extends MutableGuardian<V> implements List<V>  {
+        private final List<V> guarded;
+
+        public GuardianList(List<V> guarded, Map.Entry<String, Boolean> guard) {
+            super(guard);
+            this.guarded = guarded;
+        }
+
+        @Override
+        public int size() {
+            return guarded.size();
+        }
+
+        @Override
+        public boolean isEmpty() {
+            return guarded.isEmpty();
+        }
+
+        @Override
+        public boolean contains(Object o) {
+            return guarded.contains(o);
+        }
+
+        @Override
+        public Iterator<V> iterator() {
+            return new GuardianIterator<>(guarded.iterator(), guard);
+        }
+
+        @Override
+        public Object[] toArray() {
+            guard.setValue(true);
+            return guarded.toArray();
+        }
+
+        @Override
+        public <T> T[] toArray(T[] a) {
+            guard.setValue(true);
+            return guarded.toArray(a);
+        }
+
+        @Override
+        public boolean add(V e) {
+            guard.setValue(true);
+            return guarded.add(e);
+        }
+
+        @Override
+        public boolean remove(Object o) {
+            guard.setValue(true);
+            return guarded.remove(o);
+        }
+
+        @Override
+        public boolean containsAll(Collection<?> c) {
+            return guarded.containsAll(c);
+        }
+
+        @Override
+        public boolean addAll(Collection<? extends V> c) {
+            guard.setValue(true);
+            return guarded.addAll(c);
+        }
+
+        @Override
+        public boolean addAll(int index, Collection<? extends V> c) {
+            guard.setValue(true);
+            return guarded.addAll(index, c);
+        }
+
+        @Override
+        public boolean removeAll(Collection<?> c) {
+            guard.setValue(true);
+            return guarded.removeAll(c);
+        }
+
+        @Override
+        public boolean retainAll(Collection<?> c) {
+            guard.setValue(true);
+            return guarded.retainAll(c);
+        }
+
+        @Override
+        public void clear() {
+            guard.setValue(true);
+            guarded.clear();
+        }
+
+        @Override
+        public V get(int index) {
+            return guardMutable(guarded.get(index));
+        }
+
+        @Override
+        public V set(int index, V element) {
+            guard.setValue(true);
+            return guarded.set(index, element);
+        }
+
+        @Override
+        public void add(int index, V element) {
+            guard.setValue(true);
+            guarded.add(index, element);
+        }
+
+        @Override
+        public V remove(int index) {
+            guard.setValue(true);
+            return guarded.remove(index);
+        }
+
+        @Override
+        public int indexOf(Object o) {
+            return guarded.indexOf(o);
+        }
+
+        @Override
+        public int lastIndexOf(Object o) {
+            return guarded.lastIndexOf(o);
+        }
+
+        @Override
+        public ListIterator<V> listIterator() {
+            return new GuardianListIterator<>(guarded.listIterator(), guard);
+        }
+
+        @Override
+        public ListIterator<V> listIterator(int index) {
+            return new GuardianListIterator<>(guarded.listIterator(index), guard);
+        }
+
+        @Override
+        public List<V> subList(int fromIndex, int toIndex) {
+            final List<V> sublist = guarded.subList(fromIndex, toIndex);
+            return sublist != null ? new GuardianList<>(sublist, guard) : sublist;
+        }
+    }
+
+    private static class GuardianIterator<V> extends MutableGuardian<V> implements Iterator<V> {
+        protected final Iterator<V> guarded;
+
+        public GuardianIterator(Iterator<V> guarded, Map.Entry<String, Boolean> guard) {
+            super(guard);
+            this.guarded = guarded;
+        }
+
+        @Override
+        public boolean hasNext() {
+            return guarded.hasNext();
+        }
+
+        @Override
+        public V next() {
+            return guardMutable(guarded.next());
+        }
+
+        @Override
+        public void remove() {
+            guard.setValue(true);
+            guarded.remove();
+        }
+
+        @Override
+        public void forEachRemaining(Consumer<? super V> action) {
+            guarded.forEachRemaining(action);
+        }
+    }
+
+    private static class GuardianListIterator<V> extends GuardianIterator<V> implements ListIterator<V> {
+
+        public GuardianListIterator(Iterator<V> guarded, Entry<String, Boolean> guard) {
+            super(guarded, guard);
+        }
+
+        @Override
+        public boolean hasPrevious() {
+            return ((ListIterator<V>) guarded).hasPrevious();
+        }
+
+        @Override
+        public V previous() {
+            return guardMutable(((ListIterator<V>) guarded).previous());
+        }
+
+        @Override
+        public int nextIndex() {
+            return ((ListIterator<V>) guarded).nextIndex();
+        }
+
+        @Override
+        public int previousIndex() {
+            return ((ListIterator<V>) guarded).previousIndex();
+        }
+
+        @Override
+        public void set(V v) {
+            ((ListIterator<V>) guarded).set(v);
+            guard.setValue(true);
+        }
+
+        @Override
+        public void add(V v) {
+            ((ListIterator<V>) guarded).add(v);
+            guard.setValue(true);
+        }
+    }
 }
diff --git a/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/ClassReader.java b/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/ClassReader.java
index d6e90a7..21969f0 100644
--- a/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/ClassReader.java
+++ b/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/ClassReader.java
@@ -194,7 +194,7 @@
     this.b = classFileBuffer;
     // Check the class' major_version. This field is after the magic and minor_version fields, which
     // use 4 and 2 bytes respectively.
-    if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V20) {
+    if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V21) {
       throw new IllegalArgumentException(
           "Unsupported class file major version " + readShort(classFileOffset + 6));
     }
@@ -2050,6 +2050,7 @@
     currentOffset = bytecodeStartOffset;
     while (currentOffset < bytecodeEndOffset) {
       final int currentBytecodeOffset = currentOffset - bytecodeStartOffset;
+      readBytecodeInstructionOffset(currentBytecodeOffset);
 
       // Visit the label and the line number(s) for this bytecode offset, if any.
       Label currentLabel = labels[currentBytecodeOffset];
@@ -2666,6 +2667,20 @@
   }
 
   /**
+   * Handles the bytecode offset of the next instruction to be visited in {@link
+   * #accept(ClassVisitor,int)}. This method is called just before the instruction and before its
+   * associated label and stack map frame, if any. The default implementation of this method does
+   * nothing. Subclasses can override this method to store the argument in a mutable field, for
+   * instance, so that {@link MethodVisitor} instances can get the bytecode offset of each visited
+   * instruction (if so, the usual concurrency issues related to mutable data should be addressed).
+   *
+   * @param bytecodeOffset the bytecode offset of the next instruction to be visited.
+   */
+  protected void readBytecodeInstructionOffset(final int bytecodeOffset) {
+    // Do nothing by default.
+  }
+
+  /**
    * Returns the label corresponding to the given bytecode offset. The default implementation of
    * this method creates a label for the given offset if it has not been already created.
    *
diff --git a/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/Frame.java b/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/Frame.java
index 41d9ef4..be4364a 100644
--- a/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/Frame.java
+++ b/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/Frame.java
@@ -367,11 +367,12 @@
             typeValue = REFERENCE_KIND | symbolTable.addType(internalName);
             break;
           default:
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException(
+                "Invalid descriptor fragment: " + buffer.substring(elementDescriptorOffset));
         }
         return ((elementDescriptorOffset - offset) << DIM_SHIFT) | typeValue;
       default:
-        throw new IllegalArgumentException();
+        throw new IllegalArgumentException("Invalid descriptor: " + buffer.substring(offset));
     }
   }
 
diff --git a/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/Label.java b/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/Label.java
index a01326c..2933a99 100644
--- a/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/Label.java
+++ b/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/Label.java
@@ -81,6 +81,9 @@
   /** A flag indicating that the basic block corresponding to a label is the end of a subroutine. */
   static final int FLAG_SUBROUTINE_END = 64;
 
+  /** A flag indicating that this label has at least one associated line number. */
+  static final int FLAG_LINE_NUMBER = 128;
+
   /**
    * The number of elements to add to the {@link #otherLineNumbers} array when it needs to be
    * resized to store a new source line number.
@@ -145,9 +148,9 @@
   short flags;
 
   /**
-   * The source line number corresponding to this label, or 0. If there are several source line
-   * numbers corresponding to this label, the first one is stored in this field, and the remaining
-   * ones are stored in {@link #otherLineNumbers}.
+   * The source line number corresponding to this label, if {@link #FLAG_LINE_NUMBER} is set. If
+   * there are several source line numbers corresponding to this label, the first one is stored in
+   * this field, and the remaining ones are stored in {@link #otherLineNumbers}.
    */
   private short lineNumber;
 
@@ -332,7 +335,8 @@
    * @param lineNumber a source line number (which should be strictly positive).
    */
   final void addLineNumber(final int lineNumber) {
-    if (this.lineNumber == 0) {
+    if ((flags & FLAG_LINE_NUMBER) == 0) {
+      flags |= FLAG_LINE_NUMBER;
       this.lineNumber = (short) lineNumber;
     } else {
       if (otherLineNumbers == null) {
@@ -356,7 +360,7 @@
    */
   final void accept(final MethodVisitor methodVisitor, final boolean visitLineNumbers) {
     methodVisitor.visitLabel(this);
-    if (visitLineNumbers && lineNumber != 0) {
+    if (visitLineNumbers && (flags & FLAG_LINE_NUMBER) != 0) {
       methodVisitor.visitLineNumber(lineNumber & 0xFFFF, this);
       if (otherLineNumbers != null) {
         for (int i = 1; i <= otherLineNumbers[0]; ++i) {
diff --git a/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/MethodVisitor.java b/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/MethodVisitor.java
index 269b034..c8a482b 100644
--- a/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/MethodVisitor.java
+++ b/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/MethodVisitor.java
@@ -30,7 +30,7 @@
 /**
  * A visitor to visit a Java method. The methods of this class must be called in the following
  * order: ( {@code visitParameter} )* [ {@code visitAnnotationDefault} ] ( {@code visitAnnotation} |
- * {@code visitAnnotableParameterCount} | {@code visitParameterAnnotation} {@code
+ * {@code visitAnnotableParameterCount} | {@code visitParameterAnnotation} | {@code
  * visitTypeAnnotation} | {@code visitAttribute} )* [ {@code visitCode} ( {@code visitFrame} |
  * {@code visit<i>X</i>Insn} | {@code visitLabel} | {@code visitInsnAnnotation} | {@code
  * visitTryCatchBlock} | {@code visitTryCatchAnnotation} | {@code visitLocalVariable} | {@code
diff --git a/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/Opcodes.java b/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/Opcodes.java
index 63963b0..b577623 100644
--- a/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/Opcodes.java
+++ b/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/Opcodes.java
@@ -286,6 +286,7 @@
   int V18 = 0 << 16 | 62;
   int V19 = 0 << 16 | 63;
   int V20 = 0 << 16 | 64;
+  int V21 = 0 << 16 | 65;
 
   /**
    * Version flag indicating that the class is using 'preview' features.
diff --git a/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/RecordComponentWriter.java b/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/RecordComponentWriter.java
index 90053bd..b222c31 100644
--- a/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/RecordComponentWriter.java
+++ b/core-server/src/main/java/jersey/repackaged/org/objectweb/asm/RecordComponentWriter.java
@@ -37,7 +37,7 @@
   /** The name_index field of the Record attribute. */
   private final int nameIndex;
 
-  /** The descriptor_index field of the the Record attribute. */
+  /** The descriptor_index field of the Record attribute. */
   private final int descriptorIndex;
 
   /**
diff --git a/core-server/src/main/java/org/glassfish/jersey/server/internal/scanning/AnnotationAcceptingListener.java b/core-server/src/main/java/org/glassfish/jersey/server/internal/scanning/AnnotationAcceptingListener.java
index e85d57a..7505b0c 100644
--- a/core-server/src/main/java/org/glassfish/jersey/server/internal/scanning/AnnotationAcceptingListener.java
+++ b/core-server/src/main/java/org/glassfish/jersey/server/internal/scanning/AnnotationAcceptingListener.java
@@ -309,7 +309,7 @@
 
     private static class ClassReaderWrapper {
         private static final Logger LOGGER = Logger.getLogger(ClassReader.class.getName());
-        private static final int WARN_VERSION = Opcodes.V20;
+        private static final int WARN_VERSION = Opcodes.V21;
         private static final int INPUT_STREAM_DATA_CHUNK_SIZE = 4096;
 
         private final byte[] b;
diff --git a/core-server/src/main/resources/META-INF/NOTICE.markdown b/core-server/src/main/resources/META-INF/NOTICE.markdown
index f06adeb..00b8fa0 100644
--- a/core-server/src/main/resources/META-INF/NOTICE.markdown
+++ b/core-server/src/main/resources/META-INF/NOTICE.markdown
@@ -36,7 +36,7 @@
 * Copyright (c) 2015-2018 Oracle and/or its affiliates. All rights reserved.

 * Copyright 2010-2013 Coda Hale and Yammer, Inc.

 

-org.objectweb.asm Version 9.4

+org.objectweb.asm Version 9.5

 * License: Modified BSD (https://asm.ow2.io/license.html)

 * Copyright: (c) 2000-2011 INRIA, France Telecom. All rights reserved.

 

diff --git a/examples/NOTICE.md b/examples/NOTICE.md
index e8d46fd..f1ddd2d 100644
--- a/examples/NOTICE.md
+++ b/examples/NOTICE.md
@@ -96,7 +96,7 @@
 * Project: http://www.kineticjs.com, https://github.com/ericdrowell/KineticJS
 * Copyright: Eric Rowell
 
-org.objectweb.asm Version 9.4
+org.objectweb.asm Version 9.5
 * License: Modified BSD (https://asm.ow2.io/license.html)
 * Copyright (c) 2000-2011 INRIA, France Telecom. All rights reserved.
 
diff --git a/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/AbstractCdiBeanSupplier.java b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/AbstractCdiBeanSupplier.java
index c15658e..f070d61 100644
--- a/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/AbstractCdiBeanSupplier.java
+++ b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/AbstractCdiBeanSupplier.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014, 2021 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 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
@@ -16,10 +16,13 @@
 
 package org.glassfish.jersey.ext.cdi1x.internal;
 
+import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.lang.annotation.Annotation;
 import java.util.Iterator;
 import java.util.NoSuchElementException;
 import java.util.Set;
+import java.util.logging.Logger;
 
 import jakarta.enterprise.context.spi.Contextual;
 import jakarta.enterprise.context.spi.CreationalContext;
@@ -44,6 +47,8 @@
  */
 public abstract class AbstractCdiBeanSupplier<T> implements DisposableSupplier<T> {
 
+    private static final Logger LOGGER = Logger.getLogger(AbstractCdiBeanSupplier.class.getName());
+
     final Class<T> clazz;
     final InstanceManager<T> referenceProvider;
     final Annotation[] qualifiers;
@@ -115,7 +120,16 @@
         try {
             return target.produce(ctx);
         } catch (Exception e) {
-            return im.create(clazz);
+            LOGGER.fine(LocalizationMessages.CDI_FAILED_LOADING(clazz, e.getMessage()));
+            try {
+                return im.create(clazz);
+            } catch (RuntimeException re) {
+                StringWriter sw = new StringWriter();
+                PrintWriter pw = new PrintWriter(sw);
+                e.printStackTrace(pw);
+                LOGGER.warning(LocalizationMessages.CDI_FAILED_LOADING(clazz, sw.toString()));
+                throw re;
+            }
         }
     }
 
diff --git a/ext/cdi/jersey-cdi1x/src/main/resources/org/glassfish/jersey/ext/cdi1x/internal/localization.properties b/ext/cdi/jersey-cdi1x/src/main/resources/org/glassfish/jersey/ext/cdi1x/internal/localization.properties
index ee1f8e7..fcd79f3 100644
--- a/ext/cdi/jersey-cdi1x/src/main/resources/org/glassfish/jersey/ext/cdi1x/internal/localization.properties
+++ b/ext/cdi/jersey-cdi1x/src/main/resources/org/glassfish/jersey/ext/cdi1x/internal/localization.properties
@@ -1,5 +1,5 @@
 #
-# Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2013, 2023 Oracle and/or its affiliates. All rights reserved.
 # Copyright (c) 2018 Payara Foundation and/or its affiliates. All rights reserved.
 #
 # This program and the accompanying materials are made available under the
@@ -21,6 +21,7 @@
 cdi.class.bound.with.cdi=Class, {0}, has been bound by Jersey CDI component provider.
 cdi.hk2.bean.registered=CDI beans backed by HK2 have been registered for the following types: {0}
 cdi.lookup.failed=Error when lookup instance of class, {0}, in CDI.
+cdi.failed.loading=CDI failed instantiating the class {0} with {1}, falling back to HK2.
 cdi.multiple.locators.into.simple.app=Trying to register multiple service locators into single service locator application.
 cdi.provider.initialized=Jersey CDI component provider initialized.
 cdi.request.scoped.components.recognized=The following CDI types were recognized as request scoped components in Jersey: {0}.
diff --git a/ext/microprofile/mp-rest-client/pom.xml b/ext/microprofile/mp-rest-client/pom.xml
index b01d133..0bf5d0d 100644
--- a/ext/microprofile/mp-rest-client/pom.xml
+++ b/ext/microprofile/mp-rest-client/pom.xml
@@ -92,6 +92,11 @@
             <artifactId>jersey-media-sse</artifactId>
             <version>${project.version}</version>
         </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/InterceptorInvocationContext.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/InterceptorInvocationContext.java
index 014d603..059aa84 100644
--- a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/InterceptorInvocationContext.java
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/InterceptorInvocationContext.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 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
@@ -31,7 +31,7 @@
  * Invokes all interceptors bound to the target.
  *
  * This approach needs to be used due to CDI does not handle properly interceptor invocation
- * on proxy instances.
+ * on proxy instances. This class is thread safe.
  *
  * @author David Kral
  */
@@ -42,8 +42,8 @@
     private final Map<String, Object> contextData;
     private final List<InvocationInterceptor> interceptors;
     private final WebTarget classLevelWebTarget;
-    private Object[] args;
-    private int currentPosition;
+    private volatile Object[] args;
+    private final int currentPosition;
 
     /**
      * Creates new instance of InterceptorInvocationContext.
@@ -57,14 +57,23 @@
                                  MethodModel methodModel,
                                  Method method,
                                  Object[] args) {
-        this.contextData = new HashMap<>();
         this.currentPosition = 0;
+        this.contextData = new HashMap<>();
         this.methodModel = methodModel;
         this.method = method;
         this.args = args;
         this.classLevelWebTarget = classLevelWebTarget;
         this.interceptors = methodModel.getInvocationInterceptors();
+    }
 
+    InterceptorInvocationContext(InterceptorInvocationContext other, int currentPosition) {
+        this.currentPosition = currentPosition;
+        this.contextData = other.contextData;
+        this.methodModel = other.methodModel;
+        this.method = other.method;
+        this.args = other.args;
+        this.classLevelWebTarget = other.classLevelWebTarget;
+        this.interceptors = other.interceptors;
     }
 
     @Override
@@ -102,10 +111,21 @@
         return contextData;
     }
 
+    /**
+     * This method shall create the next invocation context using {@code position + 1}
+     * and store it in the {@code contextData} map. This is currently used by Helidon's
+     * fault tolerance implementation to get around a problem with CDI's default invocation
+     * context {@code WeldInvocationContextImpl} not correctly supporting async calls.
+     *
+     * @return value returned by intercepted method.
+     */
     @Override
     public Object proceed() {
+        InvocationContext nextContext = new InterceptorInvocationContext(this, currentPosition + 1);
+        contextData.put(getClass().getName(), nextContext);     // accessible to FT interceptor
+
         if (currentPosition < interceptors.size()) {
-            return interceptors.get(currentPosition++).intercept(this);
+            return interceptors.get(currentPosition).intercept(nextContext);
         } else {
             return methodModel.invokeMethod(classLevelWebTarget, method, args);
         }
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/PathParamModel.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/PathParamModel.java
index 6a00a44..d3241df 100644
--- a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/PathParamModel.java
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/PathParamModel.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 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
@@ -43,7 +43,7 @@
     @Override
     public WebTarget handleParameter(WebTarget requestPart, Class<? extends Annotation> annotationClass, Object instance) {
         Object resolvedValue = interfaceModel.resolveParamValue(instance, parameter);
-        return requestPart.resolveTemplate(pathParamName, resolvedValue);
+        return requestPart.resolveTemplate(pathParamName, resolvedValue, false);
     }
 
     @Override
diff --git a/ext/microprofile/mp-rest-client/src/test/java/org/glassfish/jersey/microprofile/restclient/InterceptorInvocationContextTest.java b/ext/microprofile/mp-rest-client/src/test/java/org/glassfish/jersey/microprofile/restclient/InterceptorInvocationContextTest.java
new file mode 100644
index 0000000..9ac7451
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/test/java/org/glassfish/jersey/microprofile/restclient/InterceptorInvocationContextTest.java
@@ -0,0 +1,53 @@
+/*
+ * 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.microprofile.restclient;
+
+import jakarta.ws.rs.client.WebTarget;
+
+import java.lang.reflect.Method;
+import java.util.Collections;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import static org.glassfish.jersey.microprofile.restclient.InterceptorInvocationContext.InvocationInterceptor;
+
+class InterceptorInvocationContextTest {
+
+    @Test
+    void testContextDataAfterProceed() throws NoSuchMethodException {
+        WebTarget webTarget = Mockito.mock(WebTarget.class);
+        MethodModel methodModel = Mockito.mock(MethodModel.class);
+        InvocationInterceptor invocationInterceptor = Mockito.mock(InvocationInterceptor.class);
+        Mockito.when(methodModel.getInvocationInterceptors())
+                .thenReturn(Collections.singletonList(invocationInterceptor));
+
+        Method method = getClass().getMethod("method");
+        InterceptorInvocationContext c = new InterceptorInvocationContext(
+                webTarget, methodModel, method, new Object[]{});
+
+        Assertions.assertEquals(0, c.getContextData().size());
+        c.proceed();
+        Assertions.assertEquals(1, c.getContextData().size());
+        Assertions.assertTrue(c.getContextData().containsKey(InterceptorInvocationContext.class.getName()));
+    }
+
+    public static Object method() {
+        return null;
+    }
+}
diff --git a/pom.xml b/pom.xml
index 7f852ea..62ef050 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1749,6 +1749,16 @@
                 <artifactId>jetty-webapp</artifactId>
                 <version>${jetty.version}</version>
             </dependency>
+            <dependency>
+                <groupId>org.eclipse.jetty.http2</groupId>
+                <artifactId>http2-server</artifactId>
+                <version>${jetty.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.eclipse.jetty</groupId>
+                <artifactId>jetty-alpn-conscrypt-server</artifactId>
+                <version>${jetty.version}</version>
+            </dependency>
 
             <dependency>
                 <groupId>org.simpleframework</groupId>
@@ -2228,7 +2238,7 @@
         <arquillian.weld.version>3.0.1.Final</arquillian.weld.version>
         <!-- asm is now source integrated - keeping this property to see the version -->
         <!-- see core-server/src/main/java/jersey/repackaged/asm/.. -->
-        <asm.version>9.4</asm.version>
+        <asm.version>9.5</asm.version>
         <bnd.plugin.version>2.3.6</bnd.plugin.version>
 
         <bouncycastle.version>1.68</bouncycastle.version>
@@ -2340,7 +2350,7 @@
         <jaxrs.api.spec.version>3.1</jaxrs.api.spec.version>
         <jaxrs.api.impl.version>3.1.0</jaxrs.api.impl.version>
         <jetty.osgi.version>org.eclipse.jetty.*;version="[11,15)"</jetty.osgi.version>
-        <jetty.version>11.0.14</jetty.version>
+        <jetty.version>11.0.15</jetty.version>
         <jetty9.version>9.4.51.v20230217</jetty9.version>
         <jetty.plugin.version>11.0.14</jetty.plugin.version>
         <jsonb.api.version>3.0.0</jsonb.api.version>
diff --git a/test-framework/providers/jetty-http2/pom.xml b/test-framework/providers/jetty-http2/pom.xml
new file mode 100644
index 0000000..46a156c
--- /dev/null
+++ b/test-framework/providers/jetty-http2/pom.xml
@@ -0,0 +1,46 @@
+<?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>
+
+    <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>
+</project>
diff --git a/test-framework/providers/jetty-http2/src/main/java/org/glassfish/jersey/test/jetty/http2/JettyHttp2TestContainerFactory.java b/test-framework/providers/jetty-http2/src/main/java/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/java/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/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..cd4980f
--- /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 HTTP/2 Container.
+ */
+package org.glassfish.jersey.test.jetty.http2;
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..42b7847
--- /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
\ No newline at end of file
diff --git a/test-framework/providers/jetty-http2/src/main/resources/org/glassfish/jersey/test/jetty/http2/localization.properties b/test-framework/providers/jetty-http2/src/main/resources/org/glassfish/jersey/test/jetty/http2/localization.properties
new file mode 100644
index 0000000..2886c72
--- /dev/null
+++ b/test-framework/providers/jetty-http2/src/main/resources/org/glassfish/jersey/test/jetty/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 11.
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..1964156
--- /dev/null
+++ b/test-framework/providers/jetty-http2/src/test/java/org/glassfish/jersey/test/jetty/http2/AvailablePortJettyTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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.jetty.http2.JettyHttp2TestContainerFactory;
+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/pom.xml b/test-framework/providers/pom.xml
index f271da3..3098913 100644
--- a/test-framework/providers/pom.xml
+++ b/test-framework/providers/pom.xml
@@ -40,6 +40,7 @@
         <module>inmemory</module>
         <module>jdk-http</module>
         <module>jetty</module>
+        <module>jetty-http2</module>
         <module>netty</module>
         <module>simple</module>
     </modules>
diff --git a/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/BufferingTest.java b/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/BufferingTest.java
index d71e996..5762179 100644
--- a/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/BufferingTest.java
+++ b/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/BufferingTest.java
@@ -38,6 +38,7 @@
 import org.glassfish.jersey.grizzly.connector.GrizzlyConnectorProvider;
 import org.glassfish.jersey.jdk.connector.JdkConnectorProvider;
 import org.glassfish.jersey.logging.LoggingFeature;
+import org.glassfish.jersey.netty.connector.NettyConnectorProvider;
 import org.glassfish.jersey.server.ResourceConfig;
 import org.glassfish.jersey.test.JerseyTest;
 
@@ -78,6 +79,7 @@
                 Arguments.of(new TestArguments(() -> new ApacheConnectorProvider(), RequestEntityProcessing.CHUNKED)),
                 Arguments.of(new TestArguments(() -> new Apache5ConnectorProvider(), RequestEntityProcessing.CHUNKED)),
                 Arguments.of(new TestArguments(() -> new GrizzlyConnectorProvider(), RequestEntityProcessing.CHUNKED)),
+                Arguments.of(new TestArguments(() -> new NettyConnectorProvider(), RequestEntityProcessing.CHUNKED)),
                 Arguments.of(new TestArguments(() -> new HttpUrlConnectorProvider(), RequestEntityProcessing.BUFFERED)),
                 Arguments.of(new TestArguments(() -> new JdkConnectorProvider(), RequestEntityProcessing.BUFFERED))
         );
diff --git a/tests/e2e-core-common/src/test/java/org/glassfish/jersey/tests/e2e/common/message/internal/OutboundMessageContextTest.java b/tests/e2e-core-common/src/test/java/org/glassfish/jersey/tests/e2e/common/message/internal/OutboundMessageContextTest.java
index 29a3cb3..bd6e3c5 100644
--- a/tests/e2e-core-common/src/test/java/org/glassfish/jersey/tests/e2e/common/message/internal/OutboundMessageContextTest.java
+++ b/tests/e2e-core-common/src/test/java/org/glassfish/jersey/tests/e2e/common/message/internal/OutboundMessageContextTest.java
@@ -20,6 +20,7 @@
 import java.net.URISyntaxException;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
+import java.util.Collections;
 import java.util.Date;
 import java.util.List;
 import java.util.Locale;
@@ -237,6 +238,33 @@
     }
 
     @Test
+    public void testChangedContentTypeOnList() {
+        OutboundMessageContext ctx = new OutboundMessageContext((Configuration) null);
+        ctx.setMediaType(MediaType.APPLICATION_XML_TYPE);
+        Assertions.assertEquals(MediaType.APPLICATION_XML_TYPE, ctx.getMediaType());
+        ctx.getHeaders().get(HttpHeaders.CONTENT_TYPE).set(0, MediaType.APPLICATION_JSON);
+        Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE, ctx.getMediaType());
+    }
+
+    @Test
+    public void testChangedContentTypeOnValues() {
+        OutboundMessageContext ctx = new OutboundMessageContext((Configuration) null);
+        ctx.setMediaType(MediaType.APPLICATION_XML_TYPE);
+        Assertions.assertEquals(MediaType.APPLICATION_XML_TYPE, ctx.getMediaType());
+        ctx.getHeaders().values().clear();
+        Assertions.assertEquals(null, ctx.getMediaType());
+    }
+
+    @Test
+    public void testChangedContentTypeOnEntrySet() {
+        OutboundMessageContext ctx = new OutboundMessageContext((Configuration) null);
+        ctx.setMediaType(MediaType.APPLICATION_XML_TYPE);
+        Assertions.assertEquals(MediaType.APPLICATION_XML_TYPE, ctx.getMediaType());
+        ctx.getHeaders().entrySet().clear();
+        Assertions.assertEquals(null, ctx.getMediaType());
+    }
+
+    @Test
     public void testCopyConstructor() {
         OutboundMessageContext ctx = new OutboundMessageContext((Configuration) null);
         OutboundMessageContext newCtx = new OutboundMessageContext(ctx);
diff --git a/tests/integration/microprofile/rest-client/src/test/java/org/glassfish/jersey/restclient/PathParamTest.java b/tests/integration/microprofile/rest-client/src/test/java/org/glassfish/jersey/restclient/PathParamTest.java
new file mode 100644
index 0000000..3975cd4
--- /dev/null
+++ b/tests/integration/microprofile/rest-client/src/test/java/org/glassfish/jersey/restclient/PathParamTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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.restclient;
+
+import org.eclipse.microprofile.rest.client.RestClientBuilder;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import jakarta.json.Json;
+import jakarta.json.JsonBuilderFactory;
+import jakarta.json.JsonObject;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.Application;
+import jakarta.ws.rs.core.MediaType;
+import java.net.URI;
+import java.util.Collections;
+
+public class PathParamTest extends JerseyTest {
+    @Path("/greet")
+    public static class GreetResource {
+        private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap());
+
+        @GET
+        @Produces(MediaType.APPLICATION_JSON)
+        @Path("/everything/everywhere")
+        public JsonObject getDefaultMessage() {
+            return createResponse("World");
+        }
+
+        private JsonObject createResponse(String who) {
+            String msg = String.format("%s %s!", "Hello", who);
+
+            return JSON.createObjectBuilder()
+                    .add("message", msg)
+                    .build();
+        }
+    }
+
+    @Path("/greet")
+    public static interface GreetResourceClient {
+
+        @GET
+        @Produces(MediaType.APPLICATION_JSON)
+        @Path("{folderpath}")
+        JsonObject getDefaultMessage(@PathParam("folderpath") String folderPath);
+    }
+
+    @Override
+    protected Application configure() {
+        return new ResourceConfig(GreetResource.class);
+    }
+
+    @Test
+    public void testRestClientRequest() {
+        GreetResourceClient client = RestClientBuilder.newBuilder()
+                .baseUri(URI.create("http://localhost:9998")).build(GreetResourceClient.class);
+        JsonObject jsonObject = client.getDefaultMessage("everything/everywhere");
+        Assertions.assertEquals("Hello World!", jsonObject.getString("message"));
+    }
+
+    @Test
+    public void testStandardRequest() {
+        JsonObject jsonObject = target()
+                .path("greet").path("everything").path("everywhere")
+                .request()
+                .get(JsonObject.class);
+        Assertions.assertEquals("Hello World!", jsonObject.getString("message"));
+    }
+}
diff --git a/tests/pom.xml b/tests/pom.xml
index 901478f..6b35eba 100644
--- a/tests/pom.xml
+++ b/tests/pom.xml
@@ -121,6 +121,7 @@
             </activation>
             <modules>
                 <module>release-test</module>
+                <module>version-agnostic</module>
             </modules>
         </profile>
     </profiles>
diff --git a/tests/version-agnostic/pom.xml b/tests/version-agnostic/pom.xml
new file mode 100644
index 0000000..b711a14
--- /dev/null
+++ b/tests/version-agnostic/pom.xml
@@ -0,0 +1,129 @@
+<?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>
+        <groupId>org.eclipse.ee4j</groupId>
+        <artifactId>project</artifactId>
+        <version>1.0.8</version>
+        <relativePath>../../../pom.xml</relativePath>
+    </parent>
+
+    <groupId>org.glassfish.jersey.tests</groupId>
+    <artifactId>version-agnostic</artifactId>
+    <packaging>jar</packaging>
+    <name>jersey-agnostic-test</name>
+
+    <description>Jersey post-release validation tests</description>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+        <hk2.version>3.0.3</hk2.version>
+        <jersey.version>3.0.99-SNAPSHOT</jersey.version>
+    </properties>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <version>3.0.0-M7</version>
+                <configuration>
+                    <forkCount>1</forkCount>
+                    <reuseForks>false</reuseForks>
+                    <enableAssertions>false</enableAssertions>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.glassfish.copyright</groupId>
+                <artifactId>glassfish-copyright-maven-plugin</artifactId>
+                <version>2.4</version>
+                <configuration>
+                    <excludeFile>etc/config/copyright-exclude</excludeFile>
+                    <!--svn|mercurial|git - defaults to svn-->
+                    <scm>git</scm>
+                    <!-- turn on/off debugging -->
+                    <debug>false</debug>
+                    <!-- skip files not under SCM-->
+                    <scmOnly>true</scmOnly>
+                    <!-- turn off warnings -->
+                    <warn>false</warn>
+                    <!-- for use with repair -->
+                    <update>false</update>
+                    <!-- check that year is correct -->
+                    <ignoreYear>false</ignoreYear>
+                    <templateFile>etc/config/copyright.txt</templateFile>
+                    <bsdTemplateFile>etc/config/edl-copyright.txt</bsdTemplateFile>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.glassfish.jersey</groupId>
+                <artifactId>jersey-bom</artifactId>
+                <version>${jersey.version}</version>
+                <scope>import</scope>
+                <type>pom</type>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+            <artifactId>jersey-test-framework-provider-bundle</artifactId>
+            <type>pom</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.jersey.connectors</groupId>
+            <artifactId>jersey-jetty-connector</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.glassfish.jersey.inject</groupId>
+            <artifactId>jersey-hk2</artifactId>
+            <version>${jersey.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.hk2</groupId>
+            <artifactId>hk2-api</artifactId>
+            <version>${hk2.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.hk2</groupId>
+            <artifactId>hk2-utils</artifactId>
+            <version>${hk2.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.hk2</groupId>
+            <artifactId>hk2-locator</artifactId>
+            <version>${hk2.version}</version>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/tests/version-agnostic/src/test/java/org/glassfish/jersey/test/agnostic/JettyHeaderTest.java b/tests/version-agnostic/src/test/java/org/glassfish/jersey/test/agnostic/JettyHeaderTest.java
new file mode 100644
index 0000000..01187a2
--- /dev/null
+++ b/tests/version-agnostic/src/test/java/org/glassfish/jersey/test/agnostic/JettyHeaderTest.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.test.agnostic;
+
+import jakarta.ws.rs.GET;
+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 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.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.logging.Level;
+
+public class JettyHeaderTest extends JerseyTest {
+    @Path("/")
+    public static class JettyHeaderTestResource {
+        @Context
+        HttpHeaders httpHeaders;
+
+        @GET
+        public String get() {
+            return httpHeaders.getHeaderString(HttpHeaders.USER_AGENT);
+        }
+    }
+
+    @Override
+    protected Application configure() {
+        return new ResourceConfig(JettyHeaderTestResource.class);
+                //.register(LoggingFeature.builder().level(Level.INFO).verbosity(LoggingFeature.Verbosity.PAYLOAD_ANY).build());
+    }
+
+    @Override
+    protected void configureClient(ClientConfig config) {
+        config.connectorProvider(new JettyConnectorProvider());
+    }
+
+    @Test
+    public void helloWorldTest() {
+        try (Response r = target().request().get()) {
+            Assertions.assertEquals(200, r.getStatus());
+
+            int index = -1;
+            String userAgent = r.readEntity(String.class);
+            index = userAgent.indexOf("Jersey");
+            Assertions.assertTrue(index != -1);
+
+            index = userAgent.indexOf("Jersey", index + 1);
+            Assertions.assertEquals(-1, index);
+        }
+    }
+}