merge release history into 3.x

diff --git a/containers/grizzly2-http/pom.xml b/containers/grizzly2-http/pom.xml
index 49e61f9..4969da9 100644
--- a/containers/grizzly2-http/pom.xml
+++ b/containers/grizzly2-http/pom.xml
@@ -17,7 +17,8 @@
 
 -->
 
-<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/maven-v4_0_0.xsd">
+<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/maven-v4_0_0.xsd">
     <modelVersion>4.0.0</modelVersion>
 
     <parent>
@@ -32,6 +33,14 @@
 
     <description>Grizzly 2 Http Container.</description>
 
+    <properties>
+        <!-- alternatives: JerseyHttpClientThread, JettyClientThread in the same package -->
+        <client>JdkHttpClientThread</client>
+        <clientImplPackage>org.glassfish.jersey.grizzly2.httpserver.test.tools</clientImplPackage>
+        <clientImpl>${clientImplPackage}.${client}</clientImpl>
+        <testMemory>-Xms160m -Xmx160m -Xss512k</testMemory>
+    </properties>
+
     <dependencies>
         <dependency>
             <groupId>jakarta.inject</groupId>
@@ -41,6 +50,40 @@
             <groupId>org.glassfish.grizzly</groupId>
             <artifactId>grizzly-http-server</artifactId>
         </dependency>
+
+        <dependency>
+            <groupId>org.glassfish.grizzly</groupId>
+            <artifactId>grizzly-http2</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.grizzly</groupId>
+            <artifactId>grizzly-npn-api</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-client</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty.http2</groupId>
+            <artifactId>http2-http-client-transport</artifactId>
+            <version>${jetty.version}</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcprov-jdk15on</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcmail-jdk15on</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>
@@ -60,7 +103,87 @@
                 <artifactId>maven-bundle-plugin</artifactId>
                 <inherited>true</inherited>
             </plugin>
+
+            <plugin>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <!--
+                    memory settings:
+                      - to avoid unstable JVM process; fail fast, don't produce dump
+                      - memory sizes must reflect count of clients!
+                    test time:
+                      - must not affect stability of the server; if it does, the test detected a bug
+                      - 10 seconds should be enough to process thousands of requests, if there would
+                        be some leak, it should be visible
+                    client count:
+                      - each client is reusable to produce parallel requests, but each also consumes
+                        significiant amount of memory, in this case shared with the server.
+                    -->
+                    <argLine>
+                        ${testMemory} -XX:+CrashOnOutOfMemoryError -XX:-HeapDumpOnOutOfMemoryError
+                        -DtestTime=10 -DclientCount=30
+                        -DclientImpl=${clientImpl}
+                    </argLine>
+                    <trimStackTrace>false</trimStackTrace>
+                    <parallel>methods</parallel>
+                    <threadCountMethods>3</threadCountMethods>
+                </configuration>
+            </plugin>
         </plugins>
     </build>
 
+    <profiles>
+        <profile>
+            <id>jdk11</id>
+            <activation>
+                <jdk>[11,)</jdk>
+            </activation>
+            <build>
+                <pluginManagement>
+                    <plugins>
+                        <plugin>
+                            <artifactId>maven-compiler-plugin</artifactId>
+                            <executions>
+                                <execution>
+                                    <id>default-testCompile</id>
+                                    <configuration>
+                                        <source>11</source>
+                                        <target>11</target>
+                                    </configuration>
+                                </execution>
+                            </executions>
+                        </plugin>
+                    </plugins>
+                </pluginManagement>
+            </build>
+        </profile>
+        <profile>
+            <id>jdk8</id>
+            <activation>
+                <jdk>1.8</jdk>
+            </activation>
+            <build>
+                <pluginManagement>
+                    <plugins>
+                        <plugin>
+                            <artifactId>maven-compiler-plugin</artifactId>
+                            <executions>
+                                <execution>
+                                    <id>default-testCompile</id>
+                                    <configuration>
+                                        <!--
+                                        Jetty client is not compatible with JDK8, older versions need different setup
+                                        JDK HTTP client is not in JDK8 at all
+                                        Jersey Client doesn't support HTTP/2 (at least not directly)
+                                        -->
+                                        <skip>true</skip>
+                                    </configuration>
+                                </execution>
+                            </executions>
+                        </plugin>
+                    </plugins>
+                </pluginManagement>
+            </build>
+        </profile>
+    </profiles>
 </project>
diff --git a/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServerTest.java b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServerTest.java
new file mode 100644
index 0000000..5fb5e08
--- /dev/null
+++ b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServerTest.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (c) 2021 Payara Foundation 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.grizzly2.httpserver;
+
+import java.lang.reflect.Constructor;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.glassfish.jersey.grizzly2.httpserver.test.tools.ClientThread;
+import org.glassfish.jersey.grizzly2.httpserver.test.tools.ClientThread.ClientThreadSettings;
+import org.glassfish.jersey.grizzly2.httpserver.test.tools.JdkHttpClientThread;
+import org.glassfish.jersey.grizzly2.httpserver.test.tools.ServerManager;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+
+/**
+ * Test verifying stability of the {@link GrizzlyHttpContainer} having to serve many requests
+ * and also giving several examples of how to configure HTTP, HTTPS and HTTP/2 clients.
+ * <p>
+ * Created as an attempt to reproduce Grizzly's issue #2125 (GitHub)
+ *
+ * @author David Matejcek
+ */
+public class GrizzlyHttpServerTest {
+
+    private static final String CLIENT_IMPLEMENTATION = System.getProperty("clientImpl",
+        JdkHttpClientThread.class.getCanonicalName());
+    private static final long TIME_IN_MILLIS = Long.getLong("testTime", 10L) * 1000L;
+    private static final int COUNT_OF_CLIENTS = Integer.getInteger("clientCount", 20);
+
+    private final List<ClientThread> clients = new ArrayList<>(COUNT_OF_CLIENTS);
+    private AtomicReference<Throwable> error = new AtomicReference<>();
+    private AtomicInteger counter;
+    private ServerManager server;
+
+    @Rule
+    public TestName testName = new TestName();
+
+    @BeforeClass
+    public static void printSettings() {
+        System.out.println("Client implementation: " + CLIENT_IMPLEMENTATION);
+        System.out.println("Count of clients: " + COUNT_OF_CLIENTS);
+        System.out.println("Test duration: " + TIME_IN_MILLIS / 1000 + " s");
+    }
+
+
+    @Before
+    public void init() {
+        this.counter = new AtomicInteger();
+    }
+
+
+    @After
+    public void cleanup() {
+        error = null;
+        System.out.println(String.format("Server processed %s requests of test %s.", counter, testName.getMethodName()));
+        if (server != null) {
+            server.close();
+        }
+    }
+
+
+    /**
+     * Test for unsecured HTTP 1.1 protocol.
+     *
+     * @throws Throwable
+     */
+    @Test
+    public void http() throws Throwable {
+        final boolean secured = false;
+        final boolean useHttp2 = false;
+        this.server = new ServerManager(secured, useHttp2);
+        this.clients.addAll(createClients(secured, useHttp2));
+        executeTest();
+    }
+
+
+    /**
+     * Test for HTTP 1.1 protocol encrypted by {@value ClientThread#ENCRYPTION_PROTOCOL}.
+     *
+     * @throws Throwable
+     */
+    @Test
+    public void https() throws Throwable {
+        final boolean secured = true;
+        final boolean useHttp2 = false;
+        this.server = new ServerManager(secured, useHttp2);
+        this.clients.addAll(createClients(secured, useHttp2));
+        executeTest();
+    }
+
+
+    /**
+     * This test is rather for documentaion purpose, because HTTP/2 is usually not allowed to be
+     * used without encryption. Remember that.
+     *
+     * @throws Throwable
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void http2() throws Throwable {
+        this.server = new ServerManager(false, true);
+    }
+
+
+    /**
+     * Test for HTTP/2 protocol encrypted by {@value ClientThread#ENCRYPTION_PROTOCOL}.
+     *
+     * @throws Throwable
+     */
+    @Test
+    public void https2() throws Throwable {
+        final boolean secured = true;
+        final boolean useHttp2 = true;
+        this.server = new ServerManager(secured, useHttp2);
+        this.clients.addAll(createClients(secured, useHttp2));
+        executeTest();
+    }
+
+
+    private void executeTest() throws Throwable {
+        for (final ClientThread clientThread : clients) {
+            clientThread.start();
+        }
+        final long start = System.currentTimeMillis();
+        while (error.get() == null && System.currentTimeMillis() < start + TIME_IN_MILLIS) {
+            Thread.yield();
+        }
+        for (final ClientThread clientThread : clients) {
+            clientThread.stopClient();
+        }
+        for (final ClientThread clientThread : clients) {
+            // cycles are fast, so we can afford this.
+            clientThread.join(100L);
+        }
+        if (error.get() != null) {
+            throw error.get();
+        }
+        assertTrue("No requests processed.", counter.get() > 0);
+    }
+
+
+    private Collection<ClientThread> createClients(final boolean secured, final boolean useHttp2) throws Exception {
+        final List<ClientThread> list = new ArrayList<>(COUNT_OF_CLIENTS);
+        for (int i = 0; i < COUNT_OF_CLIENTS; i++) {
+            list.add(createClient(secured, useHttp2, i + 1));
+        }
+        return list;
+    }
+
+
+    private ClientThread createClient(final boolean secured, final boolean useHttp2, final int id) throws Exception {
+        @SuppressWarnings("unchecked")
+        final Class<ClientThread> clazz = (Class<ClientThread>) Class.forName(CLIENT_IMPLEMENTATION);
+        final Constructor<ClientThread> constructor = clazz.getConstructor(ClientThreadSettings.class,
+            AtomicInteger.class, AtomicReference.class);
+        assertNotNull("constructor for " + CLIENT_IMPLEMENTATION, constructor);
+        final ClientThreadSettings settings = new ClientThreadSettings(id, secured, useHttp2,
+            server.getApplicationServiceEndpoint());
+        return constructor.newInstance(settings, counter, error);
+    }
+}
diff --git a/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/application/TestedEndpoint.java b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/application/TestedEndpoint.java
new file mode 100644
index 0000000..3ad7fb8
--- /dev/null
+++ b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/application/TestedEndpoint.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2021 Payara Foundation 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.grizzly2.httpserver.test.application;
+
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+
+@Path("tested-endpoint")
+public class TestedEndpoint {
+
+    @GET
+    @Produces(MediaType.TEXT_PLAIN)
+    public String getIt() {
+        return "Got it!";
+    }
+}
diff --git a/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/ClientThread.java b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/ClientThread.java
new file mode 100644
index 0000000..2b11fab
--- /dev/null
+++ b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/ClientThread.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 2021 Payara Foundation 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.grizzly2.httpserver.test.tools;
+
+import java.net.URI;
+import java.security.GeneralSecurityException;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+
+/**
+ * Why this? To simulate parallel client access - several clients intensively sending requests
+ * and verifying responses.
+ *
+ * @author David Matejcek
+ */
+public abstract class ClientThread extends Thread {
+
+    /** Encryption used if you enable secured communication */
+    public static final String ENCRYPTION_PROTOCOL = "TLSv1.2";
+
+    private final ClientThreadSettings settings;
+    private final AtomicInteger counter;
+    private final AtomicReference<Throwable> error;
+    private volatile boolean stop;
+
+
+
+    public ClientThread(final ClientThreadSettings settings, final AtomicInteger counter,
+        final AtomicReference<Throwable> error) throws Exception {
+        this.settings = settings;
+        this.counter = counter;
+        this.error = error;
+        setName("client-" + settings.id);
+        setUncaughtExceptionHandler((t, e) -> {
+            stop = true;
+            error.compareAndSet(null, e);
+        });
+    }
+
+
+    /**
+     * @return {@link ClientThreadSettings}
+     */
+    protected final ClientThreadSettings getSettings() {
+        return this.settings;
+    }
+
+
+    /**
+     * Executes the requests and checks the response.
+     *
+     * @throws Throwable
+     */
+    protected abstract void doGetAndCheckResponse() throws Throwable;
+
+
+    /**
+     * If the client is stateful, override this method.
+     */
+    protected void disconnect() {
+        // by default nothing to do.
+    }
+
+
+    /**
+     * Instructs the client thread to stop when possible.
+     */
+    public void stopClient() {
+        this.stop = true;
+    }
+
+
+    @Override
+    public final void run() {
+        try {
+            // stop when asked to stop or any "brother" thread observed throwable
+            while (!stop && error.get() == null) {
+                doGetAndCheckResponse();
+                counter.incrementAndGet();
+            }
+        } catch (final Throwable t) {
+            throw new IllegalStateException("The client thread failed: " + getName(), t);
+        } finally {
+            disconnect();
+        }
+    }
+
+
+    /**
+     * @return Trivial {@link SSLContext}, accepting all certificates and host names
+     * @throws GeneralSecurityException
+     */
+    protected static SSLContext createSslContext() throws GeneralSecurityException {
+        final SSLContext ctx = SSLContext.getInstance(ENCRYPTION_PROTOCOL);
+        ctx.init(null, new TrustManager[] {new NaiveTrustManager()}, null);
+        return ctx;
+    }
+
+    /**
+     * Simplified configuration of the client thread.
+     */
+    public static class ClientThreadSettings {
+
+        /** Id of the client thread */
+        public final int id;
+        /** True if the connection will be encrypted */
+        public final boolean secured;
+        /** True if the protocol should be HTTP/2, false for HTTP 1.1 */
+        public final boolean useHttp2;
+        /** The endpoint {@link URI} of the servlet */
+        public final URI targetUri;
+
+        /**
+         * Creates simplified configuration of the client thread.
+         *
+         * @param id id of the client thread
+         * @param secured true if the connection will be encrypted
+         * @param useHttp2 true if the protocol should be HTTP/2, false for HTTP 1.1
+         * @param targetUri the endpoint {@link URI} of the servlet
+         */
+        public ClientThreadSettings(final int id, final boolean secured, final boolean useHttp2, final URI targetUri) {
+            this.id = id;
+            this.secured = secured;
+            this.useHttp2 = useHttp2;
+            this.targetUri = targetUri;
+        }
+    }
+}
diff --git a/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/JdkHttpClientThread.java b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/JdkHttpClientThread.java
new file mode 100644
index 0000000..8e9d543
--- /dev/null
+++ b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/JdkHttpClientThread.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2021 Payara Foundation 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.grizzly2.httpserver.test.tools;
+
+import java.net.http.HttpClient;
+import java.net.http.HttpClient.Version;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.glassfish.grizzly.http.util.Header;
+
+import jakarta.ws.rs.core.MediaType;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * JDK11+ has it's own {@link HttpClient} implementation supporting both HTTP 1.1 and HTTP/2.
+ *
+ * @author David Matejcek
+ */
+public class JdkHttpClientThread extends ClientThread {
+    private final HttpClient client;
+
+    public JdkHttpClientThread(final ClientThreadSettings settings,
+        final AtomicInteger counter, final AtomicReference<Throwable> error) throws Exception {
+        super(settings, counter, error);
+        this.client = createClient(settings.secured, settings.useHttp2);
+    }
+
+
+    @Override
+    public void doGetAndCheckResponse() throws Throwable {
+        final HttpRequest request = HttpRequest.newBuilder(getSettings().targetUri)
+            .header(Header.ContentType.toString(), MediaType.TEXT_PLAIN).GET().build();
+        final HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
+        assertEquals(200, response.statusCode());
+        assertEquals("Got it!", response.body());
+    }
+
+
+    private static HttpClient createClient(final boolean secured, final boolean useHttp2) throws Exception {
+        final HttpClient.Builder builder = HttpClient.newBuilder()
+            .version(useHttp2 ? Version.HTTP_2 : Version.HTTP_1_1);
+        if (secured) {
+            builder.sslContext(createSslContext());
+        }
+        return builder.build();
+    }
+}
diff --git a/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/JerseyHttpClientThread.java b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/JerseyHttpClientThread.java
new file mode 100644
index 0000000..dcf359f
--- /dev/null
+++ b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/JerseyHttpClientThread.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2021 Payara Foundation 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.grizzly2.httpserver.test.tools;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.glassfish.jersey.client.ClientConfig;
+
+import jakarta.ws.rs.client.Client;
+import jakarta.ws.rs.client.ClientBuilder;
+import jakarta.ws.rs.client.Invocation.Builder;
+import jakarta.ws.rs.client.WebTarget;
+import jakarta.ws.rs.core.Response;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Jersey doesn't support HTTP/2 at this moment, but this class may be extended later.
+ * Even this way it passes all tests, because server is still able to use HTTP 1.1 despite we
+ * configured it to use HTTP/2
+ *
+ * @author David Matejcek
+ */
+public class JerseyHttpClientThread extends ClientThread {
+
+    private final Client client;
+
+    public JerseyHttpClientThread(final ClientThreadSettings settings,
+        final AtomicInteger counter, final AtomicReference<Throwable> error) throws Exception {
+        super(settings, counter, error);
+        this.client = createClient(settings.secured, settings.useHttp2);
+    }
+
+
+    @Override
+    public void doGetAndCheckResponse() throws Throwable {
+        final WebTarget path = client.target(getSettings().targetUri.toString());
+        final Builder builder = path.request();
+        final Response response = builder.get();
+        final String responseMsg = response.readEntity(String.class);
+        assertEquals(200, response.getStatus());
+        assertEquals("Got it!", responseMsg);
+    }
+
+
+    private static Client createClient(final boolean secured, final boolean useHttp2) throws Exception {
+        final ClientBuilder builder = ClientBuilder.newBuilder().withConfig(new ClientConfig());
+        if (secured) {
+            builder.sslContext(createSslContext());
+        }
+        return builder.build();
+    }
+}
diff --git a/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/JettyHttpClientThread.java b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/JettyHttpClientThread.java
new file mode 100644
index 0000000..171d534
--- /dev/null
+++ b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/JettyHttpClientThread.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2021 Payara Foundation 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.grizzly2.httpserver.test.tools;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.HttpClientTransport;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
+import org.eclipse.jetty.http2.client.HTTP2Client;
+import org.eclipse.jetty.http2.client.http.HttpClientTransportOverHTTP2;
+import org.eclipse.jetty.io.ClientConnector;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Jetty {@link HttpClient} supports both HTTP 1.1 and HTTP/2.
+ *
+ * @author David Matejcek
+ */
+public class JettyHttpClientThread extends ClientThread {
+
+    private final HttpClient client;
+
+    public JettyHttpClientThread(final ClientThreadSettings settings, final AtomicInteger counter,
+        final AtomicReference<Throwable> error) throws Exception {
+        super(settings, counter, error);
+        this.client = createJettyClient(settings.secured, settings.useHttp2);
+    }
+
+
+    @Override
+    protected void disconnect() {
+        try {
+            this.client.stop();
+        } catch (Exception e) {
+            throw new IllegalStateException("Could not stop the client: " + getName(), e);
+        }
+    }
+
+    @Override
+    public void doGetAndCheckResponse() throws Throwable {
+        final ContentResponse response = this.client.GET(getSettings().targetUri);
+        assertEquals(200, response.getStatus());
+        assertEquals("Got it!", response.getContentAsString());
+    }
+
+
+    private static HttpClient createJettyClient(final boolean secured, final boolean useHttp2) throws Exception {
+        if (!secured && !useHttp2) {
+            final HttpClient httpClient = new HttpClient();
+            httpClient.start();
+            return httpClient;
+        }
+
+        final SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
+        sslContextFactory.setSslContext(createSslContext());
+        final ClientConnector connector = new ClientConnector();
+        connector.setSslContextFactory(sslContextFactory);
+
+        final HttpClientTransport transport;
+        if (useHttp2) {
+            transport = new HttpClientTransportOverHTTP2(new HTTP2Client(connector));
+        } else {
+            transport = new HttpClientTransportOverHTTP(connector);
+        }
+
+        final HttpClient httpClient = new HttpClient(transport);
+        httpClient.start();
+        return httpClient;
+    }
+}
diff --git a/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/KeyStoreManager.java b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/KeyStoreManager.java
new file mode 100644
index 0000000..4cb8de3
--- /dev/null
+++ b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/KeyStoreManager.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2021 Payara Foundation 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.grizzly2.httpserver.test.tools;
+
+import java.io.ByteArrayOutputStream;
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.Date;
+import java.util.Random;
+
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.X509v3CertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+
+/**
+ * Creates a new keystore in memory.
+ * This keystore contains just a private key and a self-signed certificate valid for two days.
+ *
+ * @author David Matejcek
+ */
+public class KeyStoreManager {
+
+    private static final String KEYSTORE_PASSWORD = "";
+    private final KeyStore keyStore;
+    private final byte[] keyStoreBytes;
+
+    /**
+     * @param hostname - hostname, used for CN value of the self-signed certificate
+     */
+    public KeyStoreManager(final String hostname) {
+        try {
+            this.keyStore = KeyStore.getInstance("PKCS12");
+            this.keyStore.load(null);
+            this.keyStoreBytes = generatePrivateKeyAndCertificate(hostname, this.keyStore);
+        } catch (Exception e) {
+            throw new IllegalStateException("Could not initialize the keystore.", e);
+        }
+    }
+
+
+    /**
+     * @return {@link KeyStore}
+     */
+    public KeyStore getKeyStore() {
+        return this.keyStore;
+    }
+
+
+    /**
+     * @return the key store serialized to a byte array
+     */
+    public byte[] getKeyStoreBytes() {
+        return this.keyStoreBytes;
+    }
+
+
+    /**
+     * @return the key store password
+     */
+    public String getKeyStorePassword() {
+        return KEYSTORE_PASSWORD;
+    }
+
+
+    private static byte[] generatePrivateKeyAndCertificate(final String hostname, final KeyStore keyStore) {
+        try {
+            final KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
+            generator.initialize(2048, SecureRandom.getInstance("SHA1PRNG"));
+            final KeyPair keyPair = generator.generateKeyPair();
+            final PrivateKey privateKey = keyPair.getPrivate();
+            final PublicKey publicKey = keyPair.getPublic();
+
+            final BigInteger serial = new BigInteger(256, new Random(System.currentTimeMillis()));
+            final Instant validFrom = Instant.now().minusSeconds(60L);
+            final Instant validTo = validFrom.plus(2, ChronoUnit.DAYS);
+
+            final ASN1Sequence pubSeq = ASN1Sequence.getInstance(publicKey.getEncoded());
+            final SubjectPublicKeyInfo info = SubjectPublicKeyInfo.getInstance(pubSeq.getEncoded());
+            final X500Name name = new X500Name(
+                "CN=" + hostname + ", OU=Jersey Container, O=Eclipse Foundation, L=Brussels, ST=Belgium, C=BE");
+            final X509v3CertificateBuilder builder = new X509v3CertificateBuilder(
+                name, serial, Date.from(validFrom), Date.from(validTo), name, info);
+            final JcaContentSignerBuilder signerBuilder = new JcaContentSignerBuilder("SHA512withRSA");
+            final ContentSigner signer = signerBuilder.build(privateKey);
+
+            final X509CertificateHolder cHolder = builder.build(signer);
+            final X509Certificate certificate = new JcaX509CertificateConverter().getCertificate(cHolder);
+
+            keyStore.setKeyEntry(hostname, privateKey, KEYSTORE_PASSWORD.toCharArray(),
+                new Certificate[] {certificate});
+            return toBytes(keyStore);
+        } catch (final Exception e) {
+            throw new IllegalStateException("Could not initialize the keystore", e);
+        }
+    }
+
+
+    private static byte[] toBytes(final KeyStore keyStore) throws Exception {
+        try (ByteArrayOutputStream os = new ByteArrayOutputStream(1024))  {
+            keyStore.store(os, new char[0]);
+            return os.toByteArray();
+        }
+    }
+}
diff --git a/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/NaiveTrustManager.java b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/NaiveTrustManager.java
new file mode 100644
index 0000000..85e265d
--- /dev/null
+++ b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/NaiveTrustManager.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2021 Payara Foundation 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.grizzly2.httpserver.test.tools;
+
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.X509TrustManager;
+
+
+/**
+ * This trust manager accepts all certificates.
+ * <p>
+ * <b>DO NOT USE ON PRODUCTION CODE!!!</b>
+ *
+ * @author David Matejcek
+ */
+public class NaiveTrustManager implements X509TrustManager {
+    @Override
+    public void checkClientTrusted(final X509Certificate[] chain, final String authType) throws CertificateException {
+        // Everyone is trusted!
+    }
+
+    @Override
+    public void checkServerTrusted(final X509Certificate[] chain, final String authType) throws CertificateException {
+        // Everyone is trusted!
+    }
+
+    @Override
+    public X509Certificate[] getAcceptedIssuers() {
+        return new X509Certificate[0];
+    }
+}
diff --git a/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/ServerManager.java b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/ServerManager.java
new file mode 100644
index 0000000..225e02d
--- /dev/null
+++ b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/test/tools/ServerManager.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (c) 2021 Payara Foundation 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.grizzly2.httpserver.test.tools;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.MalformedURLException;
+import java.net.ServerSocket;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.UnknownHostException;
+
+import org.glassfish.grizzly.http.server.HttpServer;
+import org.glassfish.grizzly.http.server.NetworkListener;
+import org.glassfish.grizzly.http2.Http2AddOn;
+import org.glassfish.grizzly.http2.Http2Configuration;
+import org.glassfish.grizzly.ssl.SSLContextConfigurator;
+import org.glassfish.grizzly.ssl.SSLEngineConfigurator;
+import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
+import org.glassfish.jersey.grizzly2.httpserver.test.application.TestedEndpoint;
+import org.glassfish.jersey.server.ResourceConfig;
+
+/**
+ * This manager maintains the lifecycle of the {@link HttpServer} and trivial rest application.
+ *
+ * @author David Matejcek
+ */
+public class ServerManager implements Closeable {
+
+    private static final String LISTENER_NAME_GRIZZLY = "grizzly";
+    private static final String PROTOCOL_HTTPS = "https";
+    private static final String PROTOCOL_HTTP = "http";
+    private static final String APPLICATION_CONTEXT = "/test-application";
+    private static final String SERVICE_CONTEXT = "/tested-endpoint";
+
+    private final URI endpointUri;
+    private final HttpServer server;
+
+
+    /**
+     * Initializes the server environment and starts the server.
+     *
+     * @param secured - set true to enable network encryption
+     * @param useHttp2 - set true to enable HTTP/2; then secured must be set to true too.
+     * @throws IOException
+     */
+    public ServerManager(final boolean secured, final boolean useHttp2) throws IOException {
+        this.endpointUri = getEndpointUri(secured, APPLICATION_CONTEXT);
+        final NetworkListener listener = createListener(secured, useHttp2, endpointUri.getHost(), endpointUri.getPort());
+        final ResourceConfig resourceConfig = createResourceConfig();
+        this.server = startServer(listener, this.endpointUri, resourceConfig);
+    }
+
+
+    /**
+     * @return {@link URI} of the application endpoint.
+     */
+    public URI getApplicationEndpoint() {
+        return this.endpointUri;
+    }
+
+
+    /**
+     * @return {@link URI} of the deployed service endpoint.
+     */
+    public URI getApplicationServiceEndpoint() {
+        return URI.create(getApplicationEndpoint() + SERVICE_CONTEXT);
+    }
+
+
+    /**
+     * Calls the {@link HttpServer#shutdownNow()}. The server and all it's resources are destroyed.
+     */
+    @Override
+    public void close() {
+        if (server != null) {
+            server.shutdownNow();
+        }
+    }
+
+
+    private static URI getEndpointUri(final boolean secured, final String applicationContext) {
+        try {
+            final String protocol = secured ? PROTOCOL_HTTPS : PROTOCOL_HTTP;
+            return new URL(protocol, getLocalhost(), getFreePort(), applicationContext).toURI();
+        } catch (MalformedURLException | URISyntaxException e) {
+            throw new IllegalStateException("Unable to create an endpoint URI.", e);
+        }
+    }
+
+
+    private static String getLocalhost() {
+        try {
+            return InetAddress.getLocalHost().getHostName();
+        } catch (final UnknownHostException e) {
+            return "localhost";
+        }
+    }
+
+
+    /**
+     * Tries to alocate a free local port.
+     *
+     * @return a free local port number.
+     * @throws IllegalStateException if it fails for 20 times
+     */
+    private static int getFreePort() {
+        int attempts = 0;
+        while (true) {
+            attempts++;
+            try (ServerSocket socket = new ServerSocket(0)) {
+                final int port = socket.getLocalPort();
+                socket.setSoTimeout(1);
+                socket.setReuseAddress(true);
+                return port;
+            } catch (final IOException e) {
+                if (attempts >= 20) {
+                    throw new IllegalStateException("Cannot open random port, tried 20 times.", e);
+                }
+            }
+        }
+    }
+
+
+    private static NetworkListener createListener(final boolean secured, final boolean useHttp2, final String host,
+        final int port) {
+        if (useHttp2 && !secured) {
+            throw new IllegalArgumentException("HTTP/2 cannot be used without encryption");
+        }
+        final NetworkListener listener = new NetworkListener(LISTENER_NAME_GRIZZLY, host, port);
+        listener.setSecure(secured);
+        if (secured) {
+            listener.setSSLEngineConfig(createSSLEngineConfigurator(host));
+        }
+        if (useHttp2) {
+            listener.registerAddOn(createHttp2AddOn());
+        }
+        return listener;
+    }
+
+
+    private static SSLEngineConfigurator createSSLEngineConfigurator(final String host) {
+        final KeyStoreManager keyStoreManager = new KeyStoreManager(host);
+        final SSLContextConfigurator configurator = new SSLContextConfigurator();
+        configurator.setKeyStoreBytes(keyStoreManager.getKeyStoreBytes());
+        configurator.setKeyStorePass(keyStoreManager.getKeyStorePassword());
+        configurator.setTrustStoreBytes(keyStoreManager.getKeyStoreBytes());
+        configurator.setTrustStorePass(keyStoreManager.getKeyStorePassword());
+        final SSLEngineConfigurator sslEngineConfigurator = new SSLEngineConfigurator(configurator)
+            .setClientMode(false).setNeedClientAuth(false);
+        return sslEngineConfigurator;
+    }
+
+
+    private static Http2AddOn createHttp2AddOn() {
+        final Http2Configuration configuration = Http2Configuration.builder().build();
+        return new Http2AddOn(configuration);
+    }
+
+
+    private static ResourceConfig createResourceConfig() {
+        return new ResourceConfig().registerClasses(TestedEndpoint.class);
+    }
+
+
+    private static HttpServer startServer(final NetworkListener listener, final URI endpointUri,
+        final ResourceConfig resourceConfig) {
+        final HttpServer srv = GrizzlyHttpServerFactory.createHttpServer(endpointUri, resourceConfig, false);
+        try {
+            srv.addListener(listener);
+            srv.start();
+            return srv;
+        } catch (final IOException e) {
+            throw new IllegalStateException("Could not start the server!", e);
+        }
+    }
+}
diff --git a/examples/helloworld-weld/pom.xml b/examples/helloworld-weld/pom.xml
index 16b3fb0..4ad42c1 100644
--- a/examples/helloworld-weld/pom.xml
+++ b/examples/helloworld-weld/pom.xml
@@ -38,7 +38,6 @@
         <dependency>
             <groupId>jakarta.enterprise</groupId>
             <artifactId>jakarta.enterprise.cdi-api</artifactId>
-            <version>${cdi.api.version}</version>
         </dependency>
         <dependency>
             <groupId>org.glassfish.jersey.test-framework.providers</groupId>
diff --git a/pom.xml b/pom.xml
index eab3679..fd03161 100644
--- a/pom.xml
+++ b/pom.xml
@@ -213,7 +213,7 @@
         </license>
     </licenses>
 
-    <scm>    
+    <scm>
         <connection>scm:git:git@github.com:jersey/jersey.git</connection>
         <developerConnection>scm:git:git@github.com:eclipse-ee4j/jersey.git</developerConnection>
         <url>https://github.com/eclipse-ee4j/jersey</url>
@@ -1595,6 +1595,11 @@
             </dependency>
             <dependency>
                 <groupId>org.glassfish.grizzly</groupId>
+                <artifactId>grizzly-http2</artifactId>
+                <version>${grizzly2.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.glassfish.grizzly</groupId>
                 <artifactId>grizzly-http-servlet</artifactId>
                 <version>${grizzly2.version}</version>
             </dependency>
@@ -1613,6 +1618,11 @@
                 <artifactId>grizzly-http-client</artifactId>
                 <version>${grizzly.client.version}</version>
             </dependency>
+            <dependency>
+                <groupId>org.glassfish.grizzly</groupId>
+                <artifactId>grizzly-npn-api</artifactId>
+                <version>${grizzly.npn.version}</version>
+            </dependency>
 
             <dependency>
                 <groupId>io.netty</groupId>
@@ -1974,6 +1984,18 @@
                 <version>${xmlunit.version}</version>
                 <scope>test</scope>
             </dependency>
+            <dependency>
+                <groupId>org.bouncycastle</groupId>
+                <artifactId>bcprov-jdk15on</artifactId>
+                <version>${bouncycastle.version}</version>
+                <scope>test</scope>
+            </dependency>
+            <dependency>
+                <groupId>org.bouncycastle</groupId>
+                <artifactId>bcmail-jdk15on</artifactId>
+                <version>${bouncycastle.version}</version>
+                <scope>test</scope>
+            </dependency>
 
             <dependency>
                 <groupId>org.apache.felix</groupId>
@@ -2057,6 +2079,7 @@
         <asm.version>9.2</asm.version>
         <bnd.plugin.version>2.3.6</bnd.plugin.version>
 
+        <bouncycastle.version>1.68</bouncycastle.version>
         <commons-lang3.version>3.3.2</commons-lang3.version>
         <microprofile.config.version>2.0</microprofile.config.version>
         <checkstyle.mvn.plugin.version>3.1.0</checkstyle.mvn.plugin.version>
@@ -2127,6 +2150,7 @@
         <cdi.api.version>3.0.0</cdi.api.version>
         <ejb.version>4.0.0</ejb.version>
         <grizzly2.version>3.0.0</grizzly2.version>
+        <grizzly.npn.version>2.0.0</grizzly.npn.version>
         <hk2.version>3.0.1</hk2.version>
         <jsp.version>3.0.0</jsp.version>
         <jstl.version>2.0.0</jstl.version>
diff --git a/tests/pom.xml b/tests/pom.xml
index 259a689..ca0cccf 100644
--- a/tests/pom.xml
+++ b/tests/pom.xml
@@ -68,7 +68,6 @@
         <dependency>
             <groupId>org.glassfish.jersey.inject</groupId>
             <artifactId>jersey-hk2</artifactId>
-            <version>${project.version}</version>
         </dependency>
     </dependencies>