Support for JAX-RS 2.2 Java SE Bootstrapping API (#3839)

* Java SE Bootstrap API

Signed-off-by: Markus KARG <markus@headcrashing.eu>
diff --git a/containers/grizzly2-http/pom.xml b/containers/grizzly2-http/pom.xml
index 46c9b66..8ca8823 100644
--- a/containers/grizzly2-http/pom.xml
+++ b/containers/grizzly2-http/pom.xml
@@ -41,6 +41,10 @@
             <groupId>org.glassfish.grizzly</groupId>
             <artifactId>grizzly-http-server</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.hamcrest</groupId>
+            <artifactId>hamcrest-library</artifactId>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServer.java b/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServer.java
new file mode 100644
index 0000000..581b7ee
--- /dev/null
+++ b/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServer.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2018 Markus KARG. 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 static java.lang.Boolean.TRUE;
+import static javax.ws.rs.JAXRS.Configuration.SSLClientAuthentication.MANDATORY;
+import static javax.ws.rs.JAXRS.Configuration.SSLClientAuthentication.OPTIONAL;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+
+import javax.net.ssl.SSLContext;
+import javax.ws.rs.JAXRS;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.UriBuilder;
+
+import org.glassfish.grizzly.http.server.HttpServer;
+import org.glassfish.grizzly.ssl.SSLEngineConfigurator;
+import org.glassfish.jersey.server.ServerProperties;
+import org.glassfish.jersey.server.spi.Server;
+
+/**
+ * Jersey {@code Server} implementation based on Grizzly {@link HttpServer}.
+ *
+ * @author Markus KARG (markus@headcrashing.eu)
+ * @since 2.30
+ */
+public final class GrizzlyHttpServer implements Server {
+
+    private final GrizzlyHttpContainer container;
+
+    private final HttpServer httpServer;
+
+    GrizzlyHttpServer(final Application application, final JAXRS.Configuration configuration) {
+        final String protocol = configuration.protocol();
+        final String host = configuration.host();
+        final int port = configuration.port();
+        final String rootPath = configuration.rootPath();
+        final SSLContext sslContext = configuration.sslContext();
+        final JAXRS.Configuration.SSLClientAuthentication sslClientAuthentication = configuration
+                .sslClientAuthentication();
+        final boolean autoStart = Optional.ofNullable((Boolean) configuration.property(ServerProperties.AUTO_START))
+                .orElse(TRUE);
+        final URI uri = UriBuilder.newInstance().scheme(protocol.toLowerCase()).host(host).port(port).path(rootPath)
+                .build();
+
+        this.container = new GrizzlyHttpContainer(application);
+        this.httpServer = GrizzlyHttpServerFactory.createHttpServer(uri, this.container, "HTTPS".equals(protocol),
+                new SSLEngineConfigurator(sslContext, false, sslClientAuthentication == OPTIONAL,
+                        sslClientAuthentication == MANDATORY),
+                autoStart);
+    }
+
+    @Override
+    public final GrizzlyHttpContainer container() {
+        return this.container;
+    }
+
+    @Override
+    public final int port() {
+        return this.httpServer.getListener("grizzly").getPort();
+    }
+
+    @Override
+    public final CompletableFuture<Void> start() {
+        return CompletableFuture.runAsync(() -> {
+            try {
+                this.httpServer.start();
+            } catch (final IOException e) {
+                throw new CompletionException(e);
+            }
+        });
+    }
+
+    @Override
+    public final CompletableFuture<Void> stop() {
+        return CompletableFuture.runAsync(this.httpServer::shutdownNow);
+    }
+
+    @Override
+    public final <T> T unwrap(final Class<T> nativeClass) {
+        return nativeClass.cast(this.httpServer);
+    }
+
+}
diff --git a/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServerProvider.java b/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServerProvider.java
new file mode 100644
index 0000000..82a99ee
--- /dev/null
+++ b/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServerProvider.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2018 Markus KARG. 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 javax.ws.rs.JAXRS;
+import javax.ws.rs.core.Application;
+
+import org.glassfish.grizzly.http.server.HttpServer;
+import org.glassfish.jersey.server.spi.Server;
+import org.glassfish.jersey.server.spi.ServerProvider;
+
+/**
+ * Server provider for servers based on Grizzly {@link HttpServer}.
+ *
+ * @author Markus KARG (markus@headcrashing.eu)
+ * @since 2.30
+ */
+public final class GrizzlyHttpServerProvider implements ServerProvider {
+
+    @Override
+    public final <T extends Server> T createServer(final Class<T> type, final Application application,
+            final JAXRS.Configuration configuration) {
+        return GrizzlyHttpServer.class == type || Server.class == type
+                ? type.cast(new GrizzlyHttpServer(application, configuration))
+                : null;
+    }
+}
diff --git a/containers/grizzly2-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ServerProvider b/containers/grizzly2-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ServerProvider
new file mode 100644
index 0000000..dea5f90
--- /dev/null
+++ b/containers/grizzly2-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ServerProvider
@@ -0,0 +1 @@
+org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerProvider
\ No newline at end of file
diff --git a/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServerProviderTest.java b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServerProviderTest.java
new file mode 100644
index 0000000..647d6d1
--- /dev/null
+++ b/containers/grizzly2-http/src/test/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServerProviderTest.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (c) 2018 Markus KARG. 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 static java.lang.Boolean.FALSE;
+import static java.lang.Boolean.TRUE;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.junit.Assert.assertThat;
+
+import java.security.AccessController;
+import java.security.NoSuchAlgorithmException;
+import java.util.Collections;
+import java.util.Set;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.ExecutionException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.net.ssl.SSLContext;
+import javax.ws.rs.GET;
+import javax.ws.rs.JAXRS;
+import javax.ws.rs.JAXRS.Configuration.SSLClientAuthentication;
+import javax.ws.rs.Path;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.UriBuilder;
+
+import org.glassfish.grizzly.http.server.HttpServer;
+import org.glassfish.jersey.internal.util.PropertiesHelper;
+import org.glassfish.jersey.server.ServerProperties;
+import org.glassfish.jersey.server.spi.Container;
+import org.glassfish.jersey.server.spi.Server;
+import org.glassfish.jersey.server.spi.ServerProvider;
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link GrizzlyHttpServerProvider}.
+ *
+ * @author Markus KARG (markus@headcrashing.eu)
+ * @since 2.30
+ */
+public final class GrizzlyHttpServerProviderTest {
+
+    @Test(timeout = 15000)
+    public final void shouldProvideServer() throws InterruptedException, ExecutionException {
+        // given
+        final ServerProvider serverProvider = new GrizzlyHttpServerProvider();
+        final Resource resource = new Resource();
+        final Application application = new Application() {
+            @Override
+            public final Set<Object> getSingletons() {
+                return Collections.singleton(resource);
+            }
+        };
+        final JAXRS.Configuration configuration = name -> {
+            switch (name) {
+            case JAXRS.Configuration.PROTOCOL:
+                return "HTTP";
+            case JAXRS.Configuration.HOST:
+                return "localhost";
+            case JAXRS.Configuration.PORT:
+                return getPort();
+            case JAXRS.Configuration.ROOT_PATH:
+                return "/";
+            case JAXRS.Configuration.SSL_CLIENT_AUTHENTICATION:
+                return SSLClientAuthentication.NONE;
+            case JAXRS.Configuration.SSL_CONTEXT:
+                try {
+                    return SSLContext.getDefault();
+                } catch (final NoSuchAlgorithmException e) {
+                    throw new RuntimeException(e);
+                }
+            case ServerProperties.AUTO_START:
+                return FALSE;
+            default:
+                return null;
+            }
+        };
+
+        // when
+        final Server server = serverProvider.createServer(Server.class, application, configuration);
+        final Object nativeHandle = server.unwrap(Object.class);
+        final CompletionStage<?> start = server.start();
+        final Object startResult = start.toCompletableFuture().get();
+        final Container container = server.container();
+        final int port = server.port();
+        final String entity = ClientBuilder.newClient()
+                .target(UriBuilder.newInstance().scheme("http").host("localhost").port(port).build()).request()
+                .get(String.class);
+        final CompletionStage<?> stop = server.stop();
+        final Object stopResult = stop.toCompletableFuture().get();
+
+        // then
+        assertThat(server, is(instanceOf(GrizzlyHttpServer.class)));
+        assertThat(nativeHandle, is(instanceOf(HttpServer.class)));
+        assertThat(startResult, is(nullValue()));
+        assertThat(container, is(instanceOf(GrizzlyHttpContainer.class)));
+        assertThat(port, is(greaterThan(0)));
+        assertThat(entity, is(resource.toString()));
+        assertThat(stopResult, is(nullValue()));
+    }
+
+    @Path("/")
+    protected static final class Resource {
+        @GET
+        @Override
+        public final String toString() {
+            return super.toString();
+        }
+    }
+
+    private static final Logger LOGGER = Logger.getLogger(GrizzlyHttpServerProviderTest.class.getName());
+
+    private static final int DEFAULT_PORT = 0;
+
+    private static final int getPort() {
+        final String value = AccessController
+                .doPrivileged(PropertiesHelper.getSystemProperty("jersey.config.test.container.port"));
+        if (value != null) {
+            try {
+                final int i = Integer.parseInt(value);
+                if (i < 0) {
+                    throw new NumberFormatException("Value is negative.");
+                }
+                return i;
+            } catch (final NumberFormatException e) {
+                LOGGER.log(Level.CONFIG,
+                        "Value of 'jersey.config.test.container.port'"
+                                + " property is not a valid non-negative integer [" + value + "]."
+                                + " Reverting to default [" + DEFAULT_PORT + "].",
+                        e);
+            }
+        }
+
+        return DEFAULT_PORT;
+    }
+
+    @Test(timeout = 15000)
+    public final void shouldScanFreePort() throws InterruptedException, ExecutionException {
+        // given
+        final ServerProvider serverProvider = new GrizzlyHttpServerProvider();
+        final Application application = new Application();
+        final JAXRS.Configuration configuration = name -> {
+            switch (name) {
+            case JAXRS.Configuration.PROTOCOL:
+                return "HTTP";
+            case JAXRS.Configuration.HOST:
+                return "localhost";
+            case JAXRS.Configuration.PORT:
+                return JAXRS.Configuration.FREE_PORT;
+            case JAXRS.Configuration.ROOT_PATH:
+                return "/";
+            case JAXRS.Configuration.SSL_CLIENT_AUTHENTICATION:
+                return SSLClientAuthentication.NONE;
+            case JAXRS.Configuration.SSL_CONTEXT:
+                try {
+                    return SSLContext.getDefault();
+                } catch (final NoSuchAlgorithmException e) {
+                    throw new RuntimeException(e);
+                }
+            case ServerProperties.AUTO_START:
+                return TRUE;
+            default:
+                return null;
+            }
+        };
+
+        // when
+        final Server server = serverProvider.createServer(Server.class, application, configuration);
+
+        // then
+        assertThat(server.port(), is(greaterThan(0)));
+    }
+
+}
diff --git a/containers/jdk-http/pom.xml b/containers/jdk-http/pom.xml
index 3497cac..6601b6f 100644
--- a/containers/jdk-http/pom.xml
+++ b/containers/jdk-http/pom.xml
@@ -38,6 +38,10 @@
             <artifactId>guava</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.hamcrest</groupId>
+            <artifactId>hamcrest-library</artifactId>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/containers/jdk-http/src/main/java/org/glassfish/jersey/jdkhttp/JdkHttpServer.java b/containers/jdk-http/src/main/java/org/glassfish/jersey/jdkhttp/JdkHttpServer.java
new file mode 100644
index 0000000..16a445c
--- /dev/null
+++ b/containers/jdk-http/src/main/java/org/glassfish/jersey/jdkhttp/JdkHttpServer.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2018 Markus KARG. 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.jdkhttp;
+
+import static java.lang.Boolean.TRUE;
+import static javax.ws.rs.JAXRS.Configuration.SSLClientAuthentication.MANDATORY;
+import static javax.ws.rs.JAXRS.Configuration.SSLClientAuthentication.OPTIONAL;
+
+import java.net.URI;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+
+import javax.net.ssl.SSLContext;
+import javax.ws.rs.JAXRS;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.UriBuilder;
+
+import org.glassfish.jersey.server.ServerProperties;
+import org.glassfish.jersey.server.spi.Server;
+
+import com.sun.net.httpserver.HttpServer;
+
+/**
+ * Jersey {@code Server} implementation based on JDK {@link HttpServer}.
+ *
+ * @author Markus KARG (markus@headcrashing.eu)
+ * @since 2.30
+ */
+public final class JdkHttpServer implements Server {
+
+    private final JdkHttpHandlerContainer container;
+
+    private final HttpServer httpServer;
+
+    JdkHttpServer(final Application application, final JAXRS.Configuration configuration) {
+        final String protocol = configuration.protocol();
+        final String host = configuration.host();
+        final int port = configuration.port();
+        final String rootPath = configuration.rootPath();
+        final SSLContext sslContext = configuration.sslContext();
+        final JAXRS.Configuration.SSLClientAuthentication sslClientAuthentication = configuration
+                .sslClientAuthentication();
+        final boolean autoStart = Optional.ofNullable((Boolean) configuration.property(ServerProperties.AUTO_START))
+                .orElse(TRUE);
+        final URI uri = UriBuilder.newInstance().scheme(protocol.toLowerCase()).host(host).port(port).path(rootPath)
+                .build();
+
+        this.container = new JdkHttpHandlerContainer(application);
+        this.httpServer = JdkHttpServerFactory.createHttpServer(uri, this.container,
+                "HTTPS".equals(protocol) ? sslContext : null, sslClientAuthentication == OPTIONAL,
+                sslClientAuthentication == MANDATORY, autoStart);
+    }
+
+    @Override
+    public final JdkHttpHandlerContainer container() {
+        return this.container;
+    }
+
+    @Override
+    public final int port() {
+        return this.httpServer.getAddress().getPort();
+    }
+
+    @Override
+    public final CompletableFuture<Void> start() {
+        return CompletableFuture.runAsync(this.httpServer::start);
+    }
+
+    @Override
+    public final CompletableFuture<Void> stop() {
+        return CompletableFuture.runAsync(() -> this.httpServer.stop(0));
+    }
+
+    @Override
+    public final <T> T unwrap(final Class<T> nativeClass) {
+        return nativeClass.cast(this.httpServer);
+    }
+
+}
diff --git a/containers/jdk-http/src/main/java/org/glassfish/jersey/jdkhttp/JdkHttpServerFactory.java b/containers/jdk-http/src/main/java/org/glassfish/jersey/jdkhttp/JdkHttpServerFactory.java
index 0f1fbb2..f91a0a8 100644
--- a/containers/jdk-http/src/main/java/org/glassfish/jersey/jdkhttp/JdkHttpServerFactory.java
+++ b/containers/jdk-http/src/main/java/org/glassfish/jersey/jdkhttp/JdkHttpServerFactory.java
@@ -26,6 +26,7 @@
 import javax.ws.rs.ProcessingException;
 
 import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLParameters;
 
 import org.glassfish.jersey.internal.guava.ThreadFactoryBuilder;
 import org.glassfish.jersey.jdkhttp.internal.LocalizationMessages;
@@ -37,6 +38,7 @@
 import com.sun.net.httpserver.HttpHandler;
 import com.sun.net.httpserver.HttpServer;
 import com.sun.net.httpserver.HttpsConfigurator;
+import com.sun.net.httpserver.HttpsParameters;
 import com.sun.net.httpserver.HttpsServer;
 
 /**
@@ -177,8 +179,17 @@
     }
 
     private static HttpServer createHttpServer(final URI uri,
+            final JdkHttpHandlerContainer handler,
+            final SSLContext sslContext,
+            final boolean start) {
+        return createHttpServer(uri, handler, sslContext, false, false, start);
+    }
+
+    static HttpServer createHttpServer(final URI uri,
                                                final JdkHttpHandlerContainer handler,
                                                final SSLContext sslContext,
+                                               final boolean sslClientAuthWanted,
+                                               final boolean sslClientAuthNeeded,
                                                final boolean start) {
         if (uri == null) {
             throw new IllegalArgumentException(LocalizationMessages.ERROR_CONTAINER_URI_NULL());
@@ -187,7 +198,14 @@
         final String scheme = uri.getScheme();
         final boolean isHttp = "http".equalsIgnoreCase(scheme);
         final boolean isHttps = "https".equalsIgnoreCase(scheme);
-        final HttpsConfigurator httpsConfigurator = sslContext != null ? new HttpsConfigurator(sslContext) : null;
+        final HttpsConfigurator httpsConfigurator = sslContext != null ? new HttpsConfigurator(sslContext) {
+            public final void configure(final HttpsParameters httpsParameters) {
+                final SSLParameters sslParameters = sslContext.getDefaultSSLParameters();
+                sslParameters.setWantClientAuth(sslClientAuthWanted);
+                sslParameters.setNeedClientAuth(sslClientAuthNeeded);
+                httpsParameters.setSSLParameters(sslParameters);
+            }
+        } : null;
 
         if (isHttp) {
             if (httpsConfigurator != null) {
diff --git a/containers/jdk-http/src/main/java/org/glassfish/jersey/jdkhttp/JdkHttpServerProvider.java b/containers/jdk-http/src/main/java/org/glassfish/jersey/jdkhttp/JdkHttpServerProvider.java
new file mode 100644
index 0000000..6c10cab
--- /dev/null
+++ b/containers/jdk-http/src/main/java/org/glassfish/jersey/jdkhttp/JdkHttpServerProvider.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2018 Markus KARG. 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.jdkhttp;
+
+import javax.ws.rs.JAXRS;
+import javax.ws.rs.core.Application;
+
+import org.glassfish.jersey.server.spi.Server;
+import org.glassfish.jersey.server.spi.ServerProvider;
+
+import com.sun.net.httpserver.HttpServer;
+
+/**
+ * Server provider for servers based on JDK {@link HttpServer}.
+ *
+ * @author Markus KARG (markus@headcrashing.eu)
+ * @since 2.30
+ */
+public final class JdkHttpServerProvider implements ServerProvider {
+
+    @Override
+    public final <T extends Server> T createServer(final Class<T> type, final Application application,
+            final JAXRS.Configuration configuration) {
+        return JdkHttpServer.class == type || Server.class == type
+                ? type.cast(new JdkHttpServer(application, configuration))
+                : null;
+    }
+}
diff --git a/containers/jdk-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ServerProvider b/containers/jdk-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ServerProvider
new file mode 100644
index 0000000..2d8abc3
--- /dev/null
+++ b/containers/jdk-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ServerProvider
@@ -0,0 +1 @@
+org.glassfish.jersey.jdkhttp.JdkHttpServerProvider
\ No newline at end of file
diff --git a/containers/jdk-http/src/test/java/org/glassfish/jersey/jdkhttp/AbstractJdkHttpServerTester.java b/containers/jdk-http/src/test/java/org/glassfish/jersey/jdkhttp/AbstractJdkHttpServerTester.java
index 24998f9..b5c0be8 100644
--- a/containers/jdk-http/src/test/java/org/glassfish/jersey/jdkhttp/AbstractJdkHttpServerTester.java
+++ b/containers/jdk-http/src/test/java/org/glassfish/jersey/jdkhttp/AbstractJdkHttpServerTester.java
@@ -21,6 +21,7 @@
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
+import javax.net.ssl.SSLContext;
 import javax.ws.rs.core.UriBuilder;
 
 import com.sun.net.httpserver.HttpServer;
@@ -37,7 +38,7 @@
 public abstract class AbstractJdkHttpServerTester {
 
     public static final String CONTEXT = "";
-    private final int DEFAULT_PORT = 9998;
+    private final int DEFAULT_PORT = 0;
 
     private static final Logger LOGGER = Logger.getLogger(AbstractJdkHttpServerTester.class.getName());
 
@@ -47,20 +48,24 @@
      * @return The HTTP port of the URI
      */
     protected final int getPort() {
+        if (server != null) {
+            return server.getAddress().getPort();
+        }
+
         final String value =
                 AccessController.doPrivileged(PropertiesHelper.getSystemProperty("jersey.config.test.container.port"));
         if (value != null) {
 
             try {
                 final int i = Integer.parseInt(value);
-                if (i <= 0) {
-                    throw new NumberFormatException("Value not positive.");
+                if (i < 0) {
+                    throw new NumberFormatException("Value is negative.");
                 }
                 return i;
             } catch (NumberFormatException e) {
                 LOGGER.log(Level.CONFIG,
                         "Value of 'jersey.config.test.container.port'"
-                                + " property is not a valid positive integer [" + value + "]."
+                                + " property is not a valid non-negative integer [" + value + "]."
                                 + " Reverting to default [" + DEFAULT_PORT + "].",
                         e);
             }
@@ -79,14 +84,22 @@
         config.register(LoggingFeature.class);
         final URI baseUri = getBaseUri();
         server = JdkHttpServerFactory.createHttpServer(baseUri, config);
-        LOGGER.log(Level.INFO, "jdk-http server started on base uri: " + baseUri);
+        LOGGER.log(Level.INFO, "jdk-http server started on base uri: " + getBaseUri());
     }
 
     public void startServer(ResourceConfig config) {
         final URI baseUri = getBaseUri();
         config.register(LoggingFeature.class);
         server = JdkHttpServerFactory.createHttpServer(baseUri, config);
-        LOGGER.log(Level.INFO, "jdk-http server started on base uri: " + baseUri);
+        LOGGER.log(Level.INFO, "jdk-http server started on base uri: " + getBaseUri());
+    }
+
+    public HttpServer startServer(final URI uri, final ResourceConfig config,
+            final SSLContext sslContext, final boolean start) {
+        config.register(LoggingFeature.class);
+        server = JdkHttpServerFactory.createHttpServer(uri, config, sslContext, start);
+        LOGGER.log(Level.INFO, "jdk-http server started on base uri: " + UriBuilder.fromUri(uri).port(getPort()).build());
+        return server;
     }
 
     public URI getBaseUri() {
diff --git a/containers/jdk-http/src/test/java/org/glassfish/jersey/jdkhttp/JdkHttpServerProviderTest.java b/containers/jdk-http/src/test/java/org/glassfish/jersey/jdkhttp/JdkHttpServerProviderTest.java
new file mode 100644
index 0000000..c7c4e44
--- /dev/null
+++ b/containers/jdk-http/src/test/java/org/glassfish/jersey/jdkhttp/JdkHttpServerProviderTest.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (c) 2018 Markus KARG. 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.jdkhttp;
+
+import static java.lang.Boolean.FALSE;
+import static java.lang.Boolean.TRUE;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.junit.Assert.assertThat;
+
+import java.security.AccessController;
+import java.security.NoSuchAlgorithmException;
+import java.util.Collections;
+import java.util.Set;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.ExecutionException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.net.ssl.SSLContext;
+import javax.ws.rs.GET;
+import javax.ws.rs.JAXRS;
+import javax.ws.rs.JAXRS.Configuration.SSLClientAuthentication;
+import javax.ws.rs.Path;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.UriBuilder;
+
+import org.glassfish.jersey.internal.util.PropertiesHelper;
+import org.glassfish.jersey.server.ServerProperties;
+import org.glassfish.jersey.server.spi.Container;
+import org.glassfish.jersey.server.spi.Server;
+import org.glassfish.jersey.server.spi.ServerProvider;
+import org.junit.Test;
+
+import com.sun.net.httpserver.HttpServer;
+
+/**
+ * Unit tests for {@link JdkHttpServerProvider}.
+ *
+ * @author Markus KARG (markus@headcrashing.eu)
+ * @since 2.30
+ */
+public final class JdkHttpServerProviderTest {
+
+    @Test(timeout = 15000)
+    public final void shouldProvideServer() throws InterruptedException, ExecutionException {
+        // given
+        final ServerProvider serverProvider = new JdkHttpServerProvider();
+        final Resource resource = new Resource();
+        final Application application = new Application() {
+            @Override
+            public final Set<Object> getSingletons() {
+                return Collections.singleton(resource);
+            }
+        };
+        final JAXRS.Configuration configuration = name -> {
+            switch (name) {
+            case JAXRS.Configuration.PROTOCOL:
+                return "HTTP";
+            case JAXRS.Configuration.HOST:
+                return "localhost";
+            case JAXRS.Configuration.PORT:
+                return getPort();
+            case JAXRS.Configuration.ROOT_PATH:
+                return "/";
+            case JAXRS.Configuration.SSL_CLIENT_AUTHENTICATION:
+                return SSLClientAuthentication.NONE;
+            case JAXRS.Configuration.SSL_CONTEXT:
+                try {
+                    return SSLContext.getDefault();
+                } catch (final NoSuchAlgorithmException e) {
+                    throw new RuntimeException(e);
+                }
+            case ServerProperties.AUTO_START:
+                return FALSE;
+            default:
+                return null;
+            }
+        };
+
+        // when
+        final Server server = serverProvider.createServer(Server.class, application, configuration);
+        final Object nativeHandle = server.unwrap(Object.class);
+        final CompletionStage<?> start = server.start();
+        final Object startResult = start.toCompletableFuture().get();
+        final Container container = server.container();
+        final int port = server.port();
+        final String entity = ClientBuilder.newClient()
+                .target(UriBuilder.newInstance().scheme("http").host("localhost").port(port).build()).request()
+                .get(String.class);
+        final CompletionStage<?> stop = server.stop();
+        final Object stopResult = stop.toCompletableFuture().get();
+
+        // then
+        assertThat(server, is(instanceOf(JdkHttpServer.class)));
+        assertThat(nativeHandle, is(instanceOf(HttpServer.class)));
+        assertThat(startResult, is(nullValue()));
+        assertThat(container, is(instanceOf(JdkHttpHandlerContainer.class)));
+        assertThat(port, is(greaterThan(0)));
+        assertThat(entity, is(resource.toString()));
+        assertThat(stopResult, is(nullValue()));
+    }
+
+    @Path("/")
+    protected static final class Resource {
+        @GET
+        @Override
+        public final String toString() {
+            return super.toString();
+        }
+    }
+
+    private static final Logger LOGGER = Logger.getLogger(JdkHttpServerProviderTest.class.getName());
+
+    private static final int DEFAULT_PORT = 0;
+
+    private static final int getPort() {
+        final String value = AccessController
+                .doPrivileged(PropertiesHelper.getSystemProperty("jersey.config.test.container.port"));
+        if (value != null) {
+            try {
+                final int i = Integer.parseInt(value);
+                if (i < 0) {
+                    throw new NumberFormatException("Value is negative.");
+                }
+                return i;
+            } catch (final NumberFormatException e) {
+                LOGGER.log(Level.CONFIG,
+                        "Value of 'jersey.config.test.container.port'"
+                                + " property is not a valid non-negative integer [" + value + "]."
+                                + " Reverting to default [" + DEFAULT_PORT + "].",
+                        e);
+            }
+        }
+
+        return DEFAULT_PORT;
+    }
+
+    @Test(timeout = 15000)
+    public final void shouldScanFreePort() throws InterruptedException, ExecutionException {
+        // given
+        final ServerProvider serverProvider = new JdkHttpServerProvider();
+        final Application application = new Application();
+        final JAXRS.Configuration configuration = name -> {
+            switch (name) {
+            case JAXRS.Configuration.PROTOCOL:
+                return "HTTP";
+            case JAXRS.Configuration.HOST:
+                return "localhost";
+            case JAXRS.Configuration.PORT:
+                return JAXRS.Configuration.FREE_PORT;
+            case JAXRS.Configuration.ROOT_PATH:
+                return "/";
+            case JAXRS.Configuration.SSL_CLIENT_AUTHENTICATION:
+                return SSLClientAuthentication.NONE;
+            case JAXRS.Configuration.SSL_CONTEXT:
+                try {
+                    return SSLContext.getDefault();
+                } catch (final NoSuchAlgorithmException e) {
+                    throw new RuntimeException(e);
+                }
+            case ServerProperties.AUTO_START:
+                return TRUE;
+            default:
+                return null;
+            }
+        };
+
+        // when
+        final Server server = serverProvider.createServer(Server.class, application, configuration);
+
+        // then
+        assertThat(server.port(), is(greaterThan(0)));
+    }
+
+}
diff --git a/containers/jdk-http/src/test/java/org/glassfish/jersey/jdkhttp/JdkHttpsServerTest.java b/containers/jdk-http/src/test/java/org/glassfish/jersey/jdkhttp/JdkHttpsServerTest.java
index 180f2bc..5fe94cb 100644
--- a/containers/jdk-http/src/test/java/org/glassfish/jersey/jdkhttp/JdkHttpsServerTest.java
+++ b/containers/jdk-http/src/test/java/org/glassfish/jersey/jdkhttp/JdkHttpsServerTest.java
@@ -64,9 +64,6 @@
     private static final String TRUSTSTORE_SERVER_FILE = "./truststore_server";
     private static final String TRUSTSTORE_SERVER_PWD = "asdfgh";
 
-    private HttpServer server;
-    private final URI httpsUri = UriBuilder.fromUri("https://localhost/").port(getPort()).build();
-    private final URI httpUri = UriBuilder.fromUri("http://localhost/").port(getPort()).build();
     private final ResourceConfig rc = new ResourceConfig(TestResource.class);
 
     @Path("/testHttps")
@@ -83,7 +80,7 @@
      */
     @Test
     public void testCreateHttpsServerNoSslContext() throws Exception {
-        server = JdkHttpServerFactory.createHttpServer(httpsUri, rc, null, false);
+        HttpServer server = startServer(getHttpsUri(), rc, null, false);
         assertThat(server, instanceOf(HttpsServer.class));
     }
 
@@ -93,7 +90,7 @@
      */
     @Test(expected = IllegalArgumentException.class)
     public void testStartHttpServerNoSslContext() throws Exception {
-        server = JdkHttpServerFactory.createHttpServer(httpsUri, rc, null, true);
+        startServer(getHttpsUri(), rc, null, true);
     }
 
     /**
@@ -103,13 +100,13 @@
      */
     @Test(expected = SSLHandshakeException.class)
     public void testCreateHttpsServerDefaultSslContext() throws Throwable {
-        server = JdkHttpServerFactory.createHttpServer(httpsUri, rc, SSLContext.getDefault(), true);
+        HttpServer server = startServer(getHttpsUri(), rc, SSLContext.getDefault(), true);
         assertThat(server, instanceOf(HttpsServer.class));
 
         // access the https server with not configured client
         final Client client = ClientBuilder.newBuilder().newClient();
         try {
-            client.target(httpsUri).path("testHttps").request().get(String.class);
+            client.target(UriBuilder.fromUri("https://localhost/").port(getPort())).path("testHttps").request().get(String.class);
         } catch (final ProcessingException e) {
             throw e.getCause();
         }
@@ -122,13 +119,13 @@
      */
     @Test(expected = IOException.class)
     public void testHttpsServerNoSslContextDelayedStart() throws Throwable {
-        server = JdkHttpServerFactory.createHttpServer(httpsUri, rc, null, false);
+        HttpServer server = startServer(getHttpsUri(), rc, null, false);
         assertThat(server, instanceOf(HttpsServer.class));
         server.start();
 
         final Client client = ClientBuilder.newBuilder().newClient();
         try {
-            client.target(httpsUri).path("testHttps").request().get(String.class);
+            client.target(getHttpsUri()).path("testHttps").request().get(String.class);
         } catch (final ProcessingException e) {
             throw e.getCause();
         }
@@ -140,7 +137,7 @@
      */
     @Test(expected = IllegalStateException.class)
     public void testConfigureSslContextAfterStart() throws Throwable {
-        server = JdkHttpServerFactory.createHttpServer(httpsUri, rc, null, false);
+        HttpServer server = startServer(getHttpsUri(), rc, null, false);
         assertThat(server, instanceOf(HttpsServer.class));
         server.start();
         ((HttpsServer) server).setHttpsConfigurator(new HttpsConfigurator(getServerSslContext()));
@@ -154,14 +151,14 @@
     public void testCreateHttpsServerRoundTrip() throws IOException {
         final SSLContext serverSslContext = getServerSslContext();
 
-        server = JdkHttpServerFactory.createHttpServer(httpsUri, rc, serverSslContext, true);
+        HttpServer server = startServer(getHttpsUri(), rc, serverSslContext, true);
 
         final SSLContext foundContext = ((HttpsServer) server).getHttpsConfigurator().getSSLContext();
         assertEquals(serverSslContext, foundContext);
 
         final SSLContext clientSslContext = getClientSslContext();
         final Client client = ClientBuilder.newBuilder().sslContext(clientSslContext).build();
-        final String response = client.target(httpsUri).path("testHttps").request().get(String.class);
+        final String response = client.target(UriBuilder.fromUri("https://localhost/").port(getPort())).path("testHttps").request().get(String.class);
 
         assertEquals("test", response);
     }
@@ -172,7 +169,7 @@
      */
     @Test
     public void testHttpWithSsl() throws IOException {
-        server = JdkHttpServerFactory.createHttpServer(httpUri, rc, getServerSslContext(), true);
+        HttpServer server = startServer(getBaseUri(), rc, getServerSslContext(), true);
         assertThat(server, instanceOf(HttpServer.class));
         assertThat(server, not(instanceOf(HttpsServer.class)));
     }
@@ -204,11 +201,7 @@
         return sslConfigServer.createSSLContext();
     }
 
-    @After
-    public void tearDown() {
-        if (server != null) {
-            server.stop(0);
-            server = null;
-        }
+    private URI getHttpsUri() {
+        return UriBuilder.fromUri("https://localhost/").port(getPort()).build();
     }
 }
diff --git a/containers/jetty-http/pom.xml b/containers/jetty-http/pom.xml
index d4c158e..0787c91 100644
--- a/containers/jetty-http/pom.xml
+++ b/containers/jetty-http/pom.xml
@@ -50,6 +50,10 @@
             <groupId>org.eclipse.jetty</groupId>
             <artifactId>jetty-continuation</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.hamcrest</groupId>
+            <artifactId>hamcrest-library</artifactId>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpServer.java b/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpServer.java
new file mode 100644
index 0000000..bb22343
--- /dev/null
+++ b/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpServer.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2018 Markus KARG. 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;
+
+import static java.lang.Boolean.TRUE;
+import static javax.ws.rs.JAXRS.Configuration.SSLClientAuthentication.MANDATORY;
+import static javax.ws.rs.JAXRS.Configuration.SSLClientAuthentication.OPTIONAL;
+
+import java.net.URI;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+
+import javax.net.ssl.SSLContext;
+import javax.ws.rs.JAXRS;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.UriBuilder;
+
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.glassfish.jersey.server.ContainerFactory;
+import org.glassfish.jersey.server.ServerProperties;
+import org.glassfish.jersey.server.spi.Server;
+
+/**
+ * Jersey {@code Server} implementation based on Jetty
+ * {@link org.eclipse.jetty.server.Server Server}.
+ *
+ * @author Markus KARG (markus@headcrashing.eu)
+ * @since 2.30
+ */
+public final class JettyHttpServer implements Server {
+
+    private final JettyHttpContainer container;
+
+    private final org.eclipse.jetty.server.Server httpServer;
+
+    JettyHttpServer(final Application application, final JAXRS.Configuration configuration) {
+        final String protocol = configuration.protocol();
+        final String host = configuration.host();
+        final int port = configuration.port();
+        final String rootPath = configuration.rootPath();
+        final SSLContext sslContext = configuration.sslContext();
+        final JAXRS.Configuration.SSLClientAuthentication sslClientAuthentication = configuration
+                .sslClientAuthentication();
+        final boolean autoStart = Optional.ofNullable((Boolean) configuration.property(ServerProperties.AUTO_START))
+                .orElse(TRUE);
+        final URI uri = UriBuilder.newInstance().scheme(protocol.toLowerCase()).host(host).port(port).path(rootPath)
+                .build();
+
+        final SslContextFactory sslContextFactory;
+        if ("https".equals(uri.getScheme())) {
+            sslContextFactory = new SslContextFactory();
+            sslContextFactory.setSslContext(sslContext);
+            sslContextFactory.setWantClientAuth(sslClientAuthentication == OPTIONAL);
+            sslContextFactory.setNeedClientAuth(sslClientAuthentication == MANDATORY);
+        } else {
+            sslContextFactory = null;
+        }
+        this.container = ContainerFactory.createContainer(JettyHttpContainer.class, application);
+        this.httpServer = JettyHttpContainerFactory.createServer(uri, sslContextFactory, this.container, autoStart);
+    }
+
+    @Override
+    public final JettyHttpContainer container() {
+        return this.container;
+    }
+
+    @Override
+    public final int port() {
+        final ServerConnector serverConnector = (ServerConnector) this.httpServer.getConnectors()[0];
+        final int configuredPort = serverConnector.getPort();
+        final int localPort = serverConnector.getLocalPort();
+        return localPort < 0 ? configuredPort : localPort;
+    }
+
+    @Override
+    public final CompletableFuture<Void> start() {
+        return CompletableFuture.runAsync(() -> {
+            try {
+                this.httpServer.start();
+            } catch (final Exception e) {
+                throw new CompletionException(e);
+            }
+        });
+    }
+
+    @Override
+    public final CompletableFuture<Void> stop() {
+        return CompletableFuture.runAsync(() -> {
+            try {
+                this.httpServer.stop();
+            } catch (final Exception e) {
+                throw new CompletionException(e);
+            }
+        });
+    }
+
+    @Override
+    public final <T> T unwrap(final Class<T> nativeClass) {
+        return nativeClass.cast(this.httpServer);
+    }
+
+}
diff --git a/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpServerProvider.java b/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpServerProvider.java
new file mode 100644
index 0000000..8dc3fe3
--- /dev/null
+++ b/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpServerProvider.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2018 Markus KARG. 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;
+
+import javax.ws.rs.JAXRS;
+import javax.ws.rs.core.Application;
+
+import org.glassfish.jersey.server.spi.Server;
+import org.glassfish.jersey.server.spi.ServerProvider;
+
+/**
+ * Server provider for servers based on Jetty
+ * {@link org.eclipse.jetty.server.Server Server}.
+ *
+ * @author Markus KARG (markus@headcrashing.eu)
+ * @since 2.30
+ */
+public final class JettyHttpServerProvider implements ServerProvider {
+
+    @Override
+    public final <T extends Server> T createServer(final Class<T> type, final Application application,
+            final JAXRS.Configuration configuration) {
+        return JettyHttpServer.class == type || Server.class == type
+                ? type.cast(new JettyHttpServer(application, configuration))
+                : null;
+    }
+}
diff --git a/containers/jetty-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ServerProvider b/containers/jetty-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ServerProvider
new file mode 100644
index 0000000..403cc50
--- /dev/null
+++ b/containers/jetty-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ServerProvider
@@ -0,0 +1 @@
+org.glassfish.jersey.jetty.JettyHttpServerProvider
\ No newline at end of file
diff --git a/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/AbstractJettyServerTester.java b/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/AbstractJettyServerTester.java
index dcfe4b7..d0bb608 100644
--- a/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/AbstractJettyServerTester.java
+++ b/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/AbstractJettyServerTester.java
@@ -28,6 +28,7 @@
 import org.glassfish.jersey.server.ResourceConfig;
 
 import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
 import org.junit.After;
 
 /**
@@ -42,7 +43,7 @@
     private static final Logger LOGGER = Logger.getLogger(AbstractJettyServerTester.class.getName());
 
     public static final String CONTEXT = "";
-    private static final int DEFAULT_PORT = 9998;
+    private static final int DEFAULT_PORT = 0;
 
     /**
      * Get the port to be used for test application deployments.
@@ -50,20 +51,24 @@
      * @return The HTTP port of the URI
      */
     protected final int getPort() {
+        if (server != null) {
+            return ((ServerConnector) server.getConnectors()[0]).getLocalPort();
+        }
+
         final String value = AccessController
                 .doPrivileged(PropertiesHelper.getSystemProperty("jersey.config.test.container.port"));
         if (value != null) {
 
             try {
                 final int i = Integer.parseInt(value);
-                if (i <= 0) {
-                    throw new NumberFormatException("Value not positive.");
+                if (i < 0) {
+                    throw new NumberFormatException("Value is negative.");
                 }
                 return i;
             } catch (NumberFormatException e) {
                 LOGGER.log(Level.CONFIG,
                         "Value of 'jersey.config.test.container.port'"
-                                + " property is not a valid positive integer [" + value + "]."
+                                + " property is not a valid non-negative integer [" + value + "]."
                                 + " Reverting to default [" + DEFAULT_PORT + "].",
                         e);
             }
@@ -82,13 +87,13 @@
         config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY));
         final URI baseUri = getBaseUri();
         server = JettyHttpContainerFactory.createServer(baseUri, config);
-        LOGGER.log(Level.INFO, "Jetty-http server started on base uri: " + baseUri);
+        LOGGER.log(Level.INFO, "Jetty-http server started on base uri: " + getBaseUri());
     }
 
     public void startServer(ResourceConfig config) {
         final URI baseUri = getBaseUri();
         server = JettyHttpContainerFactory.createServer(baseUri, config);
-        LOGGER.log(Level.INFO, "Jetty-http server started on base uri: " + baseUri);
+        LOGGER.log(Level.INFO, "Jetty-http server started on base uri: " + getBaseUri());
     }
 
     public URI getBaseUri() {
diff --git a/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/JettyHttpServerProviderTest.java b/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/JettyHttpServerProviderTest.java
new file mode 100644
index 0000000..c4dc31e
--- /dev/null
+++ b/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/JettyHttpServerProviderTest.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) 2018 Markus KARG. 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;
+
+import static java.lang.Boolean.FALSE;
+import static java.lang.Boolean.TRUE;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.junit.Assert.assertThat;
+
+import java.security.AccessController;
+import java.security.NoSuchAlgorithmException;
+import java.util.Collections;
+import java.util.Set;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.ExecutionException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.net.ssl.SSLContext;
+import javax.ws.rs.GET;
+import javax.ws.rs.JAXRS;
+import javax.ws.rs.JAXRS.Configuration.SSLClientAuthentication;
+import javax.ws.rs.Path;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.UriBuilder;
+
+import org.glassfish.jersey.internal.util.PropertiesHelper;
+import org.glassfish.jersey.server.ServerProperties;
+import org.glassfish.jersey.server.spi.Container;
+import org.glassfish.jersey.server.spi.Server;
+import org.glassfish.jersey.server.spi.ServerProvider;
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link JettyHttpServerProvider}.
+ *
+ * @author Markus KARG (markus@headcrashing.eu)
+ * @since 2.30
+ */
+public final class JettyHttpServerProviderTest {
+
+    @Test(timeout = 15000)
+    public final void shouldProvideServer() throws InterruptedException, ExecutionException {
+        // given
+        final ServerProvider serverProvider = new JettyHttpServerProvider();
+        final Resource resource = new Resource();
+        final Application application = new Application() {
+            @Override
+            public final Set<Object> getSingletons() {
+                return Collections.singleton(resource);
+            }
+        };
+        final JAXRS.Configuration configuration = name -> {
+            switch (name) {
+            case JAXRS.Configuration.PROTOCOL:
+                return "HTTP";
+            case JAXRS.Configuration.HOST:
+                return "localhost";
+            case JAXRS.Configuration.PORT:
+                return getPort();
+            case JAXRS.Configuration.ROOT_PATH:
+                return "/";
+            case JAXRS.Configuration.SSL_CLIENT_AUTHENTICATION:
+                return SSLClientAuthentication.NONE;
+            case JAXRS.Configuration.SSL_CONTEXT:
+                try {
+                    return SSLContext.getDefault();
+                } catch (final NoSuchAlgorithmException e) {
+                    throw new RuntimeException(e);
+                }
+            case ServerProperties.AUTO_START:
+                return FALSE;
+            default:
+                return null;
+            }
+        };
+
+        // when
+        final Server server = serverProvider.createServer(Server.class, application, configuration);
+        final Object nativeHandle = server.unwrap(Object.class);
+        final CompletionStage<?> start = server.start();
+        final Object startResult = start.toCompletableFuture().get();
+        final Container container = server.container();
+        final int port = server.port();
+        final String entity = ClientBuilder.newClient()
+                .target(UriBuilder.newInstance().scheme("http").host("localhost").port(port).build()).request()
+                .get(String.class);
+        final CompletionStage<?> stop = server.stop();
+        final Object stopResult = stop.toCompletableFuture().get();
+
+        // then
+        assertThat(server, is(instanceOf(JettyHttpServer.class)));
+        assertThat(nativeHandle, is(instanceOf(org.eclipse.jetty.server.Server.class)));
+        assertThat(startResult, is(nullValue()));
+        assertThat(container, is(instanceOf(JettyHttpContainer.class)));
+        assertThat(port, is(greaterThan(0)));
+        assertThat(entity, is(resource.toString()));
+        assertThat(stopResult, is(nullValue()));
+    }
+
+    @Path("/")
+    protected static final class Resource {
+        @GET
+        @Override
+        public final String toString() {
+            return super.toString();
+        }
+    }
+
+    private static final Logger LOGGER = Logger.getLogger(JettyHttpServerProviderTest.class.getName());
+
+    private static final int DEFAULT_PORT = 0;
+
+    private static final int getPort() {
+        final String value = AccessController
+                .doPrivileged(PropertiesHelper.getSystemProperty("jersey.config.test.container.port"));
+        if (value != null) {
+            try {
+                final int i = Integer.parseInt(value);
+                if (i < 0) {
+                    throw new NumberFormatException("Value is negative.");
+                }
+                return i;
+            } catch (final NumberFormatException e) {
+                LOGGER.log(Level.CONFIG,
+                        "Value of 'jersey.config.test.container.port'"
+                                + " property is not a valid non-negative integer [" + value + "]."
+                                + " Reverting to default [" + DEFAULT_PORT + "].",
+                        e);
+            }
+        }
+
+        return DEFAULT_PORT;
+    }
+
+    @Test(timeout = 15000)
+    public final void shouldScanFreePort() throws InterruptedException, ExecutionException {
+        // given
+        final ServerProvider serverProvider = new JettyHttpServerProvider();
+        final Application application = new Application();
+        final JAXRS.Configuration configuration = name -> {
+            switch (name) {
+            case JAXRS.Configuration.PROTOCOL:
+                return "HTTP";
+            case JAXRS.Configuration.HOST:
+                return "localhost";
+            case JAXRS.Configuration.PORT:
+                return JAXRS.Configuration.FREE_PORT;
+            case JAXRS.Configuration.ROOT_PATH:
+                return "/";
+            case JAXRS.Configuration.SSL_CLIENT_AUTHENTICATION:
+                return SSLClientAuthentication.NONE;
+            case JAXRS.Configuration.SSL_CONTEXT:
+                try {
+                    return SSLContext.getDefault();
+                } catch (final NoSuchAlgorithmException e) {
+                    throw new RuntimeException(e);
+                }
+            case ServerProperties.AUTO_START:
+                return TRUE;
+            default:
+                return null;
+            }
+        };
+
+        // when
+        final Server server = serverProvider.createServer(Server.class, application, configuration);
+
+        // then
+        assertThat(server.port(), is(greaterThan(0)));
+    }
+
+}
diff --git a/containers/netty-http/pom.xml b/containers/netty-http/pom.xml
index a604811..e8deb20 100644
--- a/containers/netty-http/pom.xml
+++ b/containers/netty-http/pom.xml
@@ -42,6 +42,10 @@
             <artifactId>jersey-netty-connector</artifactId>
             <version>${project.version}</version>
         </dependency>
+        <dependency>
+            <groupId>org.hamcrest</groupId>
+            <artifactId>hamcrest-library</artifactId>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/NettyHttpContainerProvider.java b/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/NettyHttpContainerProvider.java
index 124b36e..85d7b96 100644
--- a/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/NettyHttpContainerProvider.java
+++ b/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/NettyHttpContainerProvider.java
@@ -58,7 +58,7 @@
      * Create and start Netty server.
      *
      * @param baseUri       base uri.
-     * @param configuration Jersey configuration.
+     * @param container     Jersey container.
      * @param sslContext    Netty SSL context (can be null).
      * @param block         when {@code true}, this method will block until the server is stopped. When {@code false}, the
      *                      execution will
@@ -66,25 +66,59 @@
      * @return Netty channel instance.
      * @throws ProcessingException when there is an issue with creating new container.
      */
-    public static Channel createServer(final URI baseUri, final ResourceConfig configuration, SslContext sslContext,
+    public static Channel createServer(final URI baseUri, final NettyHttpContainer container, SslContext sslContext,
                                        final boolean block)
             throws ProcessingException {
 
+        final ServerBootstrap serverBootstrap = createServerBootstrap(baseUri, container, sslContext);
+        return startServer(getPort(baseUri), container, serverBootstrap, block);
+    }
+
+    /**
+     * Create but not start Netty server.
+     *
+     * @param baseUri       base uri.
+     * @param container     Jersey container.
+     * @param sslContext    Netty SSL context (can be null).
+     * @return Netty bootstrap instance.
+     * @throws ProcessingException when there is an issue with creating new container.
+     */
+    static ServerBootstrap createServerBootstrap(final URI baseUri, final NettyHttpContainer container, SslContext sslContext) {
+
         // Configure the server.
         final EventLoopGroup bossGroup = new NioEventLoopGroup(1);
         final EventLoopGroup workerGroup = new NioEventLoopGroup();
-        final NettyHttpContainer container = new NettyHttpContainer(configuration);
+
+        ServerBootstrap b = new ServerBootstrap();
+        b.option(ChannelOption.SO_BACKLOG, 1024);
+        b.group(bossGroup, workerGroup)
+         .channel(NioServerSocketChannel.class)
+         .childHandler(new JerseyServerInitializer(baseUri, sslContext, container));
+
+        return b;
+    }
+
+    /**
+     * Start Netty server.
+     *
+     * @param port          IP port to listen.
+     * @param container     Jersey container.
+     * @param serverBootstrap Netty server bootstrap (i. e. prepared but unstarted server instance)
+     * @param block         when {@code true}, this method will block until the server is stopped. When {@code false}, the
+     *                      execution will
+     *                      end immediately after the server is started.
+     * @return Netty channel instance.
+     * @throws ProcessingException when there is an issue with creating new container.
+     */
+    static Channel startServer(final int port, final NettyHttpContainer container, final ServerBootstrap serverBootstrap,
+                                       final boolean block)
+            throws ProcessingException {
 
         try {
-            ServerBootstrap b = new ServerBootstrap();
-            b.option(ChannelOption.SO_BACKLOG, 1024);
-            b.group(bossGroup, workerGroup)
-             .channel(NioServerSocketChannel.class)
-             .childHandler(new JerseyServerInitializer(baseUri, sslContext, container));
+            final EventLoopGroup bossGroup = serverBootstrap.config().group();
+            final EventLoopGroup workerGroup = serverBootstrap.config().childGroup();
 
-            int port = getPort(baseUri);
-
-            Channel ch = b.bind(port).sync().channel();
+            Channel ch = serverBootstrap.bind(port).sync().channel();
 
             ch.closeFuture().addListener(new GenericFutureListener<Future<? super Void>>() {
                 @Override
@@ -112,6 +146,24 @@
      *
      * @param baseUri       base uri.
      * @param configuration Jersey configuration.
+     * @param sslContext    Netty SSL context (can be null).
+     * @param block         when {@code true}, this method will block until the server is stopped. When {@code false}, the
+     *                      execution will
+     *                      end immediately after the server is started.
+     * @return Netty channel instance.
+     * @throws ProcessingException when there is an issue with creating new container.
+     */
+    public static Channel createServer(final URI baseUri, final ResourceConfig configuration, SslContext sslContext,
+                                       final boolean block)
+            throws ProcessingException {
+        return createServer(baseUri, new NettyHttpContainer(configuration), sslContext, block);
+    }
+
+    /**
+     * Create and start Netty server.
+     *
+     * @param baseUri       base uri.
+     * @param configuration Jersey configuration.
      * @param block         when {@code true}, this method will block until the server is stopped. When {@code false}, the
      *                      execution will
      *                      end immediately after the server is started.
@@ -172,7 +224,7 @@
         }
     }
 
-    private static int getPort(URI uri) {
+    static int getPort(URI uri) {
         if (uri.getPort() == -1) {
             if ("http".equalsIgnoreCase(uri.getScheme())) {
                 return 80;
diff --git a/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/NettyHttpServer.java b/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/NettyHttpServer.java
new file mode 100644
index 0000000..ee45da3
--- /dev/null
+++ b/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/NettyHttpServer.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2018 Markus KARG. 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.httpserver;
+
+import static java.lang.Boolean.TRUE;
+
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+
+import javax.net.ssl.SSLContext;
+import javax.ws.rs.JAXRS;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.UriBuilder;
+
+import org.glassfish.jersey.server.ServerProperties;
+import org.glassfish.jersey.server.spi.Server;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.Channel;
+import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.JdkSslContext;
+
+/**
+ * Jersey {@code Server} implementation based on Netty {@link Channel}.
+ *
+ * @author Markus KARG (markus@headcrashing.eu)
+ * @since 2.30
+ */
+public final class NettyHttpServer implements Server {
+
+    private final NettyHttpContainer container;
+
+    private final ServerBootstrap serverBootstrap;
+
+    private volatile Channel channel;
+
+    private final int port;
+
+    NettyHttpServer(final Application application, final JAXRS.Configuration configuration) {
+        final String protocol = configuration.protocol();
+        final String host = configuration.host();
+        final int port = configuration.port();
+        final String rootPath = configuration.rootPath();
+        final SSLContext sslContext = configuration.sslContext();
+        final JAXRS.Configuration.SSLClientAuthentication sslClientAuthentication = configuration
+                .sslClientAuthentication();
+        final boolean autoStart = Optional.ofNullable((Boolean) configuration.property(ServerProperties.AUTO_START))
+                .orElse(TRUE);
+        final URI uri = UriBuilder.newInstance().scheme(protocol.toLowerCase()).host(host).port(port).path(rootPath)
+                .build();
+        this.port = NettyHttpContainerProvider.getPort(uri);
+
+        this.container = new NettyHttpContainer(application);
+        this.serverBootstrap = NettyHttpContainerProvider.createServerBootstrap(uri, this.container,
+                "HTTPS".equals(protocol)
+                        ? new JdkSslContext(sslContext, false, nettyClientAuth(sslClientAuthentication))
+                        : null);
+
+        if (autoStart) {
+            this.channel = NettyHttpContainerProvider.startServer(port, this.container, this.serverBootstrap, false);
+        }
+    }
+
+    private static final ClientAuth nettyClientAuth(
+            final JAXRS.Configuration.SSLClientAuthentication sslClientAuthentication) {
+        switch (sslClientAuthentication) {
+        case MANDATORY:
+            return ClientAuth.REQUIRE;
+        case OPTIONAL:
+            return ClientAuth.OPTIONAL;
+        default:
+            return ClientAuth.NONE;
+        }
+    }
+
+    @Override
+    public final NettyHttpContainer container() {
+        return this.container;
+    }
+
+    @Override
+    public final int port() {
+        return this.channel == null ? this.port : ((InetSocketAddress) this.channel.localAddress()).getPort();
+    }
+
+    @Override
+    public final CompletableFuture<Object> start() {
+        return this.channel != null ? CompletableFuture.completedFuture(this.channel)
+                : CompletableFuture.supplyAsync(() -> {
+                    try {
+                        this.channel = NettyHttpContainerProvider.startServer(this.port, this.container,
+                                this.serverBootstrap, false);
+                        return this.channel;
+                    } catch (final Exception e) {
+                        throw new CompletionException(e);
+                    }
+                });
+    }
+
+    @Override
+    public final CompletableFuture<Void> stop() {
+        return this.channel == null ? CompletableFuture.completedFuture(null) : CompletableFuture.supplyAsync(() -> {
+            try {
+                return this.channel.close().get();
+            } catch (final Exception e) {
+                throw new CompletionException(e);
+            }
+        });
+    }
+
+    @Override
+    public final <T> T unwrap(final Class<T> nativeClass) {
+        return nativeClass.cast(this.channel == null ? this.serverBootstrap : this.channel);
+    }
+
+}
diff --git a/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/NettyHttpServerProvider.java b/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/NettyHttpServerProvider.java
new file mode 100644
index 0000000..7238988
--- /dev/null
+++ b/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/NettyHttpServerProvider.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2018 Markus KARG. 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.httpserver;
+
+import javax.ws.rs.JAXRS;
+import javax.ws.rs.core.Application;
+
+import org.glassfish.jersey.server.spi.Server;
+import org.glassfish.jersey.server.spi.ServerProvider;
+
+import io.netty.channel.Channel;
+
+/**
+ * Server provider for servers based on Netty {@link Channel}.
+ *
+ * @author Markus KARG (markus@headcrashing.eu)
+ * @since 2.30
+ */
+public final class NettyHttpServerProvider implements ServerProvider {
+
+    @Override
+    public final <T extends Server> T createServer(final Class<T> type, final Application application,
+            final JAXRS.Configuration configuration) {
+        return NettyHttpServer.class == type || Server.class == type
+                ? type.cast(new NettyHttpServer(application, configuration))
+                : null;
+    }
+}
diff --git a/containers/netty-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ServerProvider b/containers/netty-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ServerProvider
new file mode 100644
index 0000000..61e6e1b
--- /dev/null
+++ b/containers/netty-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ServerProvider
@@ -0,0 +1 @@
+org.glassfish.jersey.netty.httpserver.NettyHttpServerProvider
\ No newline at end of file
diff --git a/containers/netty-http/src/test/java/org/glassfish/jersey/netty/httpserver/NettyHttpServerProviderTest.java b/containers/netty-http/src/test/java/org/glassfish/jersey/netty/httpserver/NettyHttpServerProviderTest.java
new file mode 100644
index 0000000..951ad28
--- /dev/null
+++ b/containers/netty-http/src/test/java/org/glassfish/jersey/netty/httpserver/NettyHttpServerProviderTest.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (c) 2018 Markus KARG. 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.httpserver;
+
+import static java.lang.Boolean.FALSE;
+import static java.lang.Boolean.TRUE;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.junit.Assert.assertThat;
+
+import java.security.AccessController;
+import java.security.NoSuchAlgorithmException;
+import java.util.Collections;
+import java.util.Set;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.ExecutionException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.net.ssl.SSLContext;
+import javax.ws.rs.GET;
+import javax.ws.rs.JAXRS;
+import javax.ws.rs.JAXRS.Configuration.SSLClientAuthentication;
+import javax.ws.rs.Path;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.UriBuilder;
+
+import org.glassfish.jersey.internal.util.PropertiesHelper;
+import org.glassfish.jersey.server.ServerProperties;
+import org.glassfish.jersey.server.spi.Container;
+import org.glassfish.jersey.server.spi.Server;
+import org.glassfish.jersey.server.spi.ServerProvider;
+import org.junit.Test;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.Channel;
+
+/**
+ * Unit tests for {@link NettyHttpServerProvider}.
+ *
+ * @author Markus KARG (markus@headcrashing.eu)
+ * @since 2.30
+ */
+public final class NettyHttpServerProviderTest {
+
+    @Test(timeout = 15000)
+    public final void shouldProvideServer() throws InterruptedException, ExecutionException {
+        // given
+        final ServerProvider serverProvider = new NettyHttpServerProvider();
+        final Resource resource = new Resource();
+        final Application application = new Application() {
+            @Override
+            public final Set<Object> getSingletons() {
+                return Collections.singleton(resource);
+            }
+        };
+        final JAXRS.Configuration configuration = name -> {
+            switch (name) {
+            case JAXRS.Configuration.PROTOCOL:
+                return "HTTP";
+            case JAXRS.Configuration.HOST:
+                return "localhost";
+            case JAXRS.Configuration.PORT:
+                return getPort();
+            case JAXRS.Configuration.ROOT_PATH:
+                return "/";
+            case JAXRS.Configuration.SSL_CLIENT_AUTHENTICATION:
+                return SSLClientAuthentication.NONE;
+            case JAXRS.Configuration.SSL_CONTEXT:
+                try {
+                    return SSLContext.getDefault();
+                } catch (final NoSuchAlgorithmException e) {
+                    throw new RuntimeException(e);
+                }
+            case ServerProperties.AUTO_START:
+                return FALSE;
+            default:
+                return null;
+            }
+        };
+
+        // when
+        final Server server = serverProvider.createServer(Server.class, application, configuration);
+        final Object nativeHandle = server.unwrap(Object.class);
+        final CompletionStage<?> start = server.start();
+        final Object startResult = start.toCompletableFuture().get();
+        final Container container = server.container();
+        final int port = server.port();
+        final String entity = ClientBuilder.newClient()
+                .target(UriBuilder.newInstance().scheme("http").host("localhost").port(port).build()).request()
+                .get(String.class);
+        final CompletionStage<?> stop = server.stop();
+        final Object stopResult = stop.toCompletableFuture().get();
+
+        // then
+        assertThat(server, is(instanceOf(NettyHttpServer.class)));
+        assertThat(nativeHandle, is(instanceOf(ServerBootstrap.class)));
+        assertThat(startResult, is(instanceOf(Channel.class)));
+        assertThat(container, is(instanceOf(NettyHttpContainer.class)));
+        assertThat(port, is(greaterThan(0)));
+        assertThat(entity, is(resource.toString()));
+        assertThat(stopResult, is(nullValue()));
+    }
+
+    @Path("/")
+    protected static final class Resource {
+        @GET
+        @Override
+        public final String toString() {
+            return super.toString();
+        }
+    }
+    private static final Logger LOGGER = Logger.getLogger(NettyHttpServerProviderTest.class.getName());
+
+    private static final int DEFAULT_PORT = 0;
+
+    private static final int getPort() {
+        final String value = AccessController
+                .doPrivileged(PropertiesHelper.getSystemProperty("jersey.config.test.container.port"));
+        if (value != null) {
+            try {
+                final int i = Integer.parseInt(value);
+                if (i < 0) {
+                    throw new NumberFormatException("Value is negative.");
+                }
+                return i;
+            } catch (final NumberFormatException e) {
+                LOGGER.log(Level.CONFIG,
+                        "Value of 'jersey.config.test.container.port'"
+                                + " property is not a valid non-negative integer [" + value + "]."
+                                + " Reverting to default [" + DEFAULT_PORT + "].",
+                        e);
+            }
+        }
+
+        return DEFAULT_PORT;
+    }
+
+    @Test(timeout = 15000)
+    public final void shouldScanFreePort() throws InterruptedException, ExecutionException {
+        // given
+        final ServerProvider serverProvider = new NettyHttpServerProvider();
+        final Application application = new Application();
+        final JAXRS.Configuration configuration = name -> {
+            switch (name) {
+            case JAXRS.Configuration.PROTOCOL:
+                return "HTTP";
+            case JAXRS.Configuration.HOST:
+                return "localhost";
+            case JAXRS.Configuration.PORT:
+                return JAXRS.Configuration.FREE_PORT;
+            case JAXRS.Configuration.ROOT_PATH:
+                return "/";
+            case JAXRS.Configuration.SSL_CLIENT_AUTHENTICATION:
+                return SSLClientAuthentication.NONE;
+            case JAXRS.Configuration.SSL_CONTEXT:
+                try {
+                    return SSLContext.getDefault();
+                } catch (final NoSuchAlgorithmException e) {
+                    throw new RuntimeException(e);
+                }
+            case ServerProperties.AUTO_START:
+                return TRUE;
+            default:
+                return null;
+            }
+        };
+
+        // when
+        final Server server = serverProvider.createServer(Server.class, application, configuration);
+
+        // then
+        assertThat(server.port(), is(greaterThan(0)));
+    }
+
+}
diff --git a/containers/simple-http/pom.xml b/containers/simple-http/pom.xml
index 7ef0351..b7c6f73 100644
--- a/containers/simple-http/pom.xml
+++ b/containers/simple-http/pom.xml
@@ -49,6 +49,10 @@
             <groupId>org.simpleframework</groupId>
             <artifactId>simple-common</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.hamcrest</groupId>
+            <artifactId>hamcrest-library</artifactId>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleContainerFactory.java b/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleContainerFactory.java
index a9dc46d..db8dcdf 100644
--- a/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleContainerFactory.java
+++ b/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleContainerFactory.java
@@ -21,17 +21,19 @@
 import java.net.InetSocketAddress;
 import java.net.SocketAddress;
 import java.net.URI;
-
-import javax.ws.rs.ProcessingException;
+import java.util.Optional;
 
 import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.ws.rs.JAXRS.Configuration.SSLClientAuthentication;
+import javax.ws.rs.ProcessingException;
 
 import org.glassfish.jersey.internal.util.collection.UnsafeValue;
 import org.glassfish.jersey.server.ResourceConfig;
 import org.glassfish.jersey.simple.internal.LocalizationMessages;
-
 import org.simpleframework.http.core.Container;
 import org.simpleframework.http.core.ContainerSocketProcessor;
+import org.simpleframework.transport.Socket;
 import org.simpleframework.transport.SocketProcessor;
 import org.simpleframework.transport.connect.Connection;
 import org.simpleframework.transport.connect.SocketConnection;
@@ -149,7 +151,55 @@
             public SocketProcessor get() throws IOException {
                 return new ContainerSocketProcessor(container);
             }
-        });
+        }, true);
+    }
+
+    /**
+     * Create a {@link Closeable} that registers an {@link Container} that in turn manages all root
+     * resource and provider classes found by searching the classes referenced in the java classpath.
+     *
+     * @param address   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 context   this is the SSL context used for SSL connections.
+     * @param config    the resource configuration.
+     * @param sslClientAuthentication Secure socket client authentication policy.
+     * @param start whether the server shall listen to connections immediately
+     * @return the closeable connection, with the endpoint started.
+     * @throws ProcessingException      thrown when problems during server creation.
+     * @throws IllegalArgumentException if {@code address} is {@code null}.
+     */
+    public static SimpleServer create(final URI address, final SSLContext context,
+            final SSLClientAuthentication sslClientAuthentication, final SimpleContainer container, final boolean start) {
+        return _create(address, context, container, new UnsafeValue<SocketProcessor, IOException>() {
+            @Override
+            public SocketProcessor get() throws IOException {
+                return new ContainerSocketProcessor(container) {
+                    @Override
+                    public final void process(final Socket socket) throws IOException {
+                        final SSLEngine sslEngine = socket.getEngine();
+                        if (sslEngine != null) {
+                            switch (sslClientAuthentication) {
+                            case MANDATORY: {
+                                sslEngine.setNeedClientAuth(true);
+                                break;
+                            }
+                            case OPTIONAL: {
+                                sslEngine.setWantClientAuth(true);
+                                break;
+                            }
+                            default: {
+                                sslEngine.setNeedClientAuth(false);
+                                break;
+                            }
+                            }
+                        }
+                        super.process(socket);
+                    }
+                };
+            }
+        }, start);
     }
 
     /**
@@ -201,12 +251,13 @@
             public SocketProcessor get() throws IOException {
                 return new ContainerSocketProcessor(container, count, select);
             }
-        });
+        }, true);
     }
 
     private static SimpleServer _create(final URI address, final SSLContext context,
                                         final SimpleContainer container,
-                                        final UnsafeValue<SocketProcessor, IOException> serverProvider)
+                                        final UnsafeValue<SocketProcessor, IOException> serverProvider,
+                                        final boolean start)
             throws ProcessingException {
         if (address == null) {
             throw new IllegalArgumentException(LocalizationMessages.URI_CANNOT_BE_NULL());
@@ -236,22 +287,26 @@
             final SocketProcessor server = serverProvider.get();
             connection = new SocketConnection(server, analyzer);
 
+            final SimpleServer simpleServer = new SimpleServer() {
+                private InetSocketAddress socketAddr = listen;
 
-            final SocketAddress socketAddr = connection.connect(listen, context);
-            container.onServerStart();
-
-            return new SimpleServer() {
+                @Override
+                public final void start() throws IOException {
+                    this.socketAddr = (InetSocketAddress) connection.connect(listen, context);
+                    container.onServerStart();
+                }
 
                 @Override
                 public void close() throws IOException {
                     container.onServerStop();
                     analyzer.stop();
                     connection.close();
+                    this.socketAddr = listen;
                 }
 
                 @Override
                 public int getPort() {
-                    return ((InetSocketAddress) socketAddr).getPort();
+                    return this.socketAddr.getPort();
                 }
 
                 @Override
@@ -268,6 +323,12 @@
                     }
                 }
             };
+
+            if (start) {
+                simpleServer.start();
+            }
+
+            return simpleServer;
         } catch (final IOException ex) {
             throw new ProcessingException(LocalizationMessages.ERROR_WHEN_CREATING_SERVER(), ex);
         }
diff --git a/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleHttpServer.java b/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleHttpServer.java
new file mode 100644
index 0000000..4176fbb
--- /dev/null
+++ b/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleHttpServer.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2018 Markus KARG. 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.simple;
+
+import static java.lang.Boolean.TRUE;
+
+import java.net.URI;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+
+import javax.net.ssl.SSLContext;
+import javax.ws.rs.JAXRS;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.UriBuilder;
+
+import org.glassfish.jersey.server.ServerProperties;
+import org.glassfish.jersey.server.spi.Server;
+
+/**
+ * Jersey {@code Server} implementation based on Simple framework
+ * {@link SimpleServer}.
+ *
+ * @author Markus KARG (markus@headcrashing.eu)
+ * @since 2.30
+ */
+public final class SimpleHttpServer implements Server {
+
+    private final SimpleContainer container;
+
+    private final SimpleServer simpleServer;
+
+    SimpleHttpServer(final Application application, final JAXRS.Configuration configuration) {
+        final String protocol = configuration.protocol();
+        final String host = configuration.host();
+        final int port = configuration.port();
+        final String rootPath = configuration.rootPath();
+        final SSLContext sslContext = configuration.sslContext();
+        final JAXRS.Configuration.SSLClientAuthentication sslClientAuthentication = configuration
+                .sslClientAuthentication();
+        final boolean autoStart = Optional.ofNullable((Boolean) configuration.property(ServerProperties.AUTO_START))
+                .orElse(TRUE);
+        final URI uri = UriBuilder.newInstance().scheme(protocol.toLowerCase()).host(host).port(port).path(rootPath)
+                .build();
+
+        this.container = new SimpleContainer(application);
+        this.simpleServer = SimpleContainerFactory.create(uri, "HTTPS".equals(protocol) ? sslContext : null,
+                sslClientAuthentication, this.container, autoStart);
+    }
+
+    @Override
+    public final SimpleContainer container() {
+        return this.container;
+    }
+
+    @Override
+    public final int port() {
+        return this.simpleServer.getPort();
+    }
+
+    @Override
+    public final CompletableFuture<Void> start() {
+        return CompletableFuture.runAsync(() -> {
+            try {
+                this.simpleServer.start();
+            } catch (final Exception e) {
+                throw new CompletionException(e);
+            }
+        });
+    }
+
+    @Override
+    public final CompletableFuture<Void> stop() {
+        return CompletableFuture.runAsync(() -> {
+            try {
+                this.simpleServer.close();
+            } catch (final Exception e) {
+                throw new CompletionException(e);
+            }
+        });
+    }
+
+    @Override
+    public final <T> T unwrap(final Class<T> nativeClass) {
+        return nativeClass.cast(this.simpleServer);
+    }
+
+}
diff --git a/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleHttpServerProvider.java b/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleHttpServerProvider.java
new file mode 100644
index 0000000..70b40b9
--- /dev/null
+++ b/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleHttpServerProvider.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2018 Markus KARG. 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.simple;
+
+import javax.ws.rs.JAXRS;
+import javax.ws.rs.core.Application;
+
+import org.glassfish.jersey.server.spi.Server;
+import org.glassfish.jersey.server.spi.ServerProvider;
+
+/**
+ * Server provider for servers based on Simple framework {@link SimpleServer}.
+ *
+ * @author Markus KARG (markus@headcrashing.eu)
+ * @since 2.30
+ */
+public final class SimpleHttpServerProvider implements ServerProvider {
+
+    @Override
+    public final <T extends Server> T createServer(final Class<T> type, final Application application,
+            final JAXRS.Configuration configuration) {
+        return SimpleHttpServer.class == type || Server.class == type
+                ? type.cast(new SimpleHttpServer(application, configuration))
+                : null;
+    }
+}
diff --git a/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleServer.java b/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleServer.java
index 02aec75..b644f7f 100644
--- a/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleServer.java
+++ b/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleServer.java
@@ -17,6 +17,7 @@
 package org.glassfish.jersey.simple;
 
 import java.io.Closeable;
+import java.io.IOException;
 
 /**
  * Simple server facade providing convenient methods to obtain info about the server (i.e. port).
@@ -26,6 +27,8 @@
  */
 public interface SimpleServer extends Closeable {
 
+    public void start() throws IOException;
+
     /**
      * The port the server is listening to for incomming HTTP connections. If the port is not
      * specified the {@link org.glassfish.jersey.server.spi.Container.DEFAULT_PORT} is used.
diff --git a/containers/simple-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ServerProvider b/containers/simple-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ServerProvider
new file mode 100644
index 0000000..54d8c53
--- /dev/null
+++ b/containers/simple-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ServerProvider
@@ -0,0 +1 @@
+org.glassfish.jersey.simple.SimpleHttpServerProvider
\ No newline at end of file
diff --git a/containers/simple-http/src/test/java/org/glassfish/jersey/simple/AbstractSimpleServerTester.java b/containers/simple-http/src/test/java/org/glassfish/jersey/simple/AbstractSimpleServerTester.java
index eb653ca..9ce8094 100644
--- a/containers/simple-http/src/test/java/org/glassfish/jersey/simple/AbstractSimpleServerTester.java
+++ b/containers/simple-http/src/test/java/org/glassfish/jersey/simple/AbstractSimpleServerTester.java
@@ -39,7 +39,7 @@
 public abstract class AbstractSimpleServerTester {
 
     public static final String CONTEXT = "";
-    private final int DEFAULT_PORT = 9998;
+    private final int DEFAULT_PORT = 0;
 
     private static final Logger LOGGER = Logger.getLogger(AbstractSimpleServerTester.class.getName());
 
@@ -49,21 +49,25 @@
      * @return The HTTP port of the URI
      */
     protected final int getPort() {
+        if (server != null) {
+            return server.getPort();
+        }
+
         final String value = AccessController
                 .doPrivileged(PropertiesHelper.getSystemProperty("jersey.config.test.container.port"));
         if (value != null) {
 
             try {
                 final int i = Integer.parseInt(value);
-                if (i <= 0) {
-                    throw new NumberFormatException("Value not positive.");
+                if (i < 0) {
+                    throw new NumberFormatException("Value is negative.");
                 }
                 return i;
             } catch (NumberFormatException e) {
                 LOGGER.log(
                         Level.CONFIG,
                         "Value of 'jersey.config.test.container.port'"
-                        + " property is not a valid positive integer [" + value + "]."
+                        + " property is not a valid non-negative integer [" + value + "]."
                         + " Reverting to default [" + DEFAULT_PORT + "].",
                         e);
             }
@@ -83,28 +87,28 @@
         config.register(LoggingFeature.class);
         final URI baseUri = getBaseUri();
         server = SimpleContainerFactory.create(baseUri, config);
-        LOGGER.log(Level.INFO, "Simple-http server started on base uri: " + baseUri);
+        LOGGER.log(Level.INFO, "Simple-http server started on base uri: " + getBaseUri());
     }
 
     public void startServerNoLoggingFilter(Class... resources) {
         ResourceConfig config = new ResourceConfig(resources);
         final URI baseUri = getBaseUri();
         server = SimpleContainerFactory.create(baseUri, config);
-        LOGGER.log(Level.INFO, "Simple-http server started on base uri: " + baseUri);
+        LOGGER.log(Level.INFO, "Simple-http server started on base uri: " + getBaseUri());
     }
 
     public void startServer(ResourceConfig config) {
         final URI baseUri = getBaseUri();
         config.register(LoggingFeature.class);
         server = SimpleContainerFactory.create(baseUri, config);
-        LOGGER.log(Level.INFO, "Simple-http server started on base uri: " + baseUri);
+        LOGGER.log(Level.INFO, "Simple-http server started on base uri: " + getBaseUri());
     }
 
     public void startServer(ResourceConfig config, int count, int select) {
         final URI baseUri = getBaseUri();
         config.register(LoggingFeature.class);
         server = SimpleContainerFactory.create(baseUri, config, count, select);
-        LOGGER.log(Level.INFO, "Simple-http server started on base uri: " + baseUri);
+        LOGGER.log(Level.INFO, "Simple-http server started on base uri: " + getBaseUri());
     }
 
     public URI getBaseUri() {
diff --git a/containers/simple-http/src/test/java/org/glassfish/jersey/simple/SimpleHttpServerProviderTest.java b/containers/simple-http/src/test/java/org/glassfish/jersey/simple/SimpleHttpServerProviderTest.java
new file mode 100644
index 0000000..fe483a0
--- /dev/null
+++ b/containers/simple-http/src/test/java/org/glassfish/jersey/simple/SimpleHttpServerProviderTest.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) 2018 Markus KARG. 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.simple;
+
+import static java.lang.Boolean.FALSE;
+import static java.lang.Boolean.TRUE;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.junit.Assert.assertThat;
+
+import java.security.AccessController;
+import java.security.NoSuchAlgorithmException;
+import java.util.Collections;
+import java.util.Set;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.ExecutionException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.net.ssl.SSLContext;
+import javax.ws.rs.GET;
+import javax.ws.rs.JAXRS;
+import javax.ws.rs.JAXRS.Configuration.SSLClientAuthentication;
+import javax.ws.rs.Path;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.UriBuilder;
+
+import org.glassfish.jersey.internal.util.PropertiesHelper;
+import org.glassfish.jersey.server.ServerProperties;
+import org.glassfish.jersey.server.spi.Container;
+import org.glassfish.jersey.server.spi.Server;
+import org.glassfish.jersey.server.spi.ServerProvider;
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link SimpleHttpServerProvider}.
+ *
+ * @author Markus KARG (markus@headcrashing.eu)
+ * @since 2.30
+ */
+public final class SimpleHttpServerProviderTest {
+
+    @Test(timeout = 15000)
+    public final void shouldProvideServer() throws InterruptedException, ExecutionException {
+        // given
+        final ServerProvider serverProvider = new SimpleHttpServerProvider();
+        final Resource resource = new Resource();
+        final Application application = new Application() {
+            @Override
+            public final Set<Object> getSingletons() {
+                return Collections.singleton(resource);
+            }
+        };
+        final JAXRS.Configuration configuration = name -> {
+            switch (name) {
+            case JAXRS.Configuration.PROTOCOL:
+                return "HTTP";
+            case JAXRS.Configuration.HOST:
+                return "localhost";
+            case JAXRS.Configuration.PORT:
+                return getPort();
+            case JAXRS.Configuration.ROOT_PATH:
+                return "/";
+            case JAXRS.Configuration.SSL_CLIENT_AUTHENTICATION:
+                return SSLClientAuthentication.NONE;
+            case JAXRS.Configuration.SSL_CONTEXT:
+                try {
+                    return SSLContext.getDefault();
+                } catch (final NoSuchAlgorithmException e) {
+                    throw new RuntimeException(e);
+                }
+            case ServerProperties.AUTO_START:
+                return FALSE;
+            default:
+                return null;
+            }
+        };
+
+        // when
+        final Server server = serverProvider.createServer(Server.class, application, configuration);
+        final Object nativeHandle = server.unwrap(Object.class);
+        final CompletionStage<?> start = server.start();
+        final Object startResult = start.toCompletableFuture().get();
+        final Container container = server.container();
+        final int port = server.port();
+        final String entity = ClientBuilder.newClient()
+                .target(UriBuilder.newInstance().scheme("http").host("localhost").port(port).build()).request()
+                .get(String.class);
+        final CompletionStage<?> stop = server.stop();
+        final Object stopResult = stop.toCompletableFuture().get();
+
+        // then
+        assertThat(server, is(instanceOf(SimpleHttpServer.class)));
+        assertThat(nativeHandle, is(instanceOf(SimpleServer.class)));
+        assertThat(startResult, is(nullValue()));
+        assertThat(container, is(instanceOf(SimpleContainer.class)));
+        assertThat(port, is(greaterThan(0)));
+        assertThat(entity, is(resource.toString()));
+        assertThat(stopResult, is(nullValue()));
+    }
+
+    @Path("/")
+    protected static final class Resource {
+        @GET
+        @Override
+        public final String toString() {
+            return super.toString();
+        }
+    }
+
+    private static final Logger LOGGER = Logger.getLogger(SimpleHttpServerProviderTest.class.getName());
+
+    private static final int DEFAULT_PORT = 0;
+
+    private static final int getPort() {
+        final String value = AccessController
+                .doPrivileged(PropertiesHelper.getSystemProperty("jersey.config.test.container.port"));
+        if (value != null) {
+            try {
+                final int i = Integer.parseInt(value);
+                if (i < 0) {
+                    throw new NumberFormatException("Value is negative.");
+                }
+                return i;
+            } catch (final NumberFormatException e) {
+                LOGGER.log(Level.CONFIG,
+                        "Value of 'jersey.config.test.container.port'"
+                                + " property is not a valid non-negative integer [" + value + "]."
+                                + " Reverting to default [" + DEFAULT_PORT + "].",
+                        e);
+            }
+        }
+
+        return DEFAULT_PORT;
+    }
+
+    @Test(timeout = 15000)
+    public final void shouldScanFreePort() throws InterruptedException, ExecutionException {
+        // given
+        final ServerProvider serverProvider = new SimpleHttpServerProvider();
+        final Application application = new Application();
+        final JAXRS.Configuration configuration = name -> {
+            switch (name) {
+            case JAXRS.Configuration.PROTOCOL:
+                return "HTTP";
+            case JAXRS.Configuration.HOST:
+                return "localhost";
+            case JAXRS.Configuration.PORT:
+                return JAXRS.Configuration.FREE_PORT;
+            case JAXRS.Configuration.ROOT_PATH:
+                return "/";
+            case JAXRS.Configuration.SSL_CLIENT_AUTHENTICATION:
+                return SSLClientAuthentication.NONE;
+            case JAXRS.Configuration.SSL_CONTEXT:
+                try {
+                    return SSLContext.getDefault();
+                } catch (final NoSuchAlgorithmException e) {
+                    throw new RuntimeException(e);
+                }
+            case ServerProperties.AUTO_START:
+                return TRUE;
+            default:
+                return null;
+            }
+        };
+
+        // when
+        final Server server = serverProvider.createServer(Server.class, application, configuration);
+
+        // then
+        assertThat(server.port(), is(greaterThan(0)));
+    }
+
+}
diff --git a/core-server/src/main/java/org/glassfish/jersey/server/ServerFactory.java b/core-server/src/main/java/org/glassfish/jersey/server/ServerFactory.java
new file mode 100644
index 0000000..9bd4656
--- /dev/null
+++ b/core-server/src/main/java/org/glassfish/jersey/server/ServerFactory.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2018 Markus KARG. 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.server;
+
+import javax.ws.rs.JAXRS;
+import javax.ws.rs.ProcessingException;
+import javax.ws.rs.core.Application;
+
+import org.glassfish.jersey.internal.ServiceFinder;
+import org.glassfish.jersey.server.spi.Server;
+import org.glassfish.jersey.server.spi.ServerProvider;
+
+/**
+ * Factory for creating specific HTTP servers.
+ *
+ * @author Markus KARG (markus@headcrashing.eu)
+ * @since 2.30
+ */
+public final class ServerFactory {
+
+    /**
+     * Prevents instantiation.
+     */
+    private ServerFactory() {
+    }
+
+    /**
+     * Creates a server of a given type which runs the given application using the
+     * given bootstrap configuration.
+     * <p>
+     * The list of service-providers supporting the {@link ServerProvider}
+     * service-provider will be iterated over until one returns a non-null server
+     * instance.
+     * <p>
+     *
+     * @param <T>
+     *            the type of the server.
+     * @param type
+     *            the type of the server. Providers SHOULD support at least
+     *            {@link Server}.
+     * @param application
+     *            The application to host.
+     * @param configuration
+     *            The configuration (host, port, etc.) to be used for bootstrapping.
+     * @return the created server.
+     * @throws ProcessingException
+     *             if there is an error creating the server.
+     * @throws IllegalArgumentException
+     *             if no server provider supports the type.
+     */
+    public static <T extends Server> T createServer(final Class<T> type, final Application application,
+            final JAXRS.Configuration configuration) {
+        for (final ServerProvider serverProvider : ServiceFinder.find(ServerProvider.class)) {
+            final T server = serverProvider.createServer(type, application, configuration);
+            if (server != null) {
+                return server;
+            }
+        }
+
+        throw new IllegalArgumentException("No server provider supports the type " + type);
+    }
+
+}
diff --git a/core-server/src/main/java/org/glassfish/jersey/server/ServerProperties.java b/core-server/src/main/java/org/glassfish/jersey/server/ServerProperties.java
index fafb73a..3696193 100644
--- a/core-server/src/main/java/org/glassfish/jersey/server/ServerProperties.java
+++ b/core-server/src/main/java/org/glassfish/jersey/server/ServerProperties.java
@@ -24,6 +24,7 @@
 import org.glassfish.jersey.internal.util.PropertiesClass;
 import org.glassfish.jersey.internal.util.PropertiesHelper;
 import org.glassfish.jersey.internal.util.PropertyAlias;
+import org.glassfish.jersey.server.spi.Server;
 
 
 /**
@@ -38,6 +39,23 @@
 public final class ServerProperties {
 
     /**
+     * Defines the implementation of {@link Server} to bootstrap.
+     * <p>
+     * By default auto-selects the first server provider found.
+     * </p>
+     */
+    public static final String HTTP_SERVER_CLASS = "jersey.config.server.httpServerClass";
+
+    /**
+     * Whether to automatically startup {@link Server} at bootstrap.
+     * <p>
+     * By default, servers are immediately listening to connections after bootstrap,
+     * so no explicit invocation of {@link Server#start()} is needed.
+     * </p>
+     */
+    public static final String AUTO_START = "jersey.config.server.autoStart";
+
+    /**
      * Defines one or more packages that contain application-specific resources and
      * providers.
      *
diff --git a/core-server/src/main/java/org/glassfish/jersey/server/internal/RuntimeDelegateImpl.java b/core-server/src/main/java/org/glassfish/jersey/server/internal/RuntimeDelegateImpl.java
index f4fe50c..82ab90b 100644
--- a/core-server/src/main/java/org/glassfish/jersey/server/internal/RuntimeDelegateImpl.java
+++ b/core-server/src/main/java/org/glassfish/jersey/server/internal/RuntimeDelegateImpl.java
@@ -16,8 +16,21 @@
 
 package org.glassfish.jersey.server.internal;
 
-import java.util.concurrent.CompletionStage;
+import static java.lang.Boolean.TRUE;
 
+import java.security.NoSuchAlgorithmException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.function.BiFunction;
+
+import javax.net.ssl.SSLContext;
+import javax.ws.rs.JAXRS;
+import javax.ws.rs.JAXRS.Configuration;
+import javax.ws.rs.JAXRS.Configuration.Builder;
+import javax.ws.rs.JAXRS.Configuration.SSLClientAuthentication;
 import javax.ws.rs.core.Application;
 import javax.ws.rs.JAXRS;
 import javax.ws.rs.JAXRS.Instance;
@@ -25,11 +38,16 @@
 import org.glassfish.jersey.internal.AbstractRuntimeDelegate;
 import org.glassfish.jersey.message.internal.MessagingBinders;
 import org.glassfish.jersey.server.ContainerFactory;
+import org.glassfish.jersey.server.ServerFactory;
+import org.glassfish.jersey.server.ServerProperties;
+import org.glassfish.jersey.server.spi.Server;
 
 /**
  * Server-side implementation of JAX-RS {@link javax.ws.rs.ext.RuntimeDelegate}.
- * This overrides the default implementation of {@link javax.ws.rs.ext.RuntimeDelegate} from
- * jersey-common which does not implement {@link #createEndpoint(javax.ws.rs.core.Application, java.lang.Class)}
+ * This overrides the default implementation of
+ * {@link javax.ws.rs.ext.RuntimeDelegate} from jersey-common which does not
+ * implement
+ * {@link #createEndpoint(javax.ws.rs.core.Application, java.lang.Class)}
  * method.
  *
  * @author Jakub Podlesak
@@ -43,7 +61,7 @@
     }
 
     @Override
-    public <T> T createEndpoint(Application application, Class<T> endpointType)
+    public <T> T createEndpoint(final Application application, final Class<T> endpointType)
             throws IllegalArgumentException, UnsupportedOperationException {
         if (application == null) {
             throw new IllegalArgumentException("application is null.");
@@ -51,14 +69,105 @@
         return ContainerFactory.createContainer(endpointType, application);
     }
 
-    @Override
-    public JAXRS.Configuration.Builder createConfigurationBuilder() {
-        throw new UnsupportedOperationException();
+    private static final Map<String, Class<?>> PROPERTY_TYPES = new HashMap<>();
+
+    static {
+        PROPERTY_TYPES.put(JAXRS.Configuration.PROTOCOL, String.class);
+        PROPERTY_TYPES.put(JAXRS.Configuration.HOST, String.class);
+        PROPERTY_TYPES.put(JAXRS.Configuration.PORT, Integer.class);
+        PROPERTY_TYPES.put(JAXRS.Configuration.ROOT_PATH, String.class);
+        PROPERTY_TYPES.put(JAXRS.Configuration.SSL_CONTEXT, SSLContext.class);
+        PROPERTY_TYPES.put(JAXRS.Configuration.SSL_CLIENT_AUTHENTICATION, SSLClientAuthentication.class);
+        PROPERTY_TYPES.put(ServerProperties.HTTP_SERVER_CLASS, Class.class);
+        PROPERTY_TYPES.put(ServerProperties.AUTO_START, Boolean.class);
     }
 
     @Override
-    public CompletionStage<Instance> bootstrap(Application application, JAXRS.Configuration configuration) {
-        throw new UnsupportedOperationException();
+    public JAXRS.Configuration.Builder createConfigurationBuilder() {
+        return new JAXRS.Configuration.Builder() {
+            private final Map<String, Object> properties = new HashMap<>();
+
+            {
+                this.properties.put(JAXRS.Configuration.PROTOCOL, "HTTP");
+                this.properties.put(JAXRS.Configuration.HOST, "localhost");
+                this.properties.put(JAXRS.Configuration.PORT, -1); // Auto-select port 80 for HTTP or 443 for HTTPS
+                this.properties.put(JAXRS.Configuration.ROOT_PATH, "/");
+                this.properties.put(ServerProperties.HTTP_SERVER_CLASS, Server.class); // Auto-select first provider
+                try {
+                    this.properties.put(JAXRS.Configuration.SSL_CONTEXT, SSLContext.getDefault());
+                } catch (final NoSuchAlgorithmException e) {
+                    throw new RuntimeException(e);
+                }
+                this.properties.put(JAXRS.Configuration.SSL_CLIENT_AUTHENTICATION,
+                        JAXRS.Configuration.SSLClientAuthentication.NONE);
+                this.properties.put(ServerProperties.AUTO_START, TRUE);
+            }
+
+            @Override
+            public final JAXRS.Configuration.Builder property(final String name, final Object value) {
+                this.properties.put(name, value);
+                return this;
+            }
+
+            @SuppressWarnings("unchecked")
+            @Override
+            public final <T> Builder from(final BiFunction<String, Class<T>, Optional<T>> configProvider) {
+                PROPERTY_TYPES.forEach(
+                        (propertyName, propertyType) -> configProvider.apply(propertyName, (Class<T>) propertyType)
+                                .ifPresent(propertyValue -> this.properties.put(propertyName, propertyValue)));
+                return this;
+            }
+
+            @Override
+            public final JAXRS.Configuration build() {
+                return this.properties::get;
+            }
+        };
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public CompletableFuture<JAXRS.Instance> bootstrap(final Application application,
+            final JAXRS.Configuration configuration) {
+        return CompletableFuture.supplyAsync(() -> {
+            final Class<Server> httpServerClass = (Class<Server>) configuration
+                    .property(ServerProperties.HTTP_SERVER_CLASS);
+
+            return new JAXRS.Instance() {
+                private final Server server = ServerFactory.createServer(httpServerClass, application, configuration);
+
+                @Override
+                public final Configuration configuration() {
+                    return name -> {
+                        switch (name) {
+                        case JAXRS.Configuration.PORT:
+                            return server.port();
+                        case ServerProperties.HTTP_SERVER_CLASS:
+                            return server.getClass();
+                        default:
+                            return configuration.property(name);
+                        }
+                    };
+                }
+
+                @Override
+                public final CompletionStage<StopResult> stop() {
+                    return this.server.stop().thenApply(nativeResult -> new StopResult() {
+
+                        @Override
+                        public final <T> T unwrap(final Class<T> nativeClass) {
+                            return nativeClass.cast(nativeResult);
+                        }
+                    });
+                }
+
+                @Override
+                public final <T> T unwrap(final Class<T> nativeClass) {
+                    return nativeClass.isInstance(this.server) ? nativeClass.cast(this.server)
+                            : this.server.unwrap(nativeClass);
+                }
+            };
+        });
     }
 
 }
diff --git a/core-server/src/main/java/org/glassfish/jersey/server/spi/Server.java b/core-server/src/main/java/org/glassfish/jersey/server/spi/Server.java
new file mode 100644
index 0000000..71d9750
--- /dev/null
+++ b/core-server/src/main/java/org/glassfish/jersey/server/spi/Server.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2018 Markus KARG. 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.server.spi;
+
+import java.util.concurrent.CompletionStage;
+
+import javax.ws.rs.ConstrainedTo;
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.core.Application;
+
+import org.glassfish.jersey.spi.Contract;
+
+/**
+ * Jersey service contract for self-contained servers.
+ * <p>
+ * Runs a self-contained {@link Application} in a {@link Container} using an
+ * HTTP server implicitly started and stopped together with the application.
+ * </p>
+ *
+ * @author Markus KARG (markus@headcrashing.eu)
+ * @since 2.30
+ */
+@Contract
+@ConstrainedTo(RuntimeType.SERVER)
+public interface Server {
+
+    /**
+     * @return container in which the application lives.
+     */
+    public Container container();
+
+    /**
+     * @return IP port the application listens to for requests.
+     */
+    public int port();
+
+    /**
+     * Initiates server bootstrap.
+     * <p>
+     * Startup happens in background. The completion stage produces a native startup
+     * result.
+     * </p>
+     * <p>
+     * Portable applications should not expect any particular result type, as it is
+     * implementation-specific.
+     * </p>
+     *
+     * @return A {@link CompletionStage} providing a native startup result of the
+     *         bootstrap process. The native result MAY be {@code null}.
+     */
+    public CompletionStage<?> start();
+
+    /**
+     * Initiates server shutdown.
+     * <p>
+     * Shutdown happens in background. The completion stage produces a native
+     * shutdown result.
+     * </p>
+     * </p>
+     * Portable applications should not expect any particular result type, as it is
+     * implementation-specific.
+     * </p>
+     *
+     * @return A {@link CompletionStage} providing a native shutdown result of the
+     *         shutdown process. The native result MAY be {@code null}.
+     */
+    public CompletionStage<?> stop();
+
+    /**
+     * Provides access to the native handle(s) of the server, if it holds at least
+     * one.
+     * <p>
+     * Implementations MAY use native handles to identify the server instance, and /
+     * or use those to communicate with and control the instance. Whether or not
+     * such handles exist, and their respective data types, is
+     * implementation-specific.
+     * </p>
+     * <p>
+     * Portable applications should not invoke this method, as the types of
+     * supported handles are implementation-specific.
+     * </p>
+     *
+     * @param nativeClass
+     *            The class of the native handle.
+     * @return The native handle, or {@code null} if no handle of this type exists.
+     */
+    public <T> T unwrap(Class<T> nativeClass);
+
+}
diff --git a/core-server/src/main/java/org/glassfish/jersey/server/spi/ServerProvider.java b/core-server/src/main/java/org/glassfish/jersey/server/spi/ServerProvider.java
new file mode 100644
index 0000000..f0dd87d
--- /dev/null
+++ b/core-server/src/main/java/org/glassfish/jersey/server/spi/ServerProvider.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2018 Markus KARG. 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.server.spi;
+
+import javax.ws.rs.ConstrainedTo;
+import javax.ws.rs.JAXRS;
+import javax.ws.rs.ProcessingException;
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.core.Application;
+
+import org.glassfish.jersey.server.ApplicationHandler;
+import org.glassfish.jersey.spi.Contract;
+
+/**
+ * Service-provider interface for creating server instances.
+ *
+ * If supported by the provider, a server instance of the requested Java type
+ * will be created.
+ * <p>
+ * The created server uses an internally created {@link Container} which is
+ * responsible for listening on a communication channel provided by the server
+ * for new client requests, dispatching these requests to the registered
+ * {@link ApplicationHandler Jersey application handler} using the handler's
+ * {@link ApplicationHandler#handle(org.glassfish.jersey.server.ContainerRequest)
+ * handle(requestContext)} method and sending the responses provided by the
+ * application back to the client.
+ * </p>
+ * <p>
+ * A provider shall support a one-to-one mapping between a type, provided the
+ * type is not {@link Object}. A provider may also support mapping of sub-types
+ * of a type (provided the type is not {@code Object}). It is expected that each
+ * provider supports mapping for distinct set of types and subtypes so that
+ * different providers do not conflict with each other. In addition, a provider
+ * SHOULD support the super type {@link Server} to participate in auto-selection
+ * of providers (in this case the <em>first</em> supporting provider found is
+ * used).
+ * </p>
+ * <p>
+ * An implementation can identify itself by placing a Java service provider
+ * configuration file (if not already present) -
+ * {@code org.glassfish.jersey.server.spi.ServerProvider} - in the resource
+ * directory {@code META-INF/services}, and adding the fully qualified
+ * service-provider-class of the implementation in the file.
+ * </p>
+ *
+ * @author Markus KARG (markus@headcrashing.eu)
+ * @since 2.30
+ */
+@Contract
+@ConstrainedTo(RuntimeType.SERVER)
+public interface ServerProvider {
+
+    /**
+     * Creates a server of a given type which runs the given application using the
+     * given bootstrap configuration.
+     *
+     * @param <T>
+     *            the type of the server.
+     * @param type
+     *            the type of the server. Providers SHOULD support at least
+     *            {@link Server}.
+     * @param application
+     *            The application to host.
+     * @param configuration
+     *            The configuration (host, port, etc.) to be used for bootstrapping.
+     * @return the server, otherwise {@code null} if the provider does not support
+     *         the requested {@code type}.
+     * @throws ProcessingException
+     *             if there is an error creating the server.
+     */
+    public <T extends Server> T createServer(Class<T> type, Application application, JAXRS.Configuration configuration)
+            throws ProcessingException;
+}
diff --git a/core-server/src/test/java/org/glassfish/jersey/server/ServerFactoryTest.java b/core-server/src/test/java/org/glassfish/jersey/server/ServerFactoryTest.java
new file mode 100644
index 0000000..9033318
--- /dev/null
+++ b/core-server/src/test/java/org/glassfish/jersey/server/ServerFactoryTest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2018 Markus KARG. 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.server;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.theInstance;
+import static org.junit.Assert.assertThat;
+
+import java.util.Iterator;
+
+import javax.ws.rs.JAXRS;
+import javax.ws.rs.JAXRS.Configuration;
+import javax.ws.rs.core.Application;
+
+import org.glassfish.jersey.internal.ServiceFinder;
+import org.glassfish.jersey.internal.ServiceFinder.ServiceIteratorProvider;
+import org.glassfish.jersey.internal.guava.Iterators;
+import org.glassfish.jersey.internal.inject.InjectionManager;
+import org.glassfish.jersey.internal.inject.InjectionManagerFactory;
+import org.glassfish.jersey.server.spi.Server;
+import org.glassfish.jersey.server.spi.ServerProvider;
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import mockit.Mocked;
+import mockit.integration.junit4.JMockit;
+
+/**
+ * Unit tests for {@link ServerFactory}.
+ *
+ * @author Markus KARG (markus@headcrashing.eu)
+ * @since 2.30
+ */
+@RunWith(JMockit.class)
+public final class ServerFactoryTest {
+
+    @Test
+    public final void shouldBuildServer(@Mocked final Application mockApplication, @Mocked final Server mockServer,
+            @Mocked final JAXRS.Configuration mockConfiguration, @Mocked final InjectionManager mockInjectionManager) {
+        // given
+        ServiceFinder.setIteratorProvider(new ServiceIteratorProvider() {
+            @Override
+            public final <T> Iterator<T> createIterator(final Class<T> service, final String serviceName,
+                    final ClassLoader loader, final boolean ignoreOnClassNotFound) {
+                return Iterators.singletonIterator(service.cast(
+                        service == ServerProvider.class ? new ServerProvider() {
+                            @Override
+                            public final <U extends Server> U createServer(final Class<U> type, final Application application,
+                                    final Configuration configuration) {
+                                return application == mockApplication && configuration == mockConfiguration
+                                        ? type.cast(mockServer)
+                                        : null;
+                            }
+                        }
+                                : service == InjectionManagerFactory.class ? new InjectionManagerFactory() {
+                                    @Override
+                                    public final InjectionManager create(final Object parent) {
+                                        return mockInjectionManager;
+                                    }
+                                }
+                                        : null));
+            }
+
+            @Override
+            public final <T> Iterator<Class<T>> createClassIterator(final Class<T> service, final String serviceName,
+                    final ClassLoader loader, final boolean ignoreOnClassNotFound) {
+                return null;
+            }
+        });
+
+        // when
+        final Server server = ServerFactory.createServer(Server.class, mockApplication, mockConfiguration);
+
+        // then
+        assertThat(server, is(theInstance(mockServer)));
+    }
+
+    @After
+    public final void resetServiceFinder() {
+        ServiceFinder.setIteratorProvider(null);
+    }
+
+}
diff --git a/core-server/src/test/java/org/glassfish/jersey/server/internal/RuntimeDelegateImplTest.java b/core-server/src/test/java/org/glassfish/jersey/server/internal/RuntimeDelegateImplTest.java
index 57d9668..2af3eb2 100644
--- a/core-server/src/test/java/org/glassfish/jersey/server/internal/RuntimeDelegateImplTest.java
+++ b/core-server/src/test/java/org/glassfish/jersey/server/internal/RuntimeDelegateImplTest.java
@@ -16,18 +16,51 @@
 
 package org.glassfish.jersey.server.internal;
 
+import static java.lang.Boolean.FALSE;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.CoreMatchers.theInstance;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+import java.security.NoSuchAlgorithmException;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+import java.util.function.BiFunction;
+
+import javax.net.ssl.SSLContext;
+import javax.ws.rs.JAXRS;
+import javax.ws.rs.JAXRS.Configuration;
 import javax.ws.rs.core.Application;
 import javax.ws.rs.ext.RuntimeDelegate;
 
+import org.glassfish.jersey.internal.ServiceFinder;
+import org.glassfish.jersey.internal.ServiceFinder.ServiceIteratorProvider;
+import org.glassfish.jersey.server.ServerProperties;
+import org.glassfish.jersey.server.spi.Container;
+import org.glassfish.jersey.server.spi.Server;
+import org.glassfish.jersey.server.spi.ServerProvider;
+import org.junit.After;
 import org.junit.Test;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.fail;
+import org.junit.runner.RunWith;
+
+import mockit.Mocked;
+import mockit.integration.junit4.JMockit;
 
 /**
- * Unit test that checks that the right RuntimeDelegateImpl is loaded by JAX-RS.
+ * Unit tests for {@link RuntimeDelegate}.
  *
  * @author Martin Matula
+ * @author Markus KARG (markus@headcrashing.eu)
  */
+@RunWith(JMockit.class)
 public class RuntimeDelegateImplTest {
 
     @Test
@@ -43,8 +76,260 @@
         }
     }
 
+    /**
+     * Checks that the right RuntimeDelegateImpl is loaded by JAX-RS.
+     */
     @Test
     public void testRuntimeDelegateInstance() {
         assertSame(RuntimeDelegateImpl.class, RuntimeDelegate.getInstance().getClass());
     }
+
+    @Test
+    public final void shouldCreateConfigurationBuilder() {
+        // given
+        final RuntimeDelegate runtimeDelegate = new RuntimeDelegateImpl();
+
+        // when
+        final JAXRS.Configuration.Builder configurationBuilder = runtimeDelegate.createConfigurationBuilder();
+
+        // then
+        assertThat(configurationBuilder, is(notNullValue()));
+    }
+
+    @Test
+    public final void shouldBuildDefaultConfiguration() throws NoSuchAlgorithmException {
+        // given
+        final JAXRS.Configuration.Builder configurationBuilder = new RuntimeDelegateImpl().createConfigurationBuilder();
+
+        // when
+        final JAXRS.Configuration configuration = configurationBuilder.build();
+
+        // then
+        assertThat(configuration, is(notNullValue()));
+        assertThat(configuration.property(JAXRS.Configuration.PROTOCOL), is("HTTP"));
+        assertThat(configuration.property(JAXRS.Configuration.HOST), is("localhost"));
+        assertThat(configuration.property(JAXRS.Configuration.PORT), is(JAXRS.Configuration.DEFAULT_PORT));
+        assertThat(configuration.property(JAXRS.Configuration.ROOT_PATH), is("/"));
+        assertThat(configuration.property(JAXRS.Configuration.SSL_CLIENT_AUTHENTICATION),
+                is(JAXRS.Configuration.SSLClientAuthentication.NONE));
+        assertThat(configuration.property(JAXRS.Configuration.SSL_CONTEXT), is(theInstance(SSLContext.getDefault())));
+        assertThat(configuration.protocol(), is("HTTP"));
+        assertThat(configuration.host(), is("localhost"));
+        assertThat(configuration.port(), is(JAXRS.Configuration.DEFAULT_PORT));
+        assertThat(configuration.rootPath(), is("/"));
+        assertThat(configuration.sslClientAuthentication(), is(JAXRS.Configuration.SSLClientAuthentication.NONE));
+        assertThat(configuration.sslContext(), is(theInstance(SSLContext.getDefault())));
+    }
+
+    @Test
+    public final void shouldBuildConfigurationContainingCustomProperties() {
+        // given
+        final JAXRS.Configuration.Builder configurationBuilder = new RuntimeDelegateImpl().createConfigurationBuilder();
+
+        // when
+        final JAXRS.Configuration configuration = configurationBuilder.property("property", "value").build();
+
+        // then
+        assertThat(configuration, is(notNullValue()));
+        assertThat(configuration.property("property"), is("value"));
+    }
+
+    @Test
+    public final void shouldBuildCustomConfigurationUsingNamedStandardProperties(@Mocked final SSLContext mockSslContext)
+            throws NoSuchAlgorithmException {
+        // given
+        final JAXRS.Configuration.Builder configurationBuilder = new RuntimeDelegateImpl().createConfigurationBuilder();
+
+        // when
+        final JAXRS.Configuration configuration = configurationBuilder.property(JAXRS.Configuration.PROTOCOL, "HTTPS")
+                .property(JAXRS.Configuration.HOST, "hostname").property(JAXRS.Configuration.PORT, 8080)
+                .property(JAXRS.Configuration.ROOT_PATH, "path")
+                .property(JAXRS.Configuration.SSL_CLIENT_AUTHENTICATION,
+                        JAXRS.Configuration.SSLClientAuthentication.OPTIONAL)
+                .property(JAXRS.Configuration.SSL_CONTEXT, mockSslContext).build();
+
+        // then
+        assertThat(configuration, is(notNullValue()));
+        assertThat(configuration.protocol(), is("HTTPS"));
+        assertThat(configuration.host(), is("hostname"));
+        assertThat(configuration.port(), is(8080));
+        assertThat(configuration.rootPath(), is("path"));
+        assertThat(configuration.sslClientAuthentication(), is(JAXRS.Configuration.SSLClientAuthentication.OPTIONAL));
+        assertThat(configuration.sslContext(), is(theInstance(mockSslContext)));
+    }
+
+    @Test
+    public final void shouldBuildCustomConfigurationUsingConvenienceMethods(@Mocked final SSLContext mockSslContext)
+            throws NoSuchAlgorithmException {
+        // given
+        final JAXRS.Configuration.Builder configurationBuilder = new RuntimeDelegateImpl().createConfigurationBuilder();
+
+        // when
+        final JAXRS.Configuration configuration = configurationBuilder.protocol("HTTPS").host("hostname").port(8080)
+                .rootPath("path").sslClientAuthentication(JAXRS.Configuration.SSLClientAuthentication.OPTIONAL)
+                .sslContext(mockSslContext).build();
+
+        // then
+        assertThat(configuration, is(notNullValue()));
+        assertThat(configuration.protocol(), is("HTTPS"));
+        assertThat(configuration.host(), is("hostname"));
+        assertThat(configuration.port(), is(8080));
+        assertThat(configuration.rootPath(), is("path"));
+        assertThat(configuration.sslClientAuthentication(), is(JAXRS.Configuration.SSLClientAuthentication.OPTIONAL));
+        assertThat(configuration.sslContext(), is(theInstance(mockSslContext)));
+    }
+
+    @Test
+    public final void shouldBuildCustomConfigurationFromPropertiesProvider(@Mocked final SSLContext mockSslContext) {
+        // given
+        final JAXRS.Configuration.Builder configurationBuilder = new RuntimeDelegateImpl().createConfigurationBuilder();
+        final Class<Server> mockServerClass = Server.class;
+        final BiFunction<String, Class<Object>, Optional<Object>> propertiesProvider = (propertyName, propertyType) -> {
+            if (JAXRS.Configuration.PROTOCOL.equals(propertyName) && String.class.equals(propertyType)) {
+                return Optional.of("HTTPS");
+            }
+            if (JAXRS.Configuration.HOST.equals(propertyName) && String.class.equals(propertyType)) {
+                return Optional.of("hostname");
+            }
+            if (JAXRS.Configuration.PORT.equals(propertyName) && Integer.class.equals(propertyType)) {
+                return Optional.of(8080);
+            }
+            if (JAXRS.Configuration.ROOT_PATH.equals(propertyName) && String.class.equals(propertyType)) {
+                return Optional.of("path");
+            }
+            if (JAXRS.Configuration.SSL_CLIENT_AUTHENTICATION.equals(propertyName)
+                    && JAXRS.Configuration.SSLClientAuthentication.class.equals(propertyType)) {
+                return Optional.of(JAXRS.Configuration.SSLClientAuthentication.OPTIONAL);
+            }
+            if (JAXRS.Configuration.SSL_CONTEXT.equals(propertyName) && SSLContext.class.equals(propertyType)) {
+                return Optional.of(mockSslContext);
+            }
+            if (ServerProperties.HTTP_SERVER_CLASS.equals(propertyName) && Class.class.equals(propertyType)) {
+                return Optional.of(mockServerClass);
+            }
+            if (ServerProperties.AUTO_START.equals(propertyName) && Boolean.class.equals(propertyType)) {
+                return Optional.of(FALSE);
+            }
+            return Optional.empty();
+        };
+
+        // when
+        final JAXRS.Configuration configuration = configurationBuilder.from(propertiesProvider).build();
+
+        // then
+        assertThat(configuration, is(notNullValue()));
+        assertThat(configuration.protocol(), is("HTTPS"));
+        assertThat(configuration.host(), is("hostname"));
+        assertThat(configuration.port(), is(8080));
+        assertThat(configuration.rootPath(), is("path"));
+        assertThat(configuration.sslClientAuthentication(), is(JAXRS.Configuration.SSLClientAuthentication.OPTIONAL));
+        assertThat(configuration.sslContext(), is(theInstance(mockSslContext)));
+        assertThat(configuration.property(ServerProperties.HTTP_SERVER_CLASS), is(theInstance(mockServerClass)));
+        assertThat(configuration.property(ServerProperties.AUTO_START), is(FALSE));
+    }
+
+    @Test
+    public final void shouldBootstrapApplication(@Mocked final Container mockContainer,
+            @Mocked final Application mockApplication, @Mocked final SSLContext mockSslContext) throws InterruptedException,
+            ExecutionException, TimeoutException {
+        // given
+        final Server mockServer = new Server() {
+            @Override
+            public final Container container() {
+                return mockContainer;
+            }
+
+            @Override
+            public final int port() {
+                return 8888;
+            }
+
+            @Override
+            public final CompletionStage<?> start() {
+                return CompletableFuture.completedFuture(null);
+            }
+
+            @Override
+            public final CompletionStage<?> stop() {
+                return CompletableFuture.completedFuture(null);
+            }
+
+            @Override
+            public final <T> T unwrap(final Class<T> nativeClass) {
+                return null;
+            }
+        };
+        final RuntimeDelegate runtimeDelegate = new RuntimeDelegateImpl();
+        final JAXRS.Configuration mockConfiguration = name -> {
+            switch (name) {
+            case JAXRS.Configuration.PROTOCOL:
+                return "HTTPS";
+            case JAXRS.Configuration.HOST:
+                return "hostname";
+            case JAXRS.Configuration.PORT:
+                return JAXRS.Configuration.DEFAULT_PORT;
+            case JAXRS.Configuration.ROOT_PATH:
+                return "path";
+            case JAXRS.Configuration.SSL_CLIENT_AUTHENTICATION:
+                return JAXRS.Configuration.SSLClientAuthentication.OPTIONAL;
+            case JAXRS.Configuration.SSL_CONTEXT:
+                return mockSslContext;
+            case ServerProperties.HTTP_SERVER_CLASS:
+                return Server.class;
+            default:
+                return null;
+            }
+        };
+        ServiceFinder.setIteratorProvider(new ServiceIteratorProvider() {
+            @Override
+            public final <T> Iterator<T> createIterator(final Class<T> service, final String serviceName,
+                    final ClassLoader loader, final boolean ignoreOnClassNotFound) {
+                return Collections.singleton(service.cast(new ServerProvider() {
+                    @Override
+                    public final <U extends Server> U createServer(final Class<U> type, final Application application,
+                            final Configuration configuration) {
+                        return application == mockApplication && configuration == mockConfiguration
+                                ? type.cast(mockServer)
+                                : null;
+                    }
+                })).iterator();
+            }
+
+            @Override
+            public final <T> Iterator<Class<T>> createClassIterator(final Class<T> service, final String serviceName,
+                    final ClassLoader loader, final boolean ignoreOnClassNotFound) {
+                return null;
+            }
+        });
+
+        // when
+        final CompletionStage<JAXRS.Instance> bootstrapStage = runtimeDelegate.bootstrap(mockApplication,
+                mockConfiguration);
+        final JAXRS.Instance instance = bootstrapStage.toCompletableFuture().get(15, SECONDS);
+        final Configuration configuration = instance.configuration();
+        final Server server = instance.unwrap(Server.class);
+        final Container container = server.container();
+        final CompletionStage<JAXRS.Instance.StopResult> stopStage = instance.stop();
+        final Object stopResult = stopStage.toCompletableFuture().get(15, SECONDS);
+
+        // then
+        assertThat(instance, is(notNullValue()));
+        assertThat(configuration, is(notNullValue()));
+        assertThat(configuration.protocol(), is("HTTPS"));
+        assertThat(configuration.host(), is("hostname"));
+        assertThat(configuration.port(), is(8888));
+        assertThat(configuration.rootPath(), is("path"));
+        assertThat(configuration.sslClientAuthentication(), is(JAXRS.Configuration.SSLClientAuthentication.OPTIONAL));
+        assertThat(configuration.sslContext(), is(theInstance(mockSslContext)));
+        assertThat(configuration.property(ServerProperties.HTTP_SERVER_CLASS), is(theInstance(mockServer.getClass())));
+        assertThat(server, is(theInstance(mockServer)));
+        assertThat(container, is(theInstance(mockContainer)));
+        assertThat(stopResult, is(notNullValue()));
+    }
+
+    @After
+    public final void resetServiceFinder() {
+        ServiceFinder.setIteratorProvider(null);
+    }
+
 }