Initial Contribution

Signed-off-by: Jan Supol <jan.supol@oracle.com>
diff --git a/containers/glassfish/jersey-gf-ejb/pom.xml b/containers/glassfish/jersey-gf-ejb/pom.xml
new file mode 100644
index 0000000..4038ce7
--- /dev/null
+++ b/containers/glassfish/jersey-gf-ejb/pom.xml
@@ -0,0 +1,134 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.glassfish.jersey.containers.glassfish</groupId>
+        <artifactId>project</artifactId>
+        <version>2.28-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>jersey-gf-ejb</artifactId>
+    <packaging>jar</packaging>
+    <name>jersey-gf-ejb</name>
+
+    <description>Jersey EJB for GlassFish integration</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.glassfish.jersey.ext.cdi</groupId>
+            <artifactId>jersey-cdi1x</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>javax.ejb</groupId>
+            <artifactId>javax.ejb-api</artifactId>
+            <version>${ejb.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>javax.interceptor</groupId>
+            <artifactId>javax.interceptor-api</artifactId>
+            <version>${javax.interceptor.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.hk2.external</groupId>
+            <artifactId>javax.inject</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.jersey.core</groupId>
+            <artifactId>jersey-server</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.main.ejb</groupId>
+            <artifactId>ejb-container</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.main.common</groupId>
+            <artifactId>container-common</artifactId>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <pluginManagement>
+            <plugins>
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-javadoc-plugin</artifactId>
+                    <configuration>
+                        <!--
+                            Excluding only *.tests.* packages for this module. At least one class has to exist in non-excluded
+                            packages to generate proper -javadoc.jar file. See https://jira.codehaus.org/browse/MJAVADOC-329
+                          -->
+                        <excludePackageNames>*.tests.*</excludePackageNames>
+                    </configuration>
+                </plugin>
+            </plugins>
+        </pluginManagement>
+        <plugins>
+            <plugin>
+                <groupId>com.sun.istack</groupId>
+                <artifactId>maven-istack-commons-plugin</artifactId>
+                <inherited>true</inherited>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>build-helper-maven-plugin</artifactId>
+                <inherited>true</inherited>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <!-- Explicitly set versions for packages from GlassFish to allow future uptake of GlassFish 5.x-->
+                        <Import-Package>
+                            com.sun.*;version="[4.0,6)",
+                            org.glassfish.ejb.*;version="[4.0,6)",
+                            org.glassfish.internal.*;version="[4.0,6)",
+                            *
+                        </Import-Package>
+                        <_versionpolicy>[$(version;==;$(@)),$(version;+;$(@)))</_versionpolicy>
+                        <_nodefaultversion>false</_nodefaultversion>
+                    </instructions>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>osgi-bundle</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>bundle</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/containers/glassfish/jersey-gf-ejb/src/main/java/org/glassfish/jersey/gf/ejb/internal/EjbComponentInterceptor.java b/containers/glassfish/jersey-gf-ejb/src/main/java/org/glassfish/jersey/gf/ejb/internal/EjbComponentInterceptor.java
new file mode 100644
index 0000000..26e6e8b
--- /dev/null
+++ b/containers/glassfish/jersey-gf-ejb/src/main/java/org/glassfish/jersey/gf/ejb/internal/EjbComponentInterceptor.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.gf.ejb.internal;
+
+import javax.annotation.PostConstruct;
+import javax.interceptor.InvocationContext;
+
+import org.glassfish.jersey.ext.cdi1x.internal.CdiComponentProvider;
+import org.glassfish.jersey.internal.inject.InjectionManager;
+
+/**
+ * EJB interceptor to inject Jersey specific stuff into EJB beans.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public final class EjbComponentInterceptor {
+
+    private final InjectionManager injectionManager;
+
+    /**
+     * Create new EJB component injection manager.
+     *
+     * @param injectionManager injection manager.
+     */
+    public EjbComponentInterceptor(final InjectionManager injectionManager) {
+        this.injectionManager = injectionManager;
+    }
+
+    @PostConstruct
+    private void inject(final InvocationContext context) throws Exception {
+
+        final Object beanInstance = context.getTarget();
+        injectionManager.inject(beanInstance, CdiComponentProvider.CDI_CLASS_ANALYZER);
+
+        // Invoke next interceptor in chain
+        context.proceed();
+    }
+}
diff --git a/containers/glassfish/jersey-gf-ejb/src/main/java/org/glassfish/jersey/gf/ejb/internal/EjbComponentProvider.java b/containers/glassfish/jersey-gf-ejb/src/main/java/org/glassfish/jersey/gf/ejb/internal/EjbComponentProvider.java
new file mode 100644
index 0000000..8a652e3
--- /dev/null
+++ b/containers/glassfish/jersey-gf-ejb/src/main/java/org/glassfish/jersey/gf/ejb/internal/EjbComponentProvider.java
@@ -0,0 +1,415 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.gf.ejb.internal;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.Supplier;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.ext.ExceptionMapper;
+
+import javax.annotation.Priority;
+import javax.ejb.Local;
+import javax.ejb.Remote;
+import javax.inject.Singleton;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+
+import org.glassfish.jersey.internal.inject.AbstractBinder;
+import org.glassfish.jersey.internal.inject.Binding;
+import org.glassfish.jersey.internal.inject.Bindings;
+import org.glassfish.jersey.internal.inject.InjectionManager;
+import org.glassfish.jersey.internal.inject.InstanceBinding;
+import org.glassfish.jersey.server.ApplicationHandler;
+import org.glassfish.jersey.server.model.Invocable;
+import org.glassfish.jersey.server.spi.ComponentProvider;
+import org.glassfish.jersey.server.spi.internal.ResourceMethodInvocationHandlerProvider;
+
+import org.glassfish.ejb.deployment.descriptor.EjbBundleDescriptorImpl;
+import org.glassfish.ejb.deployment.descriptor.EjbDescriptor;
+import org.glassfish.internal.data.ApplicationInfo;
+import org.glassfish.internal.data.ApplicationRegistry;
+import org.glassfish.internal.data.ModuleInfo;
+
+import com.sun.ejb.containers.BaseContainer;
+import com.sun.ejb.containers.EjbContainerUtil;
+import com.sun.ejb.containers.EjbContainerUtilImpl;
+import com.sun.enterprise.config.serverbeans.Application;
+import com.sun.enterprise.config.serverbeans.Applications;
+
+/**
+ * EJB component provider.
+ *
+ * @author Paul Sandoz
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@Priority(300)
+@SuppressWarnings("UnusedDeclaration")
+public final class EjbComponentProvider implements ComponentProvider, ResourceMethodInvocationHandlerProvider {
+
+    private static final Logger LOGGER = Logger.getLogger(EjbComponentProvider.class.getName());
+
+    private InitialContext initialContext;
+    private final List<String> libNames = new CopyOnWriteArrayList<>();
+
+    private boolean ejbInterceptorRegistered = false;
+
+    /**
+     * HK2 factory to provide EJB components obtained via JNDI lookup.
+     */
+    private static class EjbFactory<T> implements Supplier<T> {
+
+        final InitialContext ctx;
+        final Class<T> clazz;
+        final EjbComponentProvider ejbProvider;
+
+        @SuppressWarnings("unchecked")
+        @Override
+        public T get() {
+            try {
+                return (T) lookup(ctx, clazz, clazz.getSimpleName(), ejbProvider);
+            } catch (NamingException ex) {
+                Logger.getLogger(ApplicationHandler.class.getName()).log(Level.SEVERE, null, ex);
+                return null;
+            }
+        }
+
+        public EjbFactory(Class<T> rawType, InitialContext ctx, EjbComponentProvider ejbProvider) {
+            this.clazz = rawType;
+            this.ctx = ctx;
+            this.ejbProvider = ejbProvider;
+        }
+    }
+
+    /**
+     * Annotations to determine EJB components.
+     */
+    private static final Set<String> EjbComponentAnnotations = Collections.unmodifiableSet(new HashSet<String>() {{
+        add("javax.ejb.Stateful");
+        add("javax.ejb.Stateless");
+        add("javax.ejb.Singleton");
+    }});
+
+    private InjectionManager injectionManager = null;
+
+    // ComponentProvider
+    @Override
+    public void initialize(final InjectionManager injectionManager) {
+        this.injectionManager = injectionManager;
+
+        InstanceBinding<EjbComponentProvider> descriptor = Bindings.service(EjbComponentProvider.this)
+                .to(ResourceMethodInvocationHandlerProvider.class);
+        this.injectionManager.register(descriptor);
+    }
+
+    private ApplicationInfo getApplicationInfo(EjbContainerUtil ejbUtil) throws NamingException {
+        ApplicationRegistry appRegistry = ejbUtil.getServices().getService(ApplicationRegistry.class);
+        Applications applications = ejbUtil.getServices().getService(Applications.class);
+        String appNamePrefix = (String) initialContext.lookup("java:app/AppName");
+        Set<String> appNames = appRegistry.getAllApplicationNames();
+        Set<String> disabledApps = new TreeSet<>();
+        for (String appName : appNames) {
+            if (appName.startsWith(appNamePrefix)) {
+                Application appDesc = applications.getApplication(appName);
+                if (appDesc != null && !ejbUtil.getDeployment().isAppEnabled(appDesc)) {
+                    // skip disabled version of the app
+                    disabledApps.add(appName);
+                } else {
+                    return ejbUtil.getDeployment().get(appName);
+                }
+            }
+        }
+
+        // grab the latest one, there is no way to make
+        // sure which one the user is actually enabling,
+        // so use the best case, i.e. upgrade
+        Iterator<String> it = disabledApps.iterator();
+        String lastDisabledApp = null;
+        while (it.hasNext()) {
+            lastDisabledApp = it.next();
+        }
+        if (lastDisabledApp != null) {
+            return ejbUtil.getDeployment().get(lastDisabledApp);
+        }
+
+        throw new NamingException("Application Information Not Found");
+    }
+
+    private void registerEjbInterceptor() {
+        try {
+            final Object interceptor = new EjbComponentInterceptor(injectionManager);
+            initialContext = getInitialContext();
+            final EjbContainerUtil ejbUtil = EjbContainerUtilImpl.getInstance();
+            final ApplicationInfo appInfo = getApplicationInfo(ejbUtil);
+            final List<String> tempLibNames = new LinkedList<>();
+            for (ModuleInfo moduleInfo : appInfo.getModuleInfos()) {
+                final String jarName = moduleInfo.getName();
+                if (jarName.endsWith(".jar") || jarName.endsWith(".war")) {
+                    final String moduleName = jarName.substring(0, jarName.length() - 4);
+                    tempLibNames.add(moduleName);
+                    final Object bundleDescriptor = moduleInfo.getMetaData(EjbBundleDescriptorImpl.class.getName());
+                    if (bundleDescriptor instanceof EjbBundleDescriptorImpl) {
+                        final Collection<EjbDescriptor> ejbs = ((EjbBundleDescriptorImpl) bundleDescriptor).getEjbs();
+
+                        for (final EjbDescriptor ejb : ejbs) {
+                            final BaseContainer ejbContainer = EjbContainerUtilImpl.getInstance().getContainer(ejb.getUniqueId());
+                            try {
+                                AccessController.doPrivileged(new PrivilegedExceptionAction() {
+                                    @Override
+                                    public Object run() throws Exception {
+                                        final Method registerInterceptorMethod =
+                                                BaseContainer.class
+                                                        .getDeclaredMethod("registerSystemInterceptor", java.lang.Object.class);
+                                        registerInterceptorMethod.setAccessible(true);
+
+                                        registerInterceptorMethod.invoke(ejbContainer, interceptor);
+                                        return null;
+                                    }
+                                });
+                            } catch (PrivilegedActionException pae) {
+                                final Throwable cause = pae.getCause();
+                                LOGGER.log(Level.WARNING,
+                                        LocalizationMessages.EJB_INTERCEPTOR_BINDING_WARNING(ejb.getEjbClassName()), cause);
+                            }
+                        }
+                    }
+                }
+            }
+            libNames.addAll(tempLibNames);
+            final Object interceptorBinder = initialContext.lookup("java:org.glassfish.ejb.container.interceptor_binding_spi");
+            // Some implementations of InitialContext return null instead of
+            // throwing NamingException if there is no Object associated with
+            // the name
+            if (interceptorBinder == null) {
+                throw new IllegalStateException(LocalizationMessages.EJB_INTERCEPTOR_BIND_API_NOT_AVAILABLE());
+            }
+
+            try {
+                AccessController.doPrivileged(new PrivilegedExceptionAction() {
+                    @Override
+                    public Object run() throws Exception {
+                        Method interceptorBinderMethod = interceptorBinder.getClass()
+                                .getMethod("registerInterceptor", java.lang.Object.class);
+
+                        interceptorBinderMethod.invoke(interceptorBinder, interceptor);
+                        EjbComponentProvider.this.ejbInterceptorRegistered = true;
+                        LOGGER.log(Level.CONFIG, LocalizationMessages.EJB_INTERCEPTOR_BOUND());
+                        return null;
+                    }
+                });
+            } catch (PrivilegedActionException pae) {
+                throw new IllegalStateException(LocalizationMessages.EJB_INTERCEPTOR_CONFIG_ERROR(), pae.getCause());
+            }
+
+        } catch (NamingException ex) {
+            throw new IllegalStateException(LocalizationMessages.EJB_INTERCEPTOR_BIND_API_NOT_AVAILABLE(), ex);
+        } catch (LinkageError ex) {
+            throw new IllegalStateException(LocalizationMessages.EJB_INTERCEPTOR_CONFIG_LINKAGE_ERROR(), ex);
+        }
+    }
+
+    // ComponentProvider
+    @SuppressWarnings("unchecked")
+    @Override
+    public boolean bind(Class<?> component, Set<Class<?>> providerContracts) {
+
+        if (LOGGER.isLoggable(Level.FINE)) {
+            LOGGER.fine(LocalizationMessages.EJB_CLASS_BEING_CHECKED(component));
+        }
+
+        if (injectionManager == null) {
+            throw new IllegalStateException(LocalizationMessages.EJB_COMPONENT_PROVIDER_NOT_INITIALIZED_PROPERLY());
+        }
+
+        if (!isEjbComponent(component)) {
+            return false;
+        }
+
+        if (!ejbInterceptorRegistered) {
+            registerEjbInterceptor();
+        }
+
+        Binding binding = Bindings.supplier(new EjbFactory(component, initialContext, EjbComponentProvider.this))
+                .to(component)
+                .to(providerContracts);
+        injectionManager.register(binding);
+
+        if (LOGGER.isLoggable(Level.CONFIG)) {
+            LOGGER.config(LocalizationMessages.EJB_CLASS_BOUND_WITH_CDI(component));
+        }
+
+        return true;
+    }
+
+    @Override
+    public void done() {
+        registerEjbExceptionMapper();
+    }
+
+    private void registerEjbExceptionMapper() {
+        injectionManager.register(new AbstractBinder() {
+            @Override
+            protected void configure() {
+                bind(EjbExceptionMapper.class).to(ExceptionMapper.class).in(Singleton.class);
+            }
+        });
+    }
+
+    private boolean isEjbComponent(Class<?> component) {
+        for (Annotation a : component.getAnnotations()) {
+            if (EjbComponentAnnotations.contains(a.annotationType().getName())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public InvocationHandler create(Invocable method) {
+
+        final Class<?> resourceClass = method.getHandler().getHandlerClass();
+
+        if (resourceClass == null || !isEjbComponent(resourceClass)) {
+            return null;
+        }
+
+        final Method handlingMethod = method.getDefinitionMethod();
+
+        for (Class iFace : remoteAndLocalIfaces(resourceClass)) {
+            try {
+                final Method iFaceMethod = iFace.getDeclaredMethod(handlingMethod.getName(), handlingMethod.getParameterTypes());
+                if (iFaceMethod != null) {
+                    return new InvocationHandler() {
+                        @Override
+                        public Object invoke(Object target, Method ignored, Object[] args)
+                                throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
+                            return iFaceMethod.invoke(target, args);
+                        }
+                    };
+                }
+            } catch (NoSuchMethodException | SecurityException ex) {
+                logLookupException(handlingMethod, resourceClass, iFace, ex);
+            }
+        }
+        return null;
+    }
+
+    private void logLookupException(final Method method, final Class<?> component, Class<?> iFace, Exception ex) {
+        LOGGER.log(
+                Level.WARNING,
+                LocalizationMessages.EJB_INTERFACE_HANDLING_METHOD_LOOKUP_EXCEPTION(method, component, iFace), ex);
+    }
+
+    private List<Class> remoteAndLocalIfaces(final Class<?> resourceClass) {
+        final List<Class> allLocalOrRemoteIfaces = new LinkedList<>();
+        if (resourceClass.isAnnotationPresent(Remote.class)) {
+            allLocalOrRemoteIfaces.addAll(Arrays.asList(resourceClass.getAnnotation(Remote.class).value()));
+        }
+        if (resourceClass.isAnnotationPresent(Local.class)) {
+            allLocalOrRemoteIfaces.addAll(Arrays.asList(resourceClass.getAnnotation(Local.class).value()));
+        }
+        for (Class<?> i : resourceClass.getInterfaces()) {
+            if (i.isAnnotationPresent(Remote.class) || i.isAnnotationPresent(Local.class)) {
+                allLocalOrRemoteIfaces.add(i);
+            }
+        }
+        return allLocalOrRemoteIfaces;
+    }
+
+    private static InitialContext getInitialContext() {
+        try {
+            // Deployment on Google App Engine will
+            // result in a LinkageError
+            return new InitialContext();
+        } catch (Exception ex) {
+            throw new IllegalStateException(LocalizationMessages.INITIAL_CONTEXT_NOT_AVAILABLE(), ex);
+        }
+    }
+
+    private static Object lookup(InitialContext ic, Class<?> c, String name, EjbComponentProvider provider)
+            throws NamingException {
+        try {
+            return lookupSimpleForm(ic, name, provider);
+        } catch (NamingException ex) {
+            LOGGER.log(Level.WARNING, LocalizationMessages.EJB_CLASS_SIMPLE_LOOKUP_FAILED(c.getName()), ex);
+
+            return lookupFullyQualifiedForm(ic, c, name, provider);
+        }
+    }
+
+    private static Object lookupSimpleForm(InitialContext ic, String name, EjbComponentProvider provider) throws NamingException {
+        if (provider.libNames.isEmpty()) {
+            String jndiName = "java:module/" + name;
+            return ic.lookup(jndiName);
+        } else {
+            NamingException ne = null;
+            for (String moduleName : provider.libNames) {
+                String jndiName = "java:app/" + moduleName + "/" + name;
+                Object result;
+                try {
+                    result = ic.lookup(jndiName);
+                    if (result != null) {
+                        return result;
+                    }
+                } catch (NamingException e) {
+                    ne = e;
+                }
+            }
+            throw (ne != null) ? ne : new NamingException();
+        }
+    }
+
+    private static Object lookupFullyQualifiedForm(InitialContext ic, Class<?> c, String name, EjbComponentProvider provider)
+            throws NamingException {
+        if (provider.libNames.isEmpty()) {
+            String jndiName = "java:module/" + name + "!" + c.getName();
+            return ic.lookup(jndiName);
+        } else {
+            NamingException ne = null;
+            for (String moduleName : provider.libNames) {
+                String jndiName = "java:app/" + moduleName + "/" + name + "!" + c.getName();
+                Object result;
+                try {
+                    result = ic.lookup(jndiName);
+                    if (result != null) {
+                        return result;
+                    }
+                } catch (NamingException e) {
+                    ne = e;
+                }
+            }
+            throw (ne != null) ? ne : new NamingException();
+        }
+    }
+}
diff --git a/containers/glassfish/jersey-gf-ejb/src/main/java/org/glassfish/jersey/gf/ejb/internal/EjbExceptionMapper.java b/containers/glassfish/jersey-gf-ejb/src/main/java/org/glassfish/jersey/gf/ejb/internal/EjbExceptionMapper.java
new file mode 100644
index 0000000..1121c41
--- /dev/null
+++ b/containers/glassfish/jersey-gf-ejb/src/main/java/org/glassfish/jersey/gf/ejb/internal/EjbExceptionMapper.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.gf.ejb.internal;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+
+import javax.ejb.EJBException;
+import javax.inject.Inject;
+import javax.inject.Provider;
+
+import org.glassfish.jersey.spi.ExceptionMappers;
+import org.glassfish.jersey.spi.ExtendedExceptionMapper;
+
+/**
+ * Helper class to handle exceptions wrapped by the EJB container with EJBException.
+ * If this mapper was not registered, no {@link WebApplicationException}
+ * would end up mapped to the corresponding response.
+ *
+ * @author Paul Sandoz
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public class EjbExceptionMapper implements ExtendedExceptionMapper<EJBException> {
+
+    private final Provider<ExceptionMappers> mappers;
+
+    /**
+     * Create new EJB exception mapper.
+     *
+     * @param mappers utility to find mapper delegate.
+     */
+    @Inject
+    public EjbExceptionMapper(Provider<ExceptionMappers> mappers) {
+        this.mappers = mappers;
+    }
+
+    @Override
+    public Response toResponse(EJBException exception) {
+        return causeToResponse(exception);
+    }
+
+    @Override
+    public boolean isMappable(EJBException exception) {
+        try {
+            return (causeToResponse(exception) != null);
+        } catch (Throwable ignored) {
+            return false;
+        }
+    }
+
+    private Response causeToResponse(EJBException exception) {
+
+        final Exception cause = exception.getCausedByException();
+
+        if (cause != null) {
+
+            final ExceptionMapper mapper = mappers.get().findMapping(cause);
+            if (mapper != null && mapper != this) {
+
+                return mapper.toResponse(cause);
+
+            } else if (cause instanceof WebApplicationException) {
+
+                return ((WebApplicationException) cause).getResponse();
+            }
+        }
+        return null;
+    }
+}
diff --git a/containers/glassfish/jersey-gf-ejb/src/main/java/org/glassfish/jersey/gf/ejb/internal/package-info.java b/containers/glassfish/jersey-gf-ejb/src/main/java/org/glassfish/jersey/gf/ejb/internal/package-info.java
new file mode 100644
index 0000000..700cfe7
--- /dev/null
+++ b/containers/glassfish/jersey-gf-ejb/src/main/java/org/glassfish/jersey/gf/ejb/internal/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+/**
+ * Jersey internal package supporting Jersey EJB injections in Glassfish 4 environment.
+ */
+package org.glassfish.jersey.gf.ejb.internal;
diff --git a/containers/glassfish/jersey-gf-ejb/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ComponentProvider b/containers/glassfish/jersey-gf-ejb/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ComponentProvider
new file mode 100644
index 0000000..2db13b7
--- /dev/null
+++ b/containers/glassfish/jersey-gf-ejb/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ComponentProvider
@@ -0,0 +1 @@
+org.glassfish.jersey.gf.ejb.internal.EjbComponentProvider
diff --git a/containers/glassfish/jersey-gf-ejb/src/main/resources/org/glassfish/jersey/gf/ejb/internal/localization.properties b/containers/glassfish/jersey-gf-ejb/src/main/resources/org/glassfish/jersey/gf/ejb/internal/localization.properties
new file mode 100644
index 0000000..95bbf1c
--- /dev/null
+++ b/containers/glassfish/jersey-gf-ejb/src/main/resources/org/glassfish/jersey/gf/ejb/internal/localization.properties
@@ -0,0 +1,29 @@
+#
+# Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Eclipse Public License v. 2.0, which is available at
+# http://www.eclipse.org/legal/epl-2.0.
+#
+# This Source Code may also be made available under the following Secondary
+# Licenses when the conditions for such availability set forth in the
+# Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+# version 2 with the GNU Classpath Exception, which is available at
+# https://www.gnu.org/software/classpath/license.html.
+#
+# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+#
+
+initial.context.not.available=InitialContext not found. JAX-RS EJB support is not available.
+ejb.interceptor.bind.api.not.available=The EJB interceptor binding API is not available. JAX-RS EJB integration can not be supported.
+ejb.interceptor.bind.api.non.conformant=The EJB interceptor binding API does not conform to what is expected. JAX-RS EJB integration can not be supported.
+ejb.interceptor.binding.warning=Could not bind EJB intercetor for class, {0}.
+ejb.interceptor.bound=The Jersey EJB interceptor is bound. JAX-RS EJB integration support is enabled.
+ejb.interceptor.config.error=Error when configuring to use the EJB interceptor binding API. JAX-RS EJB integration can not be supported.
+ejb.interceptor.config.security.error=Security issue when configuring to use the EJB interceptor binding API. JAX-RS EJB support is not available.
+ejb.interceptor.config.linkage.error=Linkage error when configuring to use the EJB interceptor binding API. JAX-RS EJB integration can not be supported.
+ejb.component.provider.not.initialized.properly=EJB component provider has not been initialized properly.
+ejb.class.simple.lookup.failed=An instance of EJB class, {0}, could not be looked up using simple form name. Attempting to look up using the fully-qualified form name.
+ejb.interface.handling.method.lookup.exception=Exception thrown when trying to lookup actual handling method, {0}, for EJB type, {1}, using interface {2}.
+ejb.class.being.checked=Class, {0}, is being checked with Jersey EJB component provider.
+ejb.class.bound.with.cdi=Class, {0}, has been bound by Jersey EJB component provider.
diff --git a/containers/glassfish/pom.xml b/containers/glassfish/pom.xml
new file mode 100644
index 0000000..c10d92e
--- /dev/null
+++ b/containers/glassfish/pom.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.glassfish.jersey.containers</groupId>
+        <artifactId>project</artifactId>
+        <version>2.28-SNAPSHOT</version>
+    </parent>
+
+    <groupId>org.glassfish.jersey.containers.glassfish</groupId>
+    <artifactId>project</artifactId>
+    <packaging>pom</packaging>
+    <name>jersey-glassfish-support</name>
+
+    <description>Jersey GlassFish container providers umbrella project module</description>
+
+    <modules>
+        <module>jersey-gf-ejb</module>
+    </modules>
+
+</project>
diff --git a/containers/grizzly2-http/pom.xml b/containers/grizzly2-http/pom.xml
new file mode 100644
index 0000000..49e75e5
--- /dev/null
+++ b/containers/grizzly2-http/pom.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.glassfish.jersey.containers</groupId>
+        <artifactId>project</artifactId>
+        <version>2.28-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>jersey-container-grizzly2-http</artifactId>
+    <packaging>jar</packaging>
+    <name>jersey-container-grizzly2-http</name>
+
+    <description>Grizzly 2 Http Container.</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.glassfish.hk2.external</groupId>
+            <artifactId>javax.inject</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.grizzly</groupId>
+            <artifactId>grizzly-http-server</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>com.sun.istack</groupId>
+                <artifactId>maven-istack-commons-plugin</artifactId>
+                <inherited>true</inherited>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>build-helper-maven-plugin</artifactId>
+                <inherited>true</inherited>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <inherited>true</inherited>
+            </plugin>
+        </plugins>
+
+        <resources>
+            <resource>
+                <directory>${basedir}/src/main/java</directory>
+                <includes>
+                    <include>META-INF/**/*</include>
+                </includes>
+            </resource>
+            <resource>
+                <directory>${basedir}/src/main/resources</directory>
+                <filtering>true</filtering>
+            </resource>
+        </resources>
+    </build>
+
+</project>
diff --git a/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpContainer.java b/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpContainer.java
new file mode 100644
index 0000000..8c9815d
--- /dev/null
+++ b/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpContainer.java
@@ -0,0 +1,485 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.grizzly2.httpserver;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.reflect.Type;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.Principal;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.SecurityContext;
+
+import javax.inject.Inject;
+import javax.inject.Provider;
+
+import org.glassfish.jersey.grizzly2.httpserver.internal.LocalizationMessages;
+import org.glassfish.jersey.internal.inject.AbstractBinder;
+import org.glassfish.jersey.internal.inject.ReferencingFactory;
+import org.glassfish.jersey.internal.util.ExtendedLogger;
+import org.glassfish.jersey.internal.util.collection.Ref;
+import org.glassfish.jersey.process.internal.RequestScoped;
+import org.glassfish.jersey.server.ApplicationHandler;
+import org.glassfish.jersey.server.ContainerException;
+import org.glassfish.jersey.server.ContainerRequest;
+import org.glassfish.jersey.server.ContainerResponse;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.ServerProperties;
+import org.glassfish.jersey.server.internal.ContainerUtils;
+import org.glassfish.jersey.server.spi.Container;
+import org.glassfish.jersey.server.spi.ContainerResponseWriter;
+
+import org.glassfish.grizzly.CompletionHandler;
+import org.glassfish.grizzly.http.server.HttpHandler;
+import org.glassfish.grizzly.http.server.Request;
+import org.glassfish.grizzly.http.server.Response;
+
+/**
+ * Jersey {@code Container} implementation based on Grizzly {@link org.glassfish.grizzly.http.server.HttpHandler}.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ * @author Libor Kramolis (libor.kramolis at oracle.com)
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public final class GrizzlyHttpContainer extends HttpHandler implements Container {
+
+    private static final ExtendedLogger logger =
+            new ExtendedLogger(Logger.getLogger(GrizzlyHttpContainer.class.getName()), Level.FINEST);
+
+    private final Type RequestTYPE = (new GenericType<Ref<Request>>() { }).getType();
+    private final Type ResponseTYPE = (new GenericType<Ref<Response>>() { }).getType();
+
+    /**
+     * Cached value of configuration property
+     * {@link org.glassfish.jersey.server.ServerProperties#RESPONSE_SET_STATUS_OVER_SEND_ERROR}.
+     * If {@code true} method {@link org.glassfish.grizzly.http.server.Response#setStatus} is used over
+     * {@link org.glassfish.grizzly.http.server.Response#sendError}.
+     */
+    private boolean configSetStatusOverSendError;
+
+    /**
+     * Cached value of configuration property
+     * {@link org.glassfish.jersey.server.ServerProperties#REDUCE_CONTEXT_PATH_SLASHES_ENABLED}.
+     * If {@code true} method {@link org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpContainer#getRequestUri(Request)}
+     * will reduce the of leading context-path slashes to only one.
+     */
+    private boolean configReduceContextPathSlashesEnabled;
+
+    /**
+     * Referencing factory for Grizzly request.
+     */
+    private static class GrizzlyRequestReferencingFactory extends ReferencingFactory<Request> {
+
+        @Inject
+        public GrizzlyRequestReferencingFactory(final Provider<Ref<Request>> referenceFactory) {
+            super(referenceFactory);
+        }
+    }
+
+    /**
+     * Referencing factory for Grizzly response.
+     */
+    private static class GrizzlyResponseReferencingFactory extends ReferencingFactory<Response> {
+
+        @Inject
+        public GrizzlyResponseReferencingFactory(final Provider<Ref<Response>> referenceFactory) {
+            super(referenceFactory);
+        }
+    }
+
+    /**
+     * An internal binder to enable Grizzly HTTP container specific types injection.
+     * <p/>
+     * This binder allows to inject underlying Grizzly HTTP request and response instances.
+     * Note that since Grizzly {@code Request} class is not proxiable as it does not expose an empty constructor,
+     * the injection of Grizzly request instance into singleton JAX-RS and Jersey providers is only supported via
+     * {@link javax.inject.Provider injection provider}.
+     */
+    static class GrizzlyBinder extends AbstractBinder {
+
+        @Override
+        protected void configure() {
+            bindFactory(GrizzlyRequestReferencingFactory.class).to(Request.class)
+                    .proxy(false).in(RequestScoped.class);
+            bindFactory(ReferencingFactory.<Request>referenceFactory()).to(new GenericType<Ref<Request>>() {})
+                    .in(RequestScoped.class);
+
+            bindFactory(GrizzlyResponseReferencingFactory.class).to(Response.class)
+                    .proxy(true).proxyForSameScope(false).in(RequestScoped.class);
+            bindFactory(ReferencingFactory.<Response>referenceFactory()).to(new GenericType<Ref<Response>>() {})
+                    .in(RequestScoped.class);
+        }
+    }
+
+    private static final CompletionHandler<Response> EMPTY_COMPLETION_HANDLER = new CompletionHandler<Response>() {
+
+        @Override
+        public void cancelled() {
+            // no-op
+        }
+
+        @Override
+        public void failed(final Throwable throwable) {
+            // no-op
+        }
+
+        @Override
+        public void completed(final Response result) {
+            // no-op
+        }
+
+        @Override
+        public void updated(final Response result) {
+            // no-op
+        }
+    };
+
+    private static final class ResponseWriter implements ContainerResponseWriter {
+
+        private final String name;
+        private final Response grizzlyResponse;
+        private final boolean configSetStatusOverSendError;
+
+        ResponseWriter(final Response response, final boolean configSetStatusOverSendError) {
+            this.grizzlyResponse = response;
+            this.configSetStatusOverSendError = configSetStatusOverSendError;
+
+            if (logger.isDebugLoggable()) {
+                this.name = "ResponseWriter {" + "id=" + UUID.randomUUID().toString() + ", grizzlyResponse="
+                        + grizzlyResponse.hashCode() + '}';
+                logger.debugLog("{0} - init", name);
+            } else {
+                this.name = "ResponseWriter";
+            }
+        }
+
+        @Override
+        public String toString() {
+            return name;
+        }
+
+        @Override
+        public void commit() {
+            try {
+                if (grizzlyResponse.isSuspended()) {
+                    grizzlyResponse.resume();
+                }
+            } finally {
+                logger.debugLog("{0} - commit() called", name);
+            }
+        }
+
+        @Override
+        public boolean suspend(final long timeOut, final TimeUnit timeUnit, final TimeoutHandler timeoutHandler) {
+            try {
+                grizzlyResponse.suspend(timeOut, timeUnit, EMPTY_COMPLETION_HANDLER,
+                        new org.glassfish.grizzly.http.server.TimeoutHandler() {
+
+                            @Override
+                            public boolean onTimeout(final Response response) {
+                                if (timeoutHandler != null) {
+                                    timeoutHandler.onTimeout(ResponseWriter.this);
+                                }
+
+                                // TODO should we return true in some cases instead?
+                                // Returning false relies on the fact that the timeoutHandler will resume the response.
+                                return false;
+                            }
+                        }
+                );
+                return true;
+            } catch (final IllegalStateException ex) {
+                return false;
+            } finally {
+                logger.debugLog("{0} - suspend(...) called", name);
+            }
+        }
+
+        @Override
+        public void setSuspendTimeout(final long timeOut, final TimeUnit timeUnit) throws IllegalStateException {
+            try {
+                grizzlyResponse.getSuspendContext().setTimeout(timeOut, timeUnit);
+            } finally {
+                logger.debugLog("{0} - setTimeout(...) called", name);
+            }
+        }
+
+        @Override
+        public OutputStream writeResponseStatusAndHeaders(final long contentLength,
+                                                          final ContainerResponse context)
+                throws ContainerException {
+            try {
+                final javax.ws.rs.core.Response.StatusType statusInfo = context.getStatusInfo();
+                if (statusInfo.getReasonPhrase() == null) {
+                    grizzlyResponse.setStatus(statusInfo.getStatusCode());
+                } else {
+                    grizzlyResponse.setStatus(statusInfo.getStatusCode(), statusInfo.getReasonPhrase());
+                }
+
+                grizzlyResponse.setContentLengthLong(contentLength);
+
+                for (final Map.Entry<String, List<String>> e : context.getStringHeaders().entrySet()) {
+                    for (final String value : e.getValue()) {
+                        grizzlyResponse.addHeader(e.getKey(), value);
+                    }
+                }
+
+                return grizzlyResponse.getOutputStream();
+            } finally {
+                logger.debugLog("{0} - writeResponseStatusAndHeaders() called", name);
+            }
+        }
+
+        @Override
+        @SuppressWarnings("MagicNumber")
+        public void failure(final Throwable error) {
+            try {
+                if (!grizzlyResponse.isCommitted()) {
+                    try {
+                        if (configSetStatusOverSendError) {
+                            grizzlyResponse.reset();
+                            grizzlyResponse.setStatus(500, "Request failed.");
+                        } else {
+                            grizzlyResponse.sendError(500, "Request failed.");
+                        }
+                    } catch (final IllegalStateException ex) {
+                        // a race condition externally committing the response can still occur...
+                        logger.log(Level.FINER, "Unable to reset failed response.", ex);
+                    } catch (final IOException ex) {
+                        throw new ContainerException(
+                                LocalizationMessages.EXCEPTION_SENDING_ERROR_RESPONSE(500, "Request failed."),
+                                ex);
+                    }
+                }
+            } finally {
+                logger.debugLog("{0} - failure(...) called", name);
+                rethrow(error);
+            }
+        }
+
+        @Override
+        public boolean enableResponseBuffering() {
+            return true;
+        }
+
+        /**
+         * Rethrow the original exception as required by JAX-RS, 3.3.4
+         *
+         * @param error throwable to be re-thrown
+         */
+        private void rethrow(final Throwable error) {
+            if (error instanceof RuntimeException) {
+                throw (RuntimeException) error;
+            } else {
+                throw new ContainerException(error);
+            }
+        }
+    }
+
+    private volatile ApplicationHandler appHandler;
+
+    /**
+     * Create a new Grizzly HTTP container.
+     *
+     * @param application JAX-RS / Jersey application to be deployed on Grizzly HTTP container.
+     */
+    /* package */ GrizzlyHttpContainer(final Application application) {
+        this.appHandler = new ApplicationHandler(application, new GrizzlyBinder());
+        cacheConfigSetStatusOverSendError();
+        cacheConfigEnableLeadingContextPathSlashes();
+    }
+
+    /**
+     * Create a new Grizzly HTTP container.
+     *
+     * @param application   JAX-RS / Jersey application to be deployed on Grizzly HTTP container.
+     * @param parentContext DI provider specific context with application's registered bindings.
+     */
+    /* package */ GrizzlyHttpContainer(final Application application, final Object parentContext) {
+        this.appHandler = new ApplicationHandler(application, new GrizzlyBinder(), parentContext);
+        cacheConfigSetStatusOverSendError();
+        cacheConfigEnableLeadingContextPathSlashes();
+    }
+
+    @Override
+    public void start() {
+        super.start();
+        appHandler.onStartup(this);
+    }
+
+    @Override
+    public void service(final Request request, final Response response) {
+        final ResponseWriter responseWriter = new ResponseWriter(response, configSetStatusOverSendError);
+        try {
+            logger.debugLog("GrizzlyHttpContainer.service(...) started");
+            URI baseUri = getBaseUri(request);
+            URI requestUri = getRequestUri(request);
+            final ContainerRequest requestContext = new ContainerRequest(baseUri,
+                    requestUri, request.getMethod().getMethodString(),
+                    getSecurityContext(request), new GrizzlyRequestPropertiesDelegate(request));
+            requestContext.setEntityStream(request.getInputStream());
+            for (final String headerName : request.getHeaderNames()) {
+                requestContext.headers(headerName, request.getHeaders(headerName));
+            }
+            requestContext.setWriter(responseWriter);
+
+            requestContext.setRequestScopedInitializer(injectionManager -> {
+                injectionManager.<Ref<Request>>getInstance(RequestTYPE).set(request);
+                injectionManager.<Ref<Response>>getInstance(ResponseTYPE).set(response);
+            });
+            appHandler.handle(requestContext);
+        } finally {
+            logger.debugLog("GrizzlyHttpContainer.service(...) finished");
+        }
+    }
+
+    private boolean containsContextPath(Request request) {
+        return request.getContextPath() != null && request.getContextPath().length() > 0;
+    }
+
+    @Override
+    public ResourceConfig getConfiguration() {
+        return appHandler.getConfiguration();
+    }
+
+    @Override
+    public void reload() {
+        reload(appHandler.getConfiguration());
+    }
+
+    @Override
+    public void reload(final ResourceConfig configuration) {
+        appHandler.onShutdown(this);
+
+        appHandler = new ApplicationHandler(configuration, new GrizzlyBinder());
+        appHandler.onReload(this);
+        appHandler.onStartup(this);
+        cacheConfigSetStatusOverSendError();
+        cacheConfigEnableLeadingContextPathSlashes();
+    }
+
+    @Override
+    public ApplicationHandler getApplicationHandler() {
+        return appHandler;
+    }
+
+    @Override
+    public void destroy() {
+        super.destroy();
+        appHandler.onShutdown(this);
+        appHandler = null;
+    }
+
+    private SecurityContext getSecurityContext(final Request request) {
+        return new SecurityContext() {
+
+            @Override
+            public boolean isUserInRole(final String role) {
+                return false;
+            }
+
+            @Override
+            public boolean isSecure() {
+                return request.isSecure();
+            }
+
+            @Override
+            public Principal getUserPrincipal() {
+                return request.getUserPrincipal();
+            }
+
+            @Override
+            public String getAuthenticationScheme() {
+                return request.getAuthType();
+            }
+        };
+    }
+
+    private URI getBaseUri(final Request request) {
+        try {
+            return new URI(request.getScheme(), null, request.getServerName(),
+                    request.getServerPort(), getBasePath(request), null, null);
+        } catch (final URISyntaxException ex) {
+            throw new IllegalArgumentException(ex);
+        }
+    }
+
+    private String getBasePath(final Request request) {
+        final String contextPath = request.getContextPath();
+
+        if (contextPath == null || contextPath.isEmpty()) {
+            return "/";
+        } else if (contextPath.charAt(contextPath.length() - 1) != '/') {
+            return contextPath + "/";
+        } else {
+            return contextPath;
+        }
+    }
+
+    private URI getRequestUri(final Request request) {
+        try {
+            final String serverAddress = getServerAddress(request);
+
+            String uri;
+            if (configReduceContextPathSlashesEnabled && containsContextPath(request)) {
+                uri = ContainerUtils.reduceLeadingSlashes(request.getRequestURI());
+            } else {
+                uri = request.getRequestURI();
+            }
+
+            final String queryString = request.getQueryString();
+            if (queryString != null) {
+                uri = uri + "?" + ContainerUtils.encodeUnsafeCharacters(queryString);
+            }
+
+            return new URI(serverAddress + uri);
+        } catch (URISyntaxException ex) {
+            throw new IllegalArgumentException(ex);
+        }
+    }
+
+    private String getServerAddress(final Request request) throws URISyntaxException {
+        return new URI(request.getScheme(), null,  request.getServerName(), request.getServerPort(), null, null, null).toString();
+    }
+
+    /**
+     * The method reads and caches value of configuration property
+     * {@link org.glassfish.jersey.server.ServerProperties#RESPONSE_SET_STATUS_OVER_SEND_ERROR} for future purposes.
+     */
+    private void cacheConfigSetStatusOverSendError() {
+        this.configSetStatusOverSendError = ServerProperties.getValue(getConfiguration().getProperties(),
+                ServerProperties.RESPONSE_SET_STATUS_OVER_SEND_ERROR, false, Boolean.class);
+    }
+
+    /**
+     * The method reads and caches value of configuration property
+     * {@link org.glassfish.jersey.server.ServerProperties#REDUCE_CONTEXT_PATH_SLASHES_ENABLED} for future purposes.
+     */
+    private void cacheConfigEnableLeadingContextPathSlashes() {
+        this.configReduceContextPathSlashesEnabled = ServerProperties.getValue(getConfiguration().getProperties(),
+                ServerProperties.REDUCE_CONTEXT_PATH_SLASHES_ENABLED, false, Boolean.class);
+    }
+}
diff --git a/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpContainerProvider.java b/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpContainerProvider.java
new file mode 100644
index 0000000..4d14227
--- /dev/null
+++ b/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpContainerProvider.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.grizzly2.httpserver;
+
+import javax.ws.rs.ProcessingException;
+import javax.ws.rs.core.Application;
+
+import org.glassfish.jersey.server.spi.ContainerProvider;
+
+import org.glassfish.grizzly.http.server.HttpHandler;
+
+/**
+ * Container provider for containers based on Grizzly {@link org.glassfish.grizzly.http.server.HttpHandler}.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class GrizzlyHttpContainerProvider implements ContainerProvider {
+
+    @Override
+    public <T> T createContainer(Class<T> type, Application application) throws ProcessingException {
+        if (HttpHandler.class == type || GrizzlyHttpContainer.class == type) {
+            return type.cast(new GrizzlyHttpContainer(application));
+        }
+
+        return null;
+    }
+}
diff --git a/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServerFactory.java b/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServerFactory.java
new file mode 100644
index 0000000..fed3ec7
--- /dev/null
+++ b/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyHttpServerFactory.java
@@ -0,0 +1,282 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.grizzly2.httpserver;
+
+import java.io.IOException;
+import java.net.URI;
+
+import javax.ws.rs.ProcessingException;
+
+import org.glassfish.jersey.grizzly2.httpserver.internal.LocalizationMessages;
+import org.glassfish.jersey.internal.guava.ThreadFactoryBuilder;
+import org.glassfish.jersey.process.JerseyProcessingUncaughtExceptionHandler;
+import org.glassfish.jersey.server.ApplicationHandler;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.spi.Container;
+
+import org.glassfish.grizzly.http.server.HttpHandler;
+import org.glassfish.grizzly.http.server.HttpHandlerRegistration;
+import org.glassfish.grizzly.http.server.HttpServer;
+import org.glassfish.grizzly.http.server.NetworkListener;
+import org.glassfish.grizzly.http.server.ServerConfiguration;
+import org.glassfish.grizzly.ssl.SSLEngineConfigurator;
+import org.glassfish.grizzly.utils.Charsets;
+
+/**
+ * Factory for creating Grizzly Http Server.
+ * <p>
+ * Should you need to fine tune the underlying Grizzly transport layer, you can obtain direct access to the corresponding
+ * Grizzly structures with <tt>server.getListener("grizzly").getTransport()</tt>. To make certain options take effect,
+ * you need to work with an inactive HttpServer instance (that is the one that has not been started yet).
+ * To obtain such an instance, use one of the below factory methods with {@code start} parameter set to {@code false}.
+ * When the {@code start} parameter is not present, the factory method returns an already started instance.
+ * </p>
+ *
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ * @see HttpServer
+ * @see GrizzlyHttpContainer
+ */
+public final class GrizzlyHttpServerFactory {
+
+    /**
+     * Create new {@link HttpServer} instance.
+     *
+     * @param uri uri on which the {@link ApplicationHandler} will be deployed. Only first path segment will be used as
+     *            context path, the rest will be ignored.
+     * @return newly created {@code HttpServer}.
+     * @throws ProcessingException in case of any failure when creating a new {@code HttpServer} instance.
+     */
+    public static HttpServer createHttpServer(final URI uri) {
+        return createHttpServer(uri, (GrizzlyHttpContainer) null, false, null, true);
+    }
+
+    /**
+     * Create new {@link HttpServer} instance.
+     *
+     * @param uri   uri on which the {@link ApplicationHandler} will be deployed. Only first path segment will be used
+     *              as context path, the rest will be ignored.
+     * @param start if set to false, server will not get started, which allows to configure the underlying transport
+     *              layer, see above for details.
+     * @return newly created {@code HttpServer}.
+     * @throws ProcessingException in case of any failure when creating a new {@code HttpServer} instance.
+     */
+    public static HttpServer createHttpServer(final URI uri, final boolean start) {
+        return createHttpServer(uri, (GrizzlyHttpContainer) null, false, null, start);
+    }
+
+    /**
+     * Create new {@link HttpServer} instance.
+     *
+     * @param uri           URI on which the Jersey web application will be deployed. Only first path segment will be
+     *                      used as context path, the rest will be ignored.
+     * @param configuration web application configuration.
+     * @return newly created {@code HttpServer}.
+     * @throws ProcessingException in case of any failure when creating a new {@code HttpServer} instance.
+     */
+    public static HttpServer createHttpServer(final URI uri, final ResourceConfig configuration) {
+        return createHttpServer(
+                uri,
+                new GrizzlyHttpContainer(configuration),
+                false,
+                null,
+                true
+        );
+    }
+
+    /**
+     * Create new {@link HttpServer} instance.
+     *
+     * @param uri           URI on which the Jersey web application will be deployed. Only first path segment will be
+     *                      used as context path, the rest will be ignored.
+     * @param configuration web application configuration.
+     * @param start         if set to false, server will not get started, which allows to configure the underlying
+     *                      transport layer, see above for details.
+     * @return newly created {@code HttpServer}.
+     * @throws ProcessingException in case of any failure when creating a new {@code HttpServer} instance.
+     */
+    public static HttpServer createHttpServer(final URI uri, final ResourceConfig configuration, final boolean start) {
+        return createHttpServer(
+                uri,
+                new GrizzlyHttpContainer(configuration),
+                false,
+                null,
+                start);
+    }
+
+    /**
+     * Create new {@link HttpServer} instance.
+     *
+     * @param uri                   URI on which the Jersey web application will be deployed. Only first path segment
+     *                              will be used as context path, the rest will be ignored.
+     * @param configuration         web application configuration.
+     * @param secure                used for call {@link NetworkListener#setSecure(boolean)}.
+     * @param sslEngineConfigurator Ssl settings to be passed to {@link NetworkListener#setSSLEngineConfig}.
+     * @return newly created {@code HttpServer}.
+     * @throws ProcessingException in case of any failure when creating a new {@code HttpServer} instance.
+     */
+    public static HttpServer createHttpServer(final URI uri,
+                                              final ResourceConfig configuration,
+                                              final boolean secure,
+                                              final SSLEngineConfigurator sslEngineConfigurator) {
+        return createHttpServer(
+                uri,
+                new GrizzlyHttpContainer(configuration),
+                secure,
+                sslEngineConfigurator,
+                true);
+    }
+
+    /**
+     * Create new {@link HttpServer} instance.
+     *
+     * @param uri                   URI on which the Jersey web application will be deployed. Only first path segment
+     *                              will be used as context path, the rest will be ignored.
+     * @param configuration         web application configuration.
+     * @param secure                used for call {@link NetworkListener#setSecure(boolean)}.
+     * @param sslEngineConfigurator Ssl settings to be passed to {@link NetworkListener#setSSLEngineConfig}.
+     * @param start                 if set to false, server will not get started, which allows to configure the
+     *                              underlying transport, see above for details.
+     * @return newly created {@code HttpServer}.
+     * @throws ProcessingException in case of any failure when creating a new {@code HttpServer} instance.
+     */
+    public static HttpServer createHttpServer(final URI uri,
+                                              final ResourceConfig configuration,
+                                              final boolean secure,
+                                              final SSLEngineConfigurator sslEngineConfigurator,
+                                              final boolean start) {
+        return createHttpServer(
+                uri,
+                new GrizzlyHttpContainer(configuration),
+                secure,
+                sslEngineConfigurator,
+                start);
+    }
+
+    /**
+     * Create new {@link HttpServer} instance.
+     *
+     * @param uri                   uri on which the {@link ApplicationHandler} will be deployed. Only first path
+     *                              segment will be used as context path, the rest will be ignored.
+     * @param config                web application configuration.
+     * @param secure                used for call {@link NetworkListener#setSecure(boolean)}.
+     * @param sslEngineConfigurator Ssl settings to be passed to {@link NetworkListener#setSSLEngineConfig}.
+     * @param parentContext         DI provider specific context with application's registered bindings.
+     * @return newly created {@code HttpServer}.
+     * @throws ProcessingException in case of any failure when creating a new {@code HttpServer} instance.
+     * @see GrizzlyHttpContainer
+     * @since 2.12
+     */
+    public static HttpServer createHttpServer(final URI uri,
+                                              final ResourceConfig config,
+                                              final boolean secure,
+                                              final SSLEngineConfigurator sslEngineConfigurator,
+                                              final Object parentContext) {
+        return createHttpServer(uri, new GrizzlyHttpContainer(config, parentContext), secure, sslEngineConfigurator,
+                true);
+    }
+
+    /**
+     * Create new {@link HttpServer} instance.
+     *
+     * @param uri           uri on which the {@link ApplicationHandler} will be deployed. Only first path
+     *                      segment will be used as context path, the rest will be ignored.
+     * @param config        web application configuration.
+     * @param parentContext DI provider specific context with application's registered bindings.
+     * @return newly created {@code HttpServer}.
+     * @throws ProcessingException in case of any failure when creating a new {@code HttpServer} instance.
+     * @see GrizzlyHttpContainer
+     * @since 2.12
+     */
+    public static HttpServer createHttpServer(final URI uri,
+                                              final ResourceConfig config,
+                                              final Object parentContext) {
+        return createHttpServer(uri, new GrizzlyHttpContainer(config, parentContext), false, null, true);
+    }
+
+    /**
+     * Create new {@link HttpServer} instance.
+     *
+     * @param uri                   uri on which the {@link ApplicationHandler} will be deployed. Only first path
+     *                              segment will be used as context path, the rest will be ignored.
+     * @param handler               {@link HttpHandler} instance.
+     * @param secure                used for call {@link NetworkListener#setSecure(boolean)}.
+     * @param sslEngineConfigurator Ssl settings to be passed to {@link NetworkListener#setSSLEngineConfig}.
+     * @param start                 if set to false, server will not get started, this allows end users to set
+     *                              additional properties on the underlying listener.
+     * @return newly created {@code HttpServer}.
+     * @throws ProcessingException in case of any failure when creating a new {@code HttpServer} instance.
+     * @see GrizzlyHttpContainer
+     */
+    public static HttpServer createHttpServer(final URI uri,
+                                              final GrizzlyHttpContainer handler,
+                                              final boolean secure,
+                                              final SSLEngineConfigurator sslEngineConfigurator,
+                                              final boolean start) {
+
+        final String host = (uri.getHost() == null) ? NetworkListener.DEFAULT_NETWORK_HOST : uri.getHost();
+        final int port = (uri.getPort() == -1)
+                ? (secure ? Container.DEFAULT_HTTPS_PORT : Container.DEFAULT_HTTP_PORT)
+                : uri.getPort();
+
+        final NetworkListener listener = new NetworkListener("grizzly", host, port);
+
+        listener.getTransport().getWorkerThreadPoolConfig().setThreadFactory(new ThreadFactoryBuilder()
+                .setNameFormat("grizzly-http-server-%d")
+                .setUncaughtExceptionHandler(new JerseyProcessingUncaughtExceptionHandler())
+                .build());
+
+        listener.setSecure(secure);
+        if (sslEngineConfigurator != null) {
+            listener.setSSLEngineConfig(sslEngineConfigurator);
+        }
+
+        final HttpServer server = new HttpServer();
+        server.addListener(listener);
+
+        // Map the path to the processor.
+        final ServerConfiguration config = server.getServerConfiguration();
+        if (handler != null) {
+            final String path = uri.getPath().replaceAll("/{2,}", "/");
+
+            final String contextPath = path.endsWith("/") ? path.substring(0, path.length() - 1) : path;
+            config.addHttpHandler(handler, HttpHandlerRegistration.bulder().contextPath(contextPath).build());
+        }
+
+        config.setPassTraceRequest(true);
+        config.setDefaultQueryEncoding(Charsets.UTF8_CHARSET);
+
+        if (start) {
+            try {
+                // Start the server.
+                server.start();
+            } catch (final IOException ex) {
+                server.shutdownNow();
+                throw new ProcessingException(LocalizationMessages.FAILED_TO_START_SERVER(ex.getMessage()), ex);
+            }
+        }
+
+        return server;
+    }
+
+    /**
+     * Prevents instantiation.
+     */
+    private GrizzlyHttpServerFactory() {
+    }
+}
diff --git a/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyRequestPropertiesDelegate.java b/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyRequestPropertiesDelegate.java
new file mode 100644
index 0000000..d14917f
--- /dev/null
+++ b/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/GrizzlyRequestPropertiesDelegate.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.grizzly2.httpserver;
+
+import java.util.Collection;
+
+import org.glassfish.jersey.internal.PropertiesDelegate;
+
+import org.glassfish.grizzly.http.server.Request;
+
+/**
+ * Grizzly container {@link PropertiesDelegate properties delegate}.
+ *
+ * @author Martin Matula
+ */
+class GrizzlyRequestPropertiesDelegate implements PropertiesDelegate {
+    private final Request request;
+
+    /**
+     * Create new Grizzly container properties delegate instance.
+     *
+     * @param request grizzly HTTP request.
+     */
+    GrizzlyRequestPropertiesDelegate(Request request) {
+        this.request = request;
+    }
+
+    @Override
+    public Object getProperty(String name) {
+        return request.getAttribute(name);
+    }
+
+    @Override
+    public Collection<String> getPropertyNames() {
+        return request.getAttributeNames();
+    }
+
+    @Override
+    public void setProperty(String name, Object value) {
+        request.setAttribute(name, value);
+    }
+
+    @Override
+    public void removeProperty(String name) {
+        request.removeAttribute(name);
+    }
+}
diff --git a/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/package-info.java b/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/package-info.java
new file mode 100644
index 0000000..75cf389
--- /dev/null
+++ b/containers/grizzly2-http/src/main/java/org/glassfish/jersey/grizzly2/httpserver/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+/**
+ * Jersey Grizzly 2.x container classes.
+ */
+package org.glassfish.jersey.grizzly2.httpserver;
diff --git a/containers/grizzly2-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ContainerProvider b/containers/grizzly2-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ContainerProvider
new file mode 100644
index 0000000..57f405d
--- /dev/null
+++ b/containers/grizzly2-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ContainerProvider
@@ -0,0 +1 @@
+org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpContainerProvider
\ No newline at end of file
diff --git a/containers/grizzly2-http/src/main/resources/org/glassfish/jersey/grizzly2/httpserver/internal/localization.properties b/containers/grizzly2-http/src/main/resources/org/glassfish/jersey/grizzly2/httpserver/internal/localization.properties
new file mode 100644
index 0000000..83ce439
--- /dev/null
+++ b/containers/grizzly2-http/src/main/resources/org/glassfish/jersey/grizzly2/httpserver/internal/localization.properties
@@ -0,0 +1,20 @@
+#
+# Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Eclipse Public License v. 2.0, which is available at
+# http://www.eclipse.org/legal/epl-2.0.
+#
+# This Source Code may also be made available under the following Secondary
+# Licenses when the conditions for such availability set forth in the
+# Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+# version 2 with the GNU Classpath Exception, which is available at
+# https://www.gnu.org/software/classpath/license.html.
+#
+# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+#
+
+# {0} - status code; {1} - status reason message
+exception.sending.error.response=I/O exception occurred while sending "{0}/{1}" error response.
+# {0} - exception message
+failed.to.start.server=Failed to start Grizzly HTTP server: {0}
diff --git a/containers/grizzly2-servlet/pom.xml b/containers/grizzly2-servlet/pom.xml
new file mode 100644
index 0000000..caee668
--- /dev/null
+++ b/containers/grizzly2-servlet/pom.xml
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.glassfish.jersey.containers</groupId>
+        <artifactId>project</artifactId>
+        <version>2.28-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>jersey-container-grizzly2-servlet</artifactId>
+    <packaging>jar</packaging>
+    <name>jersey-container-grizzly2-servlet</name>
+
+    <description>Grizzly 2 Servlet Container.</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>javax.servlet-api</artifactId>
+            <version>${servlet4.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.glassfish.jersey.containers</groupId>
+            <artifactId>jersey-container-servlet</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.glassfish.jersey.containers</groupId>
+            <artifactId>jersey-container-grizzly2-http</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.grizzly</groupId>
+            <artifactId>grizzly-http-servlet</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <resources>
+            <resource>
+                <directory>${basedir}/src/main/java</directory>
+                <includes>
+                    <include>META-INF/**/*</include>
+                </includes>
+            </resource>
+            <resource>
+                <directory>${basedir}/src/main/resources</directory>
+                <filtering>true</filtering>
+            </resource>
+        </resources>
+        <plugins>
+           <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <inherited>true</inherited>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Import-Package>
+                            javax.servlet.*;version="[2.4,5.0)",
+                            *
+                        </Import-Package>
+                    </instructions>
+                    <unpackBundle>true</unpackBundle>
+                </configuration>
+             </plugin>
+         </plugins>
+     </build>
+</project>
diff --git a/containers/grizzly2-servlet/src/main/java/org/glassfish/jersey/grizzly2/servlet/GrizzlyWebContainerFactory.java b/containers/grizzly2-servlet/src/main/java/org/glassfish/jersey/grizzly2/servlet/GrizzlyWebContainerFactory.java
new file mode 100644
index 0000000..db969dd
--- /dev/null
+++ b/containers/grizzly2-servlet/src/main/java/org/glassfish/jersey/grizzly2/servlet/GrizzlyWebContainerFactory.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.grizzly2.servlet;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.Map;
+
+import javax.servlet.Servlet;
+
+import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
+import org.glassfish.jersey.servlet.ServletContainer;
+import org.glassfish.jersey.uri.UriComponent;
+
+import org.glassfish.grizzly.http.server.HttpServer;
+import org.glassfish.grizzly.servlet.ServletRegistration;
+import org.glassfish.grizzly.servlet.WebappContext;
+
+/**
+ * Factory for creating and starting Grizzly 2 {@link HttpServer} instances
+ * for deploying a {@code Servlet}.
+ * <p/>
+ * The default deployed server is an instance of {@link ServletContainer}.
+ * <p/>
+ * If no initialization parameters are declared (or is null) then root
+ * resource and provider classes will be found by searching the classes
+ * referenced in the java classpath.
+ *
+ * @author Paul Sandoz
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ */
+public final class GrizzlyWebContainerFactory {
+
+    private GrizzlyWebContainerFactory() {
+    }
+
+    /**
+     * Create a {@link HttpServer} that registers the {@link ServletContainer}.
+     *
+     * @param u the URI to create the http server. The URI scheme must be
+     *          equal to "http". The URI user information and host
+     *          are ignored If the URI port is not present then port 80 will be
+     *          used. The URI query and fragment components are ignored. Only first path segment will be used
+     *          as context path, the rest will be ignored.
+     * @return the http server, with the endpoint started.
+     *
+     * @throws java.io.IOException      if an error occurs creating the container.
+     * @throws IllegalArgumentException if {@code u} is {@code null}.
+     */
+    public static HttpServer create(String u)
+            throws IOException, IllegalArgumentException {
+        if (u == null) {
+            throw new IllegalArgumentException("The URI must not be null");
+        }
+
+        return create(URI.create(u));
+    }
+
+    /**
+     * Create a {@link HttpServer} that registers the {@link ServletContainer}.
+     *
+     * @param u          the URI to create the http server. The URI scheme must be
+     *                   equal to "http". The URI user information and host
+     *                   are ignored If the URI port is not present then port 80 will be
+     *                   used. The URI query and fragment components are ignored. Only first path segment will be used
+     *                   as context path, the rest will be ignored.
+     * @param initParams the servlet initialization parameters.
+     * @return the http server, with the endpoint started.
+     *
+     * @throws IOException              if an error occurs creating the container.
+     * @throws IllegalArgumentException if {@code u} is {@code null}.
+     */
+    public static HttpServer create(String u, Map<String, String> initParams)
+            throws IOException, IllegalArgumentException {
+        if (u == null) {
+            throw new IllegalArgumentException("The URI must not be null");
+        }
+
+        return create(URI.create(u), initParams);
+    }
+
+    /**
+     * Create a {@link HttpServer} that registers the {@link ServletContainer}.
+     *
+     * @param u the URI to create the http server. The URI scheme must be
+     *          equal to "http". The URI user information and host
+     *          are ignored If the URI port is not present then port 80 will be
+     *          used. The URI query and fragment components are ignored. Only first path segment will be used
+     *          as context path, the rest will be ignored.
+     * @return the http server, with the endpoint started.
+     *
+     * @throws IOException              if an error occurs creating the container.
+     * @throws IllegalArgumentException if {@code u} is {@code null}.
+     */
+    public static HttpServer create(URI u)
+            throws IOException, IllegalArgumentException {
+        return create(u, ServletContainer.class);
+    }
+
+    /**
+     * Create a {@link HttpServer} that registers the {@link ServletContainer}.
+     *
+     * @param u          the URI to create the http server. The URI scheme must be
+     *                   equal to "http". The URI user information and host
+     *                   are ignored If the URI port is not present then port 80 will be
+     *                   used. The URI query and fragment components are ignored. Only first path segment will be used
+     *                   as context path, the rest will be ignored.
+     * @param initParams the servlet initialization parameters.
+     * @return the http server, with the endpoint started.
+     *
+     * @throws IOException              if an error occurs creating the container.
+     * @throws IllegalArgumentException if {@code u} is {@code null}.
+     */
+    public static HttpServer create(URI u,
+                                    Map<String, String> initParams) throws IOException {
+        return create(u, ServletContainer.class, initParams);
+    }
+
+    /**
+     * Create a {@link HttpServer} that registers the declared
+     * servlet class.
+     *
+     * @param u the URI to create the http server. The URI scheme must be
+     *          equal to "http". The URI user information and host
+     *          are ignored If the URI port is not present then port 80 will be
+     *          used. The URI query and fragment components are ignored. Only first path segment will be used
+     *          as context path, the rest will be ignored.
+     * @param c the servlet class.
+     * @return the http server, with the endpoint started.
+     *
+     * @throws IOException              if an error occurs creating the container.
+     * @throws IllegalArgumentException if {@code u} is {@code null}.
+     */
+    public static HttpServer create(String u, Class<? extends Servlet> c) throws IOException {
+        if (u == null) {
+            throw new IllegalArgumentException("The URI must not be null");
+        }
+
+        return create(URI.create(u), c);
+    }
+
+    /**
+     * Create a {@link HttpServer} that registers the declared
+     * servlet class.
+     *
+     * @param u          the URI to create the http server. The URI scheme must be
+     *                   equal to "http". The URI user information and host
+     *                   are ignored If the URI port is not present then port 80 will be
+     *                   used. The URI query and fragment components are ignored. Only first path segment will be used
+     *                   as context path, the rest will be ignored.
+     * @param c          the servlet class.
+     * @param initParams the servlet initialization parameters.
+     * @return the http server, with the endpoint started.
+     *
+     * @throws IOException              if an error occurs creating the container.
+     * @throws IllegalArgumentException if {@code u} is {@code null}.
+     */
+    public static HttpServer create(String u, Class<? extends Servlet> c,
+                                    Map<String, String> initParams) throws IOException {
+        if (u == null) {
+            throw new IllegalArgumentException("The URI must not be null");
+        }
+
+        return create(URI.create(u), c, initParams);
+    }
+
+    /**
+     * Create a {@link HttpServer} that registers the declared
+     * servlet class.
+     *
+     * @param u the URI to create the http server. The URI scheme must be
+     *          equal to "http". The URI user information and host
+     *          are ignored If the URI port is not present then port 80 will be
+     *          used. The URI query and fragment components are ignored. Only first path segment will be used
+     *          as context path, the rest will be ignored.
+     * @param c the servlet class
+     * @return the http server, with the endpoint started.
+     *
+     * @throws IOException              if an error occurs creating the container.
+     * @throws IllegalArgumentException if {@code u} is {@code null}.
+     */
+    public static HttpServer create(URI u, Class<? extends Servlet> c) throws IOException {
+        return create(u, c, null);
+    }
+
+    /**
+     * Create a {@link HttpServer} that registers the declared
+     * servlet class.
+     *
+     * @param u          the URI to create the http server. The URI scheme must be
+     *                   equal to "http". The URI user information and host
+     *                   are ignored If the URI port is not present then port 80 will be
+     *                   used. The URI query and fragment components are ignored. Only first path segment will be used
+     *                   as context path, the rest will be ignored.
+     * @param c          the servlet class
+     * @param initParams the servlet initialization parameters.
+     * @return the http server, with the endpoint started.
+     *
+     * @throws IOException              if an error occurs creating the container.
+     * @throws IllegalArgumentException if {@code u} is {@code null}.
+     */
+    public static HttpServer create(URI u, Class<? extends Servlet> c,
+                                    Map<String, String> initParams) throws IOException {
+        return create(u, c, null, initParams, null);
+    }
+
+    private static HttpServer create(URI u, Class<? extends Servlet> c, Servlet servlet,
+                                     Map<String, String> initParams, Map<String, String> contextInitParams)
+            throws IOException {
+        if (u == null) {
+            throw new IllegalArgumentException("The URI must not be null");
+        }
+
+        String path = u.getPath();
+        if (path == null) {
+            throw new IllegalArgumentException("The URI path, of the URI " + u + ", must be non-null");
+        } else if (path.isEmpty()) {
+            throw new IllegalArgumentException("The URI path, of the URI " + u + ", must be present");
+        } else if (path.charAt(0) != '/') {
+            throw new IllegalArgumentException("The URI path, of the URI " + u + ". must start with a '/'");
+        }
+
+        path = String.format("/%s", UriComponent.decodePath(u.getPath(), true).get(1).toString());
+
+        WebappContext context = new WebappContext("GrizzlyContext", path);
+        ServletRegistration registration;
+        if (c != null) {
+            registration = context.addServlet(c.getName(), c);
+        } else {
+            registration = context.addServlet(servlet.getClass().getName(), servlet);
+        }
+        registration.addMapping("/*");
+
+        if (contextInitParams != null) {
+            for (Map.Entry<String, String> e : contextInitParams.entrySet()) {
+                context.setInitParameter(e.getKey(), e.getValue());
+            }
+        }
+
+        if (initParams != null) {
+            registration.setInitParameters(initParams);
+        }
+
+        HttpServer server = GrizzlyHttpServerFactory.createHttpServer(u);
+        context.deploy(server);
+        return server;
+    }
+
+    /**
+     * Create a {@link HttpServer} that registers the declared servlet instance.
+     *
+     * @param u                 the URI to create the http server. The URI scheme must be
+     *                          equal to "http". The URI user information and host
+     *                          are ignored If the URI port is not present then port 80 will be
+     *                          used. The URI query and fragment components are ignored. Only first path segment will be used
+     *                          as context path, the rest will be ignored.
+     * @param servlet           the servlet instance.
+     * @param initParams        the servlet initialization parameters.
+     * @param contextInitParams the servlet context initialization parameters.
+     * @return the http server, with the endpoint started.
+     *
+     * @throws IOException              if an error occurs creating the container.
+     * @throws IllegalArgumentException if {@code u} is {@code null}.
+     */
+    public static HttpServer create(URI u, Servlet servlet, Map<String, String> initParams, Map<String, String> contextInitParams)
+            throws IOException {
+        if (servlet == null) {
+            throw new IllegalArgumentException("The servlet must not be null");
+        }
+        return create(u, null, servlet, initParams, contextInitParams);
+    }
+}
diff --git a/containers/grizzly2-servlet/src/main/java/org/glassfish/jersey/grizzly2/servlet/package-info.java b/containers/grizzly2-servlet/src/main/java/org/glassfish/jersey/grizzly2/servlet/package-info.java
new file mode 100644
index 0000000..41a7e50
--- /dev/null
+++ b/containers/grizzly2-servlet/src/main/java/org/glassfish/jersey/grizzly2/servlet/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+/**
+ * Jersey Grizzly 2.x Servlet container classes.
+ */
+package org.glassfish.jersey.grizzly2.servlet;
diff --git a/containers/jdk-http/pom.xml b/containers/jdk-http/pom.xml
new file mode 100644
index 0000000..41c87bd
--- /dev/null
+++ b/containers/jdk-http/pom.xml
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.glassfish.jersey.containers</groupId>
+        <artifactId>project</artifactId>
+        <version>2.28-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>jersey-container-jdk-http</artifactId>
+    <packaging>jar</packaging>
+    <name>jersey-container-jdk-http</name>
+
+    <description>JDK Http Container</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>com.sun.istack</groupId>
+                <artifactId>maven-istack-commons-plugin</artifactId>
+                <inherited>true</inherited>
+            </plugin>
+
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>build-helper-maven-plugin</artifactId>
+                <inherited>true</inherited>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <inherited>true</inherited>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+            </plugin>
+        </plugins>
+
+        <resources>
+            <resource>
+                <directory>${basedir}/src/main/java</directory>
+                <includes>
+                    <include>META-INF/**/*</include>
+                </includes>
+            </resource>
+            <resource>
+                <directory>${basedir}/src/main/resources</directory>
+                <filtering>true</filtering>
+            </resource>
+        </resources>
+    </build>
+
+    <profiles>
+        <profile>
+            <id>windows</id>
+            <activation>
+                <os>
+                    <family>windows</family>
+                </os>
+            </activation>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-surefire-plugin</artifactId>
+                        <configuration>
+                            <!-- Exclude unit tests regarding JDK HTTP Server because of failing a server shutdown in a class
+                            with several tests. "java.net.BindException: Address already in use: bind"
+                            bug reported on https://bugs.openjdk.java.net/browse/JDK-8015692 -->
+                            <excludes>
+                                <exclude>org/glassfish/jersey/jdkhttp/BasicJdkHttpServerTest.java</exclude>
+                                <exclude>org/glassfish/jersey/jdkhttp/JdkHttpPackageTest.java</exclude>
+                                <exclude>org/glassfish/jersey/jdkhttp/JdkHttpsServerTest.java</exclude>
+                                <exclude>org/glassfish/jersey/jdkhttp/LifecycleListenerTest.java</exclude>
+                            </excludes>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
+
+</project>
diff --git a/containers/jdk-http/src/main/java/org/glassfish/jersey/jdkhttp/JdkHttpHandlerContainer.java b/containers/jdk-http/src/main/java/org/glassfish/jersey/jdkhttp/JdkHttpHandlerContainer.java
new file mode 100644
index 0000000..051c5ca
--- /dev/null
+++ b/containers/jdk-http/src/main/java/org/glassfish/jersey/jdkhttp/JdkHttpHandlerContainer.java
@@ -0,0 +1,351 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jdkhttp;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.Principal;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.SecurityContext;
+import javax.ws.rs.core.UriBuilder;
+
+import org.glassfish.jersey.internal.MapPropertiesDelegate;
+import org.glassfish.jersey.jdkhttp.internal.LocalizationMessages;
+import org.glassfish.jersey.server.ApplicationHandler;
+import org.glassfish.jersey.server.ContainerException;
+import org.glassfish.jersey.server.ContainerRequest;
+import org.glassfish.jersey.server.ContainerResponse;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.spi.Container;
+import org.glassfish.jersey.server.spi.ContainerResponseWriter;
+
+import com.sun.net.httpserver.Headers;
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpServer;
+import com.sun.net.httpserver.HttpsExchange;
+
+/**
+ * Jersey {@code Container} implementation based on Java SE {@link HttpServer}.
+ *
+ * @author Miroslav Fuksa
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class JdkHttpHandlerContainer implements HttpHandler, Container {
+
+    private static final Logger LOGGER = Logger.getLogger(JdkHttpHandlerContainer.class.getName());
+
+    private volatile ApplicationHandler appHandler;
+
+    /**
+     * Create new lightweight Java SE HTTP server container.
+     *
+     * @param application JAX-RS / Jersey application to be deployed on the container.
+     */
+    JdkHttpHandlerContainer(final Application application) {
+        this.appHandler = new ApplicationHandler(application);
+    }
+
+    /**
+     * Create new lightweight Java SE HTTP server container.
+     *
+     * @param application   JAX-RS / Jersey application to be deployed on the container.
+     * @param parentContext DI provider specific context with application's registered bindings.
+     */
+    JdkHttpHandlerContainer(final Application application, final Object parentContext) {
+        this.appHandler = new ApplicationHandler(application, null, parentContext);
+    }
+
+    @Override
+    public void handle(final HttpExchange exchange) throws IOException {
+        /**
+         * This is a URI that contains the path, query and fragment components.
+         */
+        URI exchangeUri = exchange.getRequestURI();
+
+        /**
+         * The base path specified by the HTTP context of the HTTP handler. It
+         * is in decoded form.
+         */
+        String decodedBasePath = exchange.getHttpContext().getPath();
+
+        // Ensure that the base path ends with a '/'
+        if (!decodedBasePath.endsWith("/")) {
+            if (decodedBasePath.equals(exchangeUri.getPath())) {
+                /**
+                 * This is an edge case where the request path does not end in a
+                 * '/' and is equal to the context path of the HTTP handler.
+                 * Both the request path and base path need to end in a '/'
+                 * Currently the request path is modified.
+                 *
+                 * TODO support redirection in accordance with resource configuration feature.
+                 */
+                exchangeUri = UriBuilder.fromUri(exchangeUri)
+                        .path("/").build();
+            }
+            decodedBasePath += "/";
+        }
+
+        /*
+         * The following is madness, there is no easy way to get the complete
+         * URI of the HTTP request!!
+         *
+         * TODO this is missing the user information component, how can this be obtained?
+         */
+        final boolean isSecure = exchange instanceof HttpsExchange;
+        final String scheme = isSecure ? "https" : "http";
+
+        final URI baseUri = getBaseUri(exchange, decodedBasePath, scheme);
+        final URI requestUri = getRequestUri(exchange, baseUri);
+
+        final ResponseWriter responseWriter = new ResponseWriter(exchange);
+        final ContainerRequest requestContext = new ContainerRequest(baseUri, requestUri,
+                exchange.getRequestMethod(), getSecurityContext(exchange.getPrincipal(), isSecure),
+                new MapPropertiesDelegate());
+        requestContext.setEntityStream(exchange.getRequestBody());
+        requestContext.getHeaders().putAll(exchange.getRequestHeaders());
+        requestContext.setWriter(responseWriter);
+        try {
+            appHandler.handle(requestContext);
+        } finally {
+            // if the response was not committed yet by the JerseyApplication
+            // then commit it and log warning
+            responseWriter.closeAndLogWarning();
+        }
+    }
+
+    private URI getBaseUri(final HttpExchange exchange, final String decodedBasePath, final String scheme) {
+        final URI baseUri;
+        try {
+            final List<String> hostHeader = exchange.getRequestHeaders().get("Host");
+            if (hostHeader != null) {
+                baseUri = new URI(scheme + "://" + hostHeader.get(0) + decodedBasePath);
+            } else {
+                final InetSocketAddress addr = exchange.getLocalAddress();
+                baseUri = new URI(scheme, null, addr.getHostName(), addr.getPort(),
+                        decodedBasePath, null, null);
+            }
+        } catch (final URISyntaxException ex) {
+            throw new IllegalArgumentException(ex);
+        }
+        return baseUri;
+    }
+
+    private URI getRequestUri(final HttpExchange exchange, final URI baseUri) {
+        try {
+            return new URI(getServerAddress(baseUri) + exchange.getRequestURI());
+        } catch (URISyntaxException ex) {
+            throw new IllegalArgumentException(ex);
+        }
+    }
+
+    private String getServerAddress(final URI baseUri) throws URISyntaxException {
+        return new URI(baseUri.getScheme(), null,  baseUri.getHost(), baseUri.getPort(), null, null, null).toString();
+    }
+
+    private SecurityContext getSecurityContext(final Principal principal, final boolean isSecure) {
+        return new SecurityContext() {
+
+            @Override
+            public boolean isUserInRole(final String role) {
+                return false;
+            }
+
+            @Override
+            public boolean isSecure() {
+                return isSecure;
+            }
+
+            @Override
+            public Principal getUserPrincipal() {
+                return principal;
+            }
+
+            @Override
+            public String getAuthenticationScheme() {
+                return null;
+            }
+        };
+    }
+
+    @Override
+    public ResourceConfig getConfiguration() {
+        return appHandler.getConfiguration();
+    }
+
+    @Override
+    public void reload() {
+        reload(getConfiguration());
+    }
+
+    @Override
+    public void reload(final ResourceConfig configuration) {
+        appHandler.onShutdown(this);
+
+        appHandler = new ApplicationHandler(configuration);
+        appHandler.onReload(this);
+        appHandler.onStartup(this);
+    }
+
+    @Override
+    public ApplicationHandler getApplicationHandler() {
+        return appHandler;
+    }
+
+    /**
+     * Inform this container that the server has been started.
+     *
+     * This method must be implicitly called after the server containing this container is started.
+     */
+    void onServerStart() {
+        this.appHandler.onStartup(this);
+    }
+
+    /**
+     * Inform this container that the server is being stopped.
+     *
+     * This method must be implicitly called before the server containing this container is stopped.
+     */
+    void onServerStop() {
+        this.appHandler.onShutdown(this);
+    }
+
+    private static final class ResponseWriter implements ContainerResponseWriter {
+
+        private final HttpExchange exchange;
+        private final AtomicBoolean closed;
+
+        /**
+         * Creates a new ResponseWriter for given {@link HttpExchange HTTP Exchange}.
+         *
+         * @param exchange Exchange of the {@link HttpServer JDK Http Server}
+         */
+        ResponseWriter(final HttpExchange exchange) {
+            this.exchange = exchange;
+            this.closed = new AtomicBoolean(false);
+        }
+
+        @Override
+        public OutputStream writeResponseStatusAndHeaders(final long contentLength, final ContainerResponse context)
+                throws ContainerException {
+            final MultivaluedMap<String, String> responseHeaders = context.getStringHeaders();
+            final Headers serverHeaders = exchange.getResponseHeaders();
+            for (final Map.Entry<String, List<String>> e : responseHeaders.entrySet()) {
+                for (final String value : e.getValue()) {
+                    serverHeaders.add(e.getKey(), value);
+                }
+            }
+
+            try {
+                if (context.getStatus() == Response.Status.NO_CONTENT.getStatusCode()) {
+                    // Work around bug in LW HTTP server
+                    // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6886436
+                    exchange.sendResponseHeaders(context.getStatus(), -1);
+                } else {
+                    exchange.sendResponseHeaders(context.getStatus(),
+                            getResponseLength(contentLength));
+                }
+            } catch (final IOException ioe) {
+                throw new ContainerException(LocalizationMessages.ERROR_RESPONSEWRITER_WRITING_HEADERS(), ioe);
+            }
+
+            return exchange.getResponseBody();
+        }
+
+        private long getResponseLength(final long contentLength) {
+            if (contentLength == 0) {
+                return -1;
+            }
+            if (contentLength < 0) {
+                return 0;
+            }
+            return contentLength;
+        }
+
+        @Override
+        public boolean suspend(final long timeOut, final TimeUnit timeUnit, final TimeoutHandler timeoutHandler) {
+            throw new UnsupportedOperationException("Method suspend is not supported by the container.");
+        }
+
+        @Override
+        public void setSuspendTimeout(final long timeOut, final TimeUnit timeUnit) throws IllegalStateException {
+            throw new UnsupportedOperationException("Method setSuspendTimeout is not supported by the container.");
+        }
+
+        @Override
+        public void failure(final Throwable error) {
+            try {
+                exchange.sendResponseHeaders(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), getResponseLength(0));
+            } catch (final IOException e) {
+                LOGGER.log(Level.WARNING, LocalizationMessages.ERROR_RESPONSEWRITER_SENDING_FAILURE_RESPONSE(), e);
+            } finally {
+                commit();
+                rethrow(error);
+            }
+        }
+
+        @Override
+        public boolean enableResponseBuffering() {
+            return true;
+        }
+
+        @Override
+        public void commit() {
+            if (closed.compareAndSet(false, true)) {
+                exchange.close();
+            }
+        }
+
+        /**
+         * Rethrow the original exception as required by JAX-RS, 3.3.4
+         *
+         * @param error throwable to be re-thrown
+         */
+        private void rethrow(final Throwable error) {
+            if (error instanceof RuntimeException) {
+                throw (RuntimeException) error;
+            } else {
+                throw new ContainerException(error);
+            }
+        }
+
+        /**
+         * Commits the response and logs a warning message.
+         *
+         * This method should be called by the container at the end of the
+         * handle method to make sure that the ResponseWriter was committed.
+         */
+        private void closeAndLogWarning() {
+            if (closed.compareAndSet(false, true)) {
+                exchange.close();
+                LOGGER.log(Level.WARNING, LocalizationMessages.ERROR_RESPONSEWRITER_RESPONSE_UNCOMMITED());
+            }
+        }
+    }
+}
diff --git a/containers/jdk-http/src/main/java/org/glassfish/jersey/jdkhttp/JdkHttpHandlerContainerProvider.java b/containers/jdk-http/src/main/java/org/glassfish/jersey/jdkhttp/JdkHttpHandlerContainerProvider.java
new file mode 100644
index 0000000..cd6306d
--- /dev/null
+++ b/containers/jdk-http/src/main/java/org/glassfish/jersey/jdkhttp/JdkHttpHandlerContainerProvider.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jdkhttp;
+
+import javax.ws.rs.ProcessingException;
+import javax.ws.rs.core.Application;
+
+import org.glassfish.jersey.server.spi.ContainerProvider;
+
+import com.sun.net.httpserver.HttpHandler;
+
+/**
+ * Container provider for containers based on lightweight Java SE HTTP Server's {@link HttpHandler}.
+ *
+ * @author Miroslav Fuksa
+ */
+public final class JdkHttpHandlerContainerProvider implements ContainerProvider {
+
+    @Override
+    public <T> T createContainer(Class<T> type, Application application) throws ProcessingException {
+        if (type != HttpHandler.class && type != JdkHttpHandlerContainer.class) {
+            return null;
+        }
+        return type.cast(new JdkHttpHandlerContainer(application));
+    }
+}
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
new file mode 100644
index 0000000..0f1fbb2
--- /dev/null
+++ b/containers/jdk-http/src/main/java/org/glassfish/jersey/jdkhttp/JdkHttpServerFactory.java
@@ -0,0 +1,386 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jdkhttp;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.logging.Logger;
+
+import javax.ws.rs.ProcessingException;
+
+import javax.net.ssl.SSLContext;
+
+import org.glassfish.jersey.internal.guava.ThreadFactoryBuilder;
+import org.glassfish.jersey.jdkhttp.internal.LocalizationMessages;
+import org.glassfish.jersey.process.JerseyProcessingUncaughtExceptionHandler;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.spi.Container;
+
+import com.sun.net.httpserver.HttpContext;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpServer;
+import com.sun.net.httpserver.HttpsConfigurator;
+import com.sun.net.httpserver.HttpsServer;
+
+/**
+ * Factory for creating {@link HttpServer JDK HttpServer} instances to run Jersey applications.
+ *
+ * @author Miroslav Fuksa
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public final class JdkHttpServerFactory {
+
+    private static final Logger LOG = Logger.getLogger(JdkHttpServerFactory.class.getName());
+
+    /**
+     * Create and start the {@link HttpServer JDK HttpServer} with the Jersey application deployed
+     * at the given {@link URI}.
+     * <p>
+     * The returned {@link HttpServer JDK HttpServer} is started.
+     * </p>
+     *
+     * @param uri           the {@link URI uri} on which the Jersey application will be deployed.
+     * @param configuration the Jersey server-side application configuration.
+     * @return Newly created {@link HttpServer}.
+     * @throws ProcessingException thrown when problems during server creation
+     *                             occurs.
+     */
+    public static HttpServer createHttpServer(final URI uri, final ResourceConfig configuration) {
+        return createHttpServer(uri, configuration, true);
+    }
+
+    /**
+     * Create (and possibly start) the {@link HttpServer JDK HttpServer} with the JAX-RS / Jersey application deployed
+     * on the given {@link URI}.
+     * <p>
+     * The {@code start} flag controls whether or not the returned {@link HttpServer JDK HttpServer} is started.
+     * </p>
+     *
+     * @param uri           the {@link URI uri} on which the Jersey application will be deployed.
+     * @param configuration the Jersey server-side application configuration.
+     * @param start         if set to {@code false}, the created server will not be automatically started.
+     * @return Newly created {@link HttpServer}.
+     * @throws ProcessingException thrown when problems during server creation occurs.
+     * @since 2.8
+     */
+    public static HttpServer createHttpServer(final URI uri, final ResourceConfig configuration, final boolean start) {
+        return createHttpServer(uri, new JdkHttpHandlerContainer(configuration), start);
+    }
+
+    /**
+     * Create (and possibly start) the {@link HttpServer JDK HttpServer} with the JAX-RS / Jersey application deployed
+     * on the given {@link URI}.
+     * <p/>
+     *
+     * @param uri           the {@link URI uri} on which the Jersey application will be deployed.
+     * @param configuration the Jersey server-side application configuration.
+     * @param parentContext DI provider specific context with application's registered bindings.
+     * @return Newly created {@link HttpServer}.
+     * @throws ProcessingException thrown when problems during server creation occurs.
+     * @see org.glassfish.jersey.jdkhttp.JdkHttpHandlerContainer
+     * @since 2.12
+     */
+    public static HttpServer createHttpServer(final URI uri, final ResourceConfig configuration,
+                                              final Object parentContext) {
+        return createHttpServer(uri, new JdkHttpHandlerContainer(configuration, parentContext), true);
+    }
+
+    /**
+     * Create and start the {@link HttpServer JDK HttpServer}, eventually {@code HttpServer}'s subclass
+     * {@link HttpsServer JDK HttpsServer} with the JAX-RS / Jersey application deployed on the given {@link URI}.
+     * <p>
+     * The returned {@link HttpServer JDK HttpServer} is started.
+     * </p>
+     *
+     * @param uri           the {@link URI uri} on which the Jersey application will be deployed.
+     * @param configuration the Jersey server-side application configuration.
+     * @param sslContext    custom {@link SSLContext} to be passed to the server
+     * @return Newly created {@link HttpServer}.
+     * @throws ProcessingException thrown when problems during server creation occurs.
+     * @since 2.18
+     */
+    public static HttpServer createHttpServer(final URI uri, final ResourceConfig configuration,
+                                              final SSLContext sslContext) {
+        return createHttpServer(uri, new JdkHttpHandlerContainer(configuration),
+                sslContext, true);
+    }
+
+    /**
+     * Create (and possibly start) the {@link HttpServer JDK HttpServer}, eventually {@code HttpServer}'s subclass
+     * {@link HttpsServer JDK HttpsServer} with the JAX-RS / Jersey application deployed on the given {@link URI}.
+     * <p>
+     * The {@code start} flag controls whether or not the returned {@link HttpServer JDK HttpServer} is started.
+     * </p>
+     *
+     * @param uri           the {@link URI uri} on which the Jersey application will be deployed.
+     * @param configuration the Jersey server-side application configuration.
+     * @param sslContext    custom {@link SSLContext} to be passed to the server
+     * @param start         if set to {@code false}, the created server will not be automatically started.
+     * @return Newly created {@link HttpServer}.
+     * @throws ProcessingException thrown when problems during server creation occurs.
+     * @since 2.17
+     */
+    public static HttpServer createHttpServer(final URI uri, final ResourceConfig configuration,
+                                              final SSLContext sslContext, final boolean start) {
+        return createHttpServer(uri,
+                new JdkHttpHandlerContainer(configuration),
+                sslContext,
+                start);
+    }
+
+    /**
+     * Create (and possibly start) the {@link HttpServer JDK HttpServer}, eventually {@code HttpServer}'s subclass
+     * {@link HttpsServer} with the JAX-RS / Jersey application deployed on the given {@link URI}.
+     * <p>
+     * The {@code start} flag controls whether or not the returned {@link HttpServer JDK HttpServer} is started.
+     * </p>
+     *
+     * @param uri           the {@link URI uri} on which the Jersey application will be deployed.
+     * @param configuration the Jersey server-side application configuration.
+     * @param parentContext DI provider specific context with application's registered bindings.
+     * @param sslContext    custom {@link SSLContext} to be passed to the server
+     * @param start         if set to {@code false}, the created server will not be automatically started.
+     * @return Newly created {@link HttpServer}.
+     * @throws ProcessingException thrown when problems during server creation occurs.
+     * @since 2.18
+     */
+    public static HttpServer createHttpServer(final URI uri, final ResourceConfig configuration,
+                                              final Object parentContext,
+                                              final SSLContext sslContext, final boolean start) {
+        return createHttpServer(uri,
+                new JdkHttpHandlerContainer(configuration, parentContext),
+                sslContext,
+                start
+        );
+    }
+
+    private static HttpServer createHttpServer(final URI uri, final JdkHttpHandlerContainer handler,
+                                               final boolean start) {
+        return createHttpServer(uri, handler, null, start);
+    }
+
+    private static HttpServer createHttpServer(final URI uri,
+                                               final JdkHttpHandlerContainer handler,
+                                               final SSLContext sslContext,
+                                               final boolean start) {
+        if (uri == null) {
+            throw new IllegalArgumentException(LocalizationMessages.ERROR_CONTAINER_URI_NULL());
+        }
+
+        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;
+
+        if (isHttp) {
+            if (httpsConfigurator != null) {
+                // attempt to use https with http scheme
+                LOG.warning(LocalizationMessages.WARNING_CONTAINER_URI_SCHEME_SECURED());
+            }
+        } else if (isHttps) {
+            if (httpsConfigurator == null) {
+                if (start) {
+                    // The SSLContext (via HttpsConfigurator) has to be set before the server starts.
+                    // Starting https server w/o SSL is invalid, it will lead to error anyway.
+                    throw new IllegalArgumentException(LocalizationMessages.ERROR_CONTAINER_HTTPS_NO_SSL());
+                } else {
+                    // Creating the https server w/o SSL context, but not starting it is valid.
+                    // However, server.setHttpsConfigurator() must be called before the start.
+                    LOG.info(LocalizationMessages.INFO_CONTAINER_HTTPS_NO_SSL());
+                }
+            }
+        } else {
+            throw new IllegalArgumentException(LocalizationMessages.ERROR_CONTAINER_URI_SCHEME_UNKNOWN(uri));
+        }
+
+        final String path = uri.getPath();
+        if (path == null) {
+            throw new IllegalArgumentException(LocalizationMessages.ERROR_CONTAINER_URI_PATH_NULL(uri));
+        } else if (path.isEmpty()) {
+            throw new IllegalArgumentException(LocalizationMessages.ERROR_CONTAINER_URI_PATH_EMPTY(uri));
+        } else if (path.charAt(0) != '/') {
+            throw new IllegalArgumentException(LocalizationMessages.ERROR_CONTAINER_URI_PATH_START(uri));
+        }
+
+        final int port = (uri.getPort() == -1)
+                ? (isHttp ? Container.DEFAULT_HTTP_PORT : Container.DEFAULT_HTTPS_PORT)
+                : uri.getPort();
+
+        final HttpServer server;
+        try {
+            server = isHttp
+                    ? HttpServer.create(new InetSocketAddress(port), 0)
+                    : HttpsServer.create(new InetSocketAddress(port), 0);
+        } catch (final IOException ioe) {
+            throw new ProcessingException(LocalizationMessages.ERROR_CONTAINER_EXCEPTION_IO(), ioe);
+        }
+
+        if (isHttps && httpsConfigurator != null) {
+            ((HttpsServer) server).setHttpsConfigurator(httpsConfigurator);
+        }
+
+        server.setExecutor(Executors.newCachedThreadPool(new ThreadFactoryBuilder()
+                .setNameFormat("jdk-http-server-%d")
+                .setUncaughtExceptionHandler(new JerseyProcessingUncaughtExceptionHandler())
+                .build()));
+        server.createContext(path, handler);
+
+        final HttpServer wrapper = isHttp
+                ? createHttpServerWrapper(server, handler)
+                : createHttpsServerWrapper((HttpsServer) server, handler);
+
+        if (start) {
+            wrapper.start();
+        }
+
+        return wrapper;
+    }
+
+    private static HttpServer createHttpsServerWrapper(final HttpsServer delegate, final JdkHttpHandlerContainer handler) {
+        return new HttpsServer() {
+
+            @Override
+            public void setHttpsConfigurator(final HttpsConfigurator httpsConfigurator) {
+                delegate.setHttpsConfigurator(httpsConfigurator);
+            }
+
+            @Override
+            public HttpsConfigurator getHttpsConfigurator() {
+                return delegate.getHttpsConfigurator();
+            }
+
+            @Override
+            public void bind(final InetSocketAddress inetSocketAddress, final int i) throws IOException {
+                delegate.bind(inetSocketAddress, i);
+            }
+
+            @Override
+            public void start() {
+                delegate.start();
+                handler.onServerStart();
+            }
+
+            @Override
+            public void setExecutor(final Executor executor) {
+                delegate.setExecutor(executor);
+            }
+
+            @Override
+            public Executor getExecutor() {
+                return delegate.getExecutor();
+            }
+
+            @Override
+            public void stop(final int i) {
+                handler.onServerStop();
+                delegate.stop(i);
+            }
+
+            @Override
+            public HttpContext createContext(final String s, final HttpHandler httpHandler) {
+                return delegate.createContext(s, httpHandler);
+            }
+
+            @Override
+            public HttpContext createContext(final String s) {
+                return delegate.createContext(s);
+            }
+
+            @Override
+            public void removeContext(final String s) throws IllegalArgumentException {
+                delegate.removeContext(s);
+            }
+
+            @Override
+            public void removeContext(final HttpContext httpContext) {
+                delegate.removeContext(httpContext);
+            }
+
+            @Override
+            public InetSocketAddress getAddress() {
+                return delegate.getAddress();
+            }
+        };
+    }
+
+    private static HttpServer createHttpServerWrapper(final HttpServer delegate, final JdkHttpHandlerContainer handler) {
+        return new HttpServer() {
+
+            @Override
+            public void bind(final InetSocketAddress inetSocketAddress, final int i) throws IOException {
+                delegate.bind(inetSocketAddress, i);
+            }
+
+            @Override
+            public void start() {
+                delegate.start();
+                handler.onServerStart();
+            }
+
+            @Override
+            public void setExecutor(final Executor executor) {
+                delegate.setExecutor(executor);
+            }
+
+            @Override
+            public Executor getExecutor() {
+                return delegate.getExecutor();
+            }
+
+            @Override
+            public void stop(final int i) {
+                handler.onServerStop();
+                delegate.stop(i);
+            }
+
+            @Override
+            public HttpContext createContext(final String s, final HttpHandler httpHandler) {
+                return delegate.createContext(s, httpHandler);
+            }
+
+            @Override
+            public HttpContext createContext(final String s) {
+                return delegate.createContext(s);
+            }
+
+            @Override
+            public void removeContext(final String s) throws IllegalArgumentException {
+                delegate.removeContext(s);
+            }
+
+            @Override
+            public void removeContext(final HttpContext httpContext) {
+                delegate.removeContext(httpContext);
+            }
+
+            @Override
+            public InetSocketAddress getAddress() {
+                return delegate.getAddress();
+            }
+        };
+    }
+
+    /**
+     * Prevents instantiation.
+     */
+    private JdkHttpServerFactory() {
+        throw new AssertionError("Instantiation not allowed.");
+    }
+}
diff --git a/containers/jdk-http/src/main/java/org/glassfish/jersey/jdkhttp/package-info.java b/containers/jdk-http/src/main/java/org/glassfish/jersey/jdkhttp/package-info.java
new file mode 100644
index 0000000..c60ec6e
--- /dev/null
+++ b/containers/jdk-http/src/main/java/org/glassfish/jersey/jdkhttp/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+/**
+ * The container adapter between {@link com.sun.net.httpserver.HttpServer JDK HTTP server}
+ * and Jersey {@link org.glassfish.jersey.server.ApplicationHandler Jersey application handler}
+ * classes.
+ */
+package org.glassfish.jersey.jdkhttp;
diff --git a/containers/jdk-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ContainerProvider b/containers/jdk-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ContainerProvider
new file mode 100644
index 0000000..efdf94e
--- /dev/null
+++ b/containers/jdk-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ContainerProvider
@@ -0,0 +1 @@
+org.glassfish.jersey.jdkhttp.JdkHttpHandlerContainerProvider
\ No newline at end of file
diff --git a/containers/jdk-http/src/main/resources/org/glassfish/jersey/jdkhttp/internal/localization.properties b/containers/jdk-http/src/main/resources/org/glassfish/jersey/jdkhttp/internal/localization.properties
new file mode 100644
index 0000000..745c494
--- /dev/null
+++ b/containers/jdk-http/src/main/resources/org/glassfish/jersey/jdkhttp/internal/localization.properties
@@ -0,0 +1,30 @@
+#
+# Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Eclipse Public License v. 2.0, which is available at
+# http://www.eclipse.org/legal/epl-2.0.
+#
+# This Source Code may also be made available under the following Secondary
+# Licenses when the conditions for such availability set forth in the
+# Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+# version 2 with the GNU Classpath Exception, which is available at
+# https://www.gnu.org/software/classpath/license.html.
+#
+# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+#
+
+error.container.exception.io=IOException thrown when creating the JDK HttpServer.
+error.container.uri.null=The URI must not be null.
+error.container.uri.path.empty=The URI path, of the URI {0} must be present (not an empty string).
+error.container.uri.path.null=The URI path, of the URI {0} must be non-null.
+error.container.uri.path.start=The URI path, of the URI {0} must start with a '/'.
+error.container.uri.scheme.unknown=The URI scheme, of the URI {0} must be equal (ignoring case) to 'http' or 'https'.
+error.container.https.no.ssl=Attempt to start a HTTPS server with no SSL context defined.
+error.responsewriter.response.uncommited=ResponseWriter was not commited yet. Committing the Response now.
+error.responsewriter.sending.failure.response=Unable to send a failure response.
+error.responsewriter.writing.headers=Error writing out the response headers.
+info.container.https.no.ssl=HTTPS server will be created with no SSL context defined. HttpsConfigurator must be \
+  set before the server is started.
+warning.container.uri.scheme.secured=SSLContext is set, but http scheme was used instead of https. The SSLContext will \
+  be ignored.
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
new file mode 100644
index 0000000..24998f9
--- /dev/null
+++ b/containers/jdk-http/src/test/java/org/glassfish/jersey/jdkhttp/AbstractJdkHttpServerTester.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jdkhttp;
+
+import java.net.URI;
+import java.security.AccessController;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.core.UriBuilder;
+
+import com.sun.net.httpserver.HttpServer;
+import org.glassfish.jersey.internal.util.PropertiesHelper;
+import org.glassfish.jersey.logging.LoggingFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.junit.After;
+
+/**
+ * Abstract JDK HTTP Server unit tester.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public abstract class AbstractJdkHttpServerTester {
+
+    public static final String CONTEXT = "";
+    private final int DEFAULT_PORT = 9998;
+
+    private static final Logger LOGGER = Logger.getLogger(AbstractJdkHttpServerTester.class.getName());
+
+    /**
+     * Get the port to be used for test application deployments.
+     *
+     * @return The HTTP port of the URI
+     */
+    protected final int getPort() {
+        final String value =
+                AccessController.doPrivileged(PropertiesHelper.getSystemProperty("jersey.config.test.container.port"));
+        if (value != null) {
+
+            try {
+                final int i = Integer.parseInt(value);
+                if (i <= 0) {
+                    throw new NumberFormatException("Value not positive.");
+                }
+                return i;
+            } catch (NumberFormatException e) {
+                LOGGER.log(Level.CONFIG,
+                        "Value of 'jersey.config.test.container.port'"
+                                + " property is not a valid positive integer [" + value + "]."
+                                + " Reverting to default [" + DEFAULT_PORT + "].",
+                        e);
+            }
+        }
+        return DEFAULT_PORT;
+    }
+
+    private volatile HttpServer server;
+
+    public UriBuilder getUri() {
+        return UriBuilder.fromUri("http://localhost").port(getPort()).path(CONTEXT);
+    }
+
+    public void startServer(Class... resources) {
+        ResourceConfig config = new ResourceConfig(resources);
+        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);
+    }
+
+    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);
+    }
+
+    public URI getBaseUri() {
+        return UriBuilder.fromUri("http://localhost/").port(getPort()).build();
+    }
+
+    public void stopServer() {
+        try {
+            server.stop(3);
+            server = null;
+            LOGGER.log(Level.INFO, "Simple-http server stopped.");
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @After
+    public void tearDown() {
+        if (server != null) {
+            stopServer();
+        }
+    }
+}
diff --git a/containers/jdk-http/src/test/java/org/glassfish/jersey/jdkhttp/BasicJdkHttpServerTest.java b/containers/jdk-http/src/test/java/org/glassfish/jersey/jdkhttp/BasicJdkHttpServerTest.java
new file mode 100644
index 0000000..62a344d
--- /dev/null
+++ b/containers/jdk-http/src/test/java/org/glassfish/jersey/jdkhttp/BasicJdkHttpServerTest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jdkhttp;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.UriBuilder;
+
+import org.glassfish.jersey.server.ResourceConfig;
+
+import org.junit.After;
+import org.junit.Test;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.sun.net.httpserver.HttpServer;
+import com.sun.net.httpserver.HttpsServer;
+
+/**
+ * Jdk Http Server basic tests.
+ *
+ * @author Michal Gajdos
+ */
+public class BasicJdkHttpServerTest extends AbstractJdkHttpServerTester {
+
+    private HttpServer server;
+
+    @Path("/test")
+    public static class TestResource {
+
+        @GET
+        public String get() {
+            return "test";
+        }
+    }
+
+    @Test
+    public void testCreateHttpServer() throws Exception {
+        server = JdkHttpServerFactory.createHttpServer(
+                UriBuilder.fromUri("http://localhost/").port(getPort()).build(), new ResourceConfig(TestResource.class));
+
+        assertThat(server, instanceOf(HttpServer.class));
+        assertThat(server, not(instanceOf(HttpsServer.class)));
+    }
+
+    @Test
+    public void testCreateHttpsServer() throws Exception {
+        server = JdkHttpServerFactory.createHttpServer(
+                UriBuilder.fromUri("https://localhost/").port(getPort()).build(),
+                new ResourceConfig(TestResource.class),
+                false);
+
+        assertThat(server, instanceOf(HttpsServer.class));
+    }
+
+    @After
+    public void tearDown() {
+        if (server != null) {
+            server.stop(3);
+            server = null;
+        }
+    }
+}
diff --git a/containers/jdk-http/src/test/java/org/glassfish/jersey/jdkhttp/JdkHttpPackageTest.java b/containers/jdk-http/src/test/java/org/glassfish/jersey/jdkhttp/JdkHttpPackageTest.java
new file mode 100644
index 0000000..675bbaa
--- /dev/null
+++ b/containers/jdk-http/src/test/java/org/glassfish/jersey/jdkhttp/JdkHttpPackageTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jdkhttp;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.server.ResourceConfig;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Jdk Http Container package scanning test.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class JdkHttpPackageTest extends AbstractJdkHttpServerTester {
+
+    @Path("/packageTest")
+    public static class TestResource {
+        @GET
+        public String get() {
+            return "test";
+        }
+
+        @GET
+        @Path("sub")
+        public String getSub() {
+            return "test-sub";
+        }
+    }
+
+    @Test
+    public void testJdkHttpPackage() {
+        final ResourceConfig rc = new ResourceConfig();
+        rc.packages(this.getClass().getPackage().getName());
+
+        startServer(rc);
+
+        WebTarget r = ClientBuilder.newClient().target(getUri().path("/").build());
+
+        assertEquals("test", r.path("packageTest").request().get(String.class));
+        assertEquals("test-sub", r.path("packageTest/sub").request().get(String.class));
+        assertEquals(404, r.path("wrong").request().get(Response.class).getStatus());
+
+    }
+}
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
new file mode 100644
index 0000000..180f2bc
--- /dev/null
+++ b/containers/jdk-http/src/test/java/org/glassfish/jersey/jdkhttp/JdkHttpsServerTest.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jdkhttp;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.ProcessingException;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.core.UriBuilder;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLHandshakeException;
+
+import org.glassfish.jersey.SslConfigurator;
+import org.glassfish.jersey.server.ResourceConfig;
+
+import org.junit.After;
+import org.junit.Test;
+
+import com.google.common.io.ByteStreams;
+
+import com.sun.net.httpserver.HttpServer;
+import com.sun.net.httpserver.HttpsConfigurator;
+import com.sun.net.httpserver.HttpsServer;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Jdk Https Server tests.
+ *
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ */
+public class JdkHttpsServerTest extends AbstractJdkHttpServerTester {
+
+    private static final String TRUSTSTORE_CLIENT_FILE = "./truststore_client";
+    private static final String TRUSTSTORE_CLIENT_PWD = "asdfgh";
+    private static final String KEYSTORE_CLIENT_FILE = "./keystore_client";
+    private static final String KEYSTORE_CLIENT_PWD = "asdfgh";
+
+    private static final String KEYSTORE_SERVER_FILE = "./keystore_server";
+    private static final String KEYSTORE_SERVER_PWD = "asdfgh";
+    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")
+    public static class TestResource {
+        @GET
+        public String get() {
+            return "test";
+        }
+    }
+
+    /**
+     * Test, that {@link HttpsServer} instance is returned when providing empty SSLContext (but not starting).
+     * @throws Exception
+     */
+    @Test
+    public void testCreateHttpsServerNoSslContext() throws Exception {
+        server = JdkHttpServerFactory.createHttpServer(httpsUri, rc, null, false);
+        assertThat(server, instanceOf(HttpsServer.class));
+    }
+
+    /**
+     * Test, that exception is thrown when attempting to start a {@link HttpsServer} with empty SSLContext.
+     * @throws Exception
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testStartHttpServerNoSslContext() throws Exception {
+        server = JdkHttpServerFactory.createHttpServer(httpsUri, rc, null, true);
+    }
+
+    /**
+     * Test, that {@link javax.net.ssl.SSLHandshakeException} is thrown when attepmting to connect to server with client
+     * not configured correctly.
+     * @throws Exception
+     */
+    @Test(expected = SSLHandshakeException.class)
+    public void testCreateHttpsServerDefaultSslContext() throws Throwable {
+        server = JdkHttpServerFactory.createHttpServer(httpsUri, 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);
+        } catch (final ProcessingException e) {
+            throw e.getCause();
+        }
+    }
+
+    /**
+     * Test, that {@link HttpsServer} can be manually started even with (empty) SSLContext, but will throw an exception
+     * on request.
+     * @throws Exception
+     */
+    @Test(expected = IOException.class)
+    public void testHttpsServerNoSslContextDelayedStart() throws Throwable {
+        server = JdkHttpServerFactory.createHttpServer(httpsUri, 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);
+        } catch (final ProcessingException e) {
+            throw e.getCause();
+        }
+    }
+
+    /**
+     * Test, that {@link HttpsServer} cannot be configured with {@link HttpsConfigurator} after it has started.
+     * @throws Exception
+     */
+    @Test(expected = IllegalStateException.class)
+    public void testConfigureSslContextAfterStart() throws Throwable {
+        server = JdkHttpServerFactory.createHttpServer(httpsUri, rc, null, false);
+        assertThat(server, instanceOf(HttpsServer.class));
+        server.start();
+        ((HttpsServer) server).setHttpsConfigurator(new HttpsConfigurator(getServerSslContext()));
+    }
+
+    /**
+     * Tests a client to server roundtrip with correctly configured SSL on both sides.
+     * @throws IOException
+     */
+    @Test
+    public void testCreateHttpsServerRoundTrip() throws IOException {
+        final SSLContext serverSslContext = getServerSslContext();
+
+        server = JdkHttpServerFactory.createHttpServer(httpsUri, 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);
+
+        assertEquals("test", response);
+    }
+
+    /**
+     * Test, that if URI uses http scheme instead of https, SSLContext is ignored.
+     * @throws IOException
+     */
+    @Test
+    public void testHttpWithSsl() throws IOException {
+        server = JdkHttpServerFactory.createHttpServer(httpUri, rc, getServerSslContext(), true);
+        assertThat(server, instanceOf(HttpServer.class));
+        assertThat(server, not(instanceOf(HttpsServer.class)));
+    }
+
+    private SSLContext getClientSslContext() throws IOException {
+        final InputStream trustStore = JdkHttpsServerTest.class.getResourceAsStream(TRUSTSTORE_CLIENT_FILE);
+        final InputStream keyStore = JdkHttpsServerTest.class.getResourceAsStream(KEYSTORE_CLIENT_FILE);
+
+
+        final SslConfigurator sslConfigClient = SslConfigurator.newInstance()
+                .trustStoreBytes(ByteStreams.toByteArray(trustStore))
+                .trustStorePassword(TRUSTSTORE_CLIENT_PWD)
+                .keyStoreBytes(ByteStreams.toByteArray(keyStore))
+                .keyPassword(KEYSTORE_CLIENT_PWD);
+
+        return sslConfigClient.createSSLContext();
+    }
+
+    private SSLContext getServerSslContext() throws IOException {
+        final InputStream trustStore = JdkHttpsServerTest.class.getResourceAsStream(TRUSTSTORE_SERVER_FILE);
+        final InputStream keyStore = JdkHttpsServerTest.class.getResourceAsStream(KEYSTORE_SERVER_FILE);
+
+        final SslConfigurator sslConfigServer = SslConfigurator.newInstance()
+                .keyStoreBytes(ByteStreams.toByteArray(keyStore))
+                .keyPassword(KEYSTORE_SERVER_PWD)
+                .trustStoreBytes(ByteStreams.toByteArray(trustStore))
+                .trustStorePassword(TRUSTSTORE_SERVER_PWD);
+
+        return sslConfigServer.createSSLContext();
+    }
+
+    @After
+    public void tearDown() {
+        if (server != null) {
+            server.stop(0);
+            server = null;
+        }
+    }
+}
diff --git a/containers/jdk-http/src/test/java/org/glassfish/jersey/jdkhttp/LifecycleListenerTest.java b/containers/jdk-http/src/test/java/org/glassfish/jersey/jdkhttp/LifecycleListenerTest.java
new file mode 100644
index 0000000..229058f
--- /dev/null
+++ b/containers/jdk-http/src/test/java/org/glassfish/jersey/jdkhttp/LifecycleListenerTest.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jdkhttp;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.spi.AbstractContainerLifecycleListener;
+import org.glassfish.jersey.server.spi.Container;
+
+import org.junit.Test;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Reload and ContainerLifecycleListener support test.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class LifecycleListenerTest extends AbstractJdkHttpServerTester {
+
+    @Path("/one")
+    public static class One {
+        @GET
+        public String get() {
+            return "one";
+        }
+
+        @GET
+        @Path("sub")
+        public String getSub() {
+            return "one-sub";
+        }
+    }
+
+    @Path("/two")
+    public static class Two {
+        @GET
+        public String get() {
+            return "two";
+        }
+    }
+
+    public static class Reloader extends AbstractContainerLifecycleListener {
+        Container container;
+
+        public void reload(ResourceConfig newConfig) {
+            container.reload(newConfig);
+        }
+
+        public void reload() {
+            container.reload();
+        }
+
+        @Override
+        public void onStartup(Container container) {
+            this.container = container;
+        }
+    }
+
+    @Test
+    public void testReload() {
+        final ResourceConfig rc = new ResourceConfig(One.class);
+
+        Reloader reloader = new Reloader();
+        rc.registerInstances(reloader);
+
+        startServer(rc);
+
+        WebTarget r = ClientBuilder.newClient().target(getUri().path("/").build());
+
+        assertEquals("one", r.path("one").request().get(String.class));
+        assertEquals("one-sub", r.path("one/sub").request().get(String.class));
+        assertEquals(404, r.path("two").request().get(Response.class).getStatus());
+
+        // add Two resource
+        reloader.reload(new ResourceConfig(One.class, Two.class));
+
+        assertEquals("one", r.path("one").request().get(String.class));
+        assertEquals("one-sub", r.path("one/sub").request().get(String.class));
+        assertEquals("two", r.path("two").request().get(String.class));
+    }
+
+    static class StartStopListener extends AbstractContainerLifecycleListener {
+        volatile boolean started;
+        volatile boolean stopped;
+
+        @Override
+        public void onStartup(Container container) {
+            started = true;
+        }
+
+        @Override
+        public void onShutdown(Container container) {
+            stopped = true;
+        }
+    }
+
+    @Test
+    public void testStartupShutdownHooks() {
+        final StartStopListener listener = new StartStopListener();
+
+        startServer(new ResourceConfig(One.class).register(listener));
+
+        WebTarget r = ClientBuilder.newClient().target(getUri().path("/").build());
+
+        assertThat(r.path("one").request().get(String.class), equalTo("one"));
+        assertThat(r.path("two").request().get(Response.class).getStatus(), equalTo(404));
+
+        stopServer();
+
+        assertTrue("ContainerLifecycleListener.onStartup has not been called.", listener.started);
+        assertTrue("ContainerLifecycleListener.onShutdown has not been called.", listener.stopped);
+    }
+}
diff --git a/containers/jdk-http/src/test/java/org/glassfish/jersey/jdkhttp/RuntimeDelegateTest.java b/containers/jdk-http/src/test/java/org/glassfish/jersey/jdkhttp/RuntimeDelegateTest.java
new file mode 100644
index 0000000..26abd46
--- /dev/null
+++ b/containers/jdk-http/src/test/java/org/glassfish/jersey/jdkhttp/RuntimeDelegateTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jdkhttp;
+
+import java.net.InetSocketAddress;
+import java.util.Collections;
+import java.util.Set;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.ext.RuntimeDelegate;
+
+import org.junit.Test;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpServer;
+
+/**
+ * @author Michal Gajdos
+ */
+public class RuntimeDelegateTest {
+
+    @Path("/")
+    public static class Resource {
+
+        @GET
+        public String get() {
+            return "get";
+        }
+    }
+
+    @Test
+    public void testFetch() throws Exception {
+        final HttpServer server = HttpServer.create(new InetSocketAddress(0), 0);
+        final HttpHandler handler = RuntimeDelegate.getInstance().createEndpoint(new Application() {
+
+            @Override
+            public Set<Class<?>> getClasses() {
+                return Collections.<Class<?>>singleton(Resource.class);
+            }
+        }, HttpHandler.class);
+
+        try {
+            server.createContext("/", handler);
+            server.start();
+
+            final Response response = ClientBuilder.newClient()
+                    .target(UriBuilder.fromUri("http://localhost/").port(server.getAddress().getPort()).build())
+                    .request()
+                    .get();
+
+            assertThat(response.readEntity(String.class), is("get"));
+        } finally {
+            server.stop(0);
+        }
+    }
+}
diff --git a/containers/jdk-http/src/test/resources/org/glassfish/jersey/jdkhttp/keystore_client b/containers/jdk-http/src/test/resources/org/glassfish/jersey/jdkhttp/keystore_client
new file mode 100644
index 0000000..d016fd2
--- /dev/null
+++ b/containers/jdk-http/src/test/resources/org/glassfish/jersey/jdkhttp/keystore_client
Binary files differ
diff --git a/containers/jdk-http/src/test/resources/org/glassfish/jersey/jdkhttp/keystore_server b/containers/jdk-http/src/test/resources/org/glassfish/jersey/jdkhttp/keystore_server
new file mode 100644
index 0000000..a7c93fc
--- /dev/null
+++ b/containers/jdk-http/src/test/resources/org/glassfish/jersey/jdkhttp/keystore_server
Binary files differ
diff --git a/containers/jdk-http/src/test/resources/org/glassfish/jersey/jdkhttp/truststore_client b/containers/jdk-http/src/test/resources/org/glassfish/jersey/jdkhttp/truststore_client
new file mode 100644
index 0000000..74784fb
--- /dev/null
+++ b/containers/jdk-http/src/test/resources/org/glassfish/jersey/jdkhttp/truststore_client
Binary files differ
diff --git a/containers/jdk-http/src/test/resources/org/glassfish/jersey/jdkhttp/truststore_server b/containers/jdk-http/src/test/resources/org/glassfish/jersey/jdkhttp/truststore_server
new file mode 100644
index 0000000..9b26ce4
--- /dev/null
+++ b/containers/jdk-http/src/test/resources/org/glassfish/jersey/jdkhttp/truststore_server
Binary files differ
diff --git a/containers/jersey-servlet-core/pom.xml b/containers/jersey-servlet-core/pom.xml
new file mode 100644
index 0000000..4bc5700
--- /dev/null
+++ b/containers/jersey-servlet-core/pom.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.glassfish.jersey.containers</groupId>
+        <artifactId>project</artifactId>
+        <version>2.28-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>jersey-container-servlet-core</artifactId>
+    <packaging>jar</packaging>
+    <name>jersey-container-servlet-core</name>
+
+    <description>Jersey core Servlet 2.x implementation</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>servlet-api</artifactId>
+            <version>${servlet2.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>javax.persistence</groupId>
+            <artifactId>persistence-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.hk2.external</groupId>
+            <artifactId>javax.inject</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>com.sun.istack</groupId>
+                <artifactId>maven-istack-commons-plugin</artifactId>
+                <inherited>true</inherited>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>build-helper-maven-plugin</artifactId>
+                <inherited>true</inherited>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <inherited>true</inherited>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <!-- Note: When you're changing these properties change them also in bundles/jax-rs-ri/bundle/pom.xml. -->
+                        <Import-Package>
+                            javax.persistence.*;resolution:=optional,
+                            javax.servlet.*;version="[2.4,5.0)",
+                            javax.annotation.*;version=!,
+                            *
+                        </Import-Package>
+                        <Export-Package>org.glassfish.jersey.servlet.*</Export-Package>
+                    </instructions>
+                    <unpackBundle>true</unpackBundle>
+                </configuration>
+             </plugin>
+         </plugins>
+    </build>
+
+</project>
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/ServletContainer.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/ServletContainer.java
new file mode 100644
index 0000000..f54fc00
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/ServletContainer.java
@@ -0,0 +1,681 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.servlet;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.Iterator;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+import javax.ws.rs.ProcessingException;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriBuilderException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.glassfish.jersey.internal.inject.Providers;
+import org.glassfish.jersey.internal.util.ExtendedLogger;
+import org.glassfish.jersey.internal.util.collection.Value;
+import org.glassfish.jersey.server.ApplicationHandler;
+import org.glassfish.jersey.server.ContainerException;
+import org.glassfish.jersey.server.ContainerResponse;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.ServerProperties;
+import org.glassfish.jersey.server.internal.ContainerUtils;
+import org.glassfish.jersey.server.spi.Container;
+import org.glassfish.jersey.server.spi.ContainerLifecycleListener;
+import org.glassfish.jersey.servlet.internal.LocalizationMessages;
+import org.glassfish.jersey.servlet.internal.ResponseWriter;
+import org.glassfish.jersey.servlet.spi.FilterUrlMappingsProvider;
+import org.glassfish.jersey.uri.UriComponent;
+
+/**
+ * A {@link javax.servlet.Servlet} or {@link Filter} for deploying root resource classes.
+ * <p />
+ * The following sections make reference to initialization parameters. Unless
+ * otherwise specified the initialization parameters apply to both server
+ * and filter initialization parameters.
+ * <p />
+ * The servlet or filter may be configured to have an initialization
+ * parameter {@value ServletProperties#JAXRS_APPLICATION_CLASS}
+ * (see {@link org.glassfish.jersey.servlet.ServletProperties#JAXRS_APPLICATION_CLASS}) and whose value is a
+ * fully qualified name of a class that implements {@link javax.ws.rs.core.Application}.
+ * The class is instantiated as a singleton component
+ * managed by the runtime, and injection may be performed (the artifacts that
+ * may be injected are limited to injectable providers registered when
+ * the servlet or filter is configured).
+ * <p />
+ * If the initialization parameter {@value ServletProperties#JAXRS_APPLICATION_CLASS}
+ * is not present and a initialization parameter {@value org.glassfish.jersey.server.ServerProperties#PROVIDER_PACKAGES}
+ * is present (see {@link ServerProperties#PROVIDER_PACKAGES}) a new instance of
+ * {@link ResourceConfig} with this configuration is created. The initialization parameter
+ * {@value org.glassfish.jersey.server.ServerProperties#PROVIDER_PACKAGES} MUST be set to provide one or
+ * more package names. Each package name MUST be separated by ';'.
+ * <p />
+ * If none of the above resource configuration related initialization parameters
+ * are present a new instance of {@link ResourceConfig} with {@link WebAppResourcesScanner}
+ * is created. The initialization parameter {@value org.glassfish.jersey.server.ServerProperties#PROVIDER_CLASSPATH}
+ * is present (see {@link ServerProperties#PROVIDER_CLASSPATH}) MAY be
+ * set to provide one or more resource paths. Each path MUST be separated by ';'.
+ * If the initialization parameter is not present then the following resource
+ * paths are utilized: {@code "/WEB-INF/lib"} and {@code "/WEB-INF/classes"}.
+ * <p />
+ * All initialization parameters are added as properties of the created
+ * {@link ResourceConfig}.
+ * <p />
+ * A new {@link org.glassfish.jersey.server.ApplicationHandler} instance will be created and configured such
+ * that the following classes may be injected onto a root resource, provider
+ * and {@link javax.ws.rs.core.Application} classes using {@link javax.ws.rs.core.Context
+ * &#64;Context} annotation:
+ * {@link HttpServletRequest}, {@link HttpServletResponse},
+ * {@link ServletContext}, {@link javax.servlet.ServletConfig} and {@link WebConfig}.
+ * If this class is used as a Servlet then the {@link javax.servlet.ServletConfig} class may
+ * be injected. If this class is used as a servlet filter then the {@link FilterConfig}
+ * class may be injected. {@link WebConfig} may be injected to abstract
+ * servlet or filter deployment.
+ * <p />
+ * Persistence units that may be injected must be configured in web.xml
+ * in the normal way plus an additional servlet parameter to enable the
+ * Jersey servlet to locate them in JNDI. E.g. with the following
+ * persistence unit configuration:
+ * <pre>{@code
+ * <persistence-unit-ref>
+ *     <persistence-unit-ref-name>persistence/widget</persistence-unit-ref-name>
+ *     <persistence-unit-name>WidgetPU</persistence-unit-name>
+ * </persistence-unit-ref>
+ * }</pre>
+ * the Jersey servlet requires an additional servlet parameter as
+ * follows:
+ * <pre>{@code
+ * <init-param>
+ *     <param-name>unit:WidgetPU</param-name>
+ *     <param-value>persistence/widget</param-value>
+ * </init-param>
+ * }</pre>
+ * Given the above, Jersey will inject the {@link javax.persistence.EntityManagerFactory EntityManagerFactory} found
+ * at {@code java:comp/env/persistence/widget} in JNDI when encountering a
+ * field or parameter annotated with {@code @PersistenceUnit(unitName="WidgetPU")}.
+ *
+ * @author Paul Sandoz
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ * @author Michal Gajdos
+ * @author Libor Kramolis (libor.kramolis at oracle.com)
+ */
+public class ServletContainer extends HttpServlet implements Filter, Container {
+
+    private static final long serialVersionUID = 3932047066686065219L;
+    private static final ExtendedLogger LOGGER =
+            new ExtendedLogger(Logger.getLogger(ServletContainer.class.getName()), Level.FINEST);
+
+    private transient FilterConfig filterConfig;
+    private transient WebComponent webComponent;
+    private transient ResourceConfig resourceConfig;
+    private transient Pattern staticContentPattern;
+    private transient String filterContextPath;
+    private transient List<String> filterUrlMappings;
+
+    private transient volatile ContainerLifecycleListener containerListener;
+
+    /**
+     * Initiate the Web component.
+     *
+     * @param webConfig the Web configuration.
+     * @throws javax.servlet.ServletException in case of an initialization failure
+     */
+    protected void init(final WebConfig webConfig) throws ServletException {
+        webComponent = new WebComponent(webConfig, resourceConfig);
+        containerListener = webComponent.appHandler;
+        containerListener.onStartup(this);
+    }
+
+    /**
+     * Create Jersey Servlet container.
+     */
+    public ServletContainer() {
+    }
+
+    /**
+     * Create Jersey Servlet container.
+     *
+     * @param resourceConfig container configuration.
+     */
+    public ServletContainer(final ResourceConfig resourceConfig) {
+        this.resourceConfig = resourceConfig;
+    }
+
+    /**
+     * Dispatches client requests to the protected
+     * {@code service} method. There's no need to
+     * override this method.
+     *
+     * @param req the {@link HttpServletRequest} object that
+     *            contains the request the client made of
+     *            the servlet
+     * @param res the {@link HttpServletResponse} object that
+     *            contains the response the servlet returns
+     *            to the client
+     * @throws IOException      if an input or output error occurs
+     *                          while the servlet is handling the
+     *                          HTTP request
+     * @throws ServletException if the HTTP request cannot
+     *                          be handled
+     * @see javax.servlet.Servlet#service
+     */
+    @Override
+    public void service(final ServletRequest req, final ServletResponse res)
+            throws ServletException, IOException {
+        final HttpServletRequest request;
+        final HttpServletResponse response;
+
+        if (!(req instanceof HttpServletRequest && res instanceof HttpServletResponse)) {
+            throw new ServletException("non-HTTP request or response");
+        }
+
+        request = (HttpServletRequest) req;
+        response = (HttpServletResponse) res;
+
+        service(request, response);
+    }
+
+    /**
+     * Receives standard HTTP requests from the public {@code service} method and dispatches
+     * them to the {@code do}<i>XXX</i> methods defined in
+     * this class. This method is an HTTP-specific version of the
+     * {@link javax.servlet.Servlet#service} method. There's no
+     * need to override this method.
+     *
+     * @param request  the {@link HttpServletRequest} object that
+     *                 contains the request the client made of
+     *                 the servlet
+     * @param response the {@link HttpServletResponse} object that
+     *                 contains the response the servlet returns
+     *                 to the client
+     * @throws IOException      if an input or output error occurs
+     *                          while the servlet is handling the
+     *                          HTTP request
+     * @throws ServletException if the HTTP request
+     *                          cannot be handled
+     * @see javax.servlet.Servlet#service
+     */
+    @Override
+    protected void service(final HttpServletRequest request, final HttpServletResponse response)
+            throws ServletException, IOException {
+        /**
+         * There is an annoying edge case where the service method is
+         * invoked for the case when the URI is equal to the deployment URL
+         * minus the '/', for example http://locahost:8080/HelloWorldWebApp
+         */
+        final String servletPath = request.getServletPath();
+        final StringBuffer requestUrl = request.getRequestURL();
+        final String requestURI = request.getRequestURI();
+
+        //        final String pathInfo = request.getPathInfo();
+        //        final boolean checkPathInfo = pathInfo == null || pathInfo.isEmpty() || pathInfo.equals("/");
+        //        if (checkPathInfo && !request.getRequestURI().endsWith("/")) {
+        // Only do this if the last segment of the servlet path does not contain '.'
+        // This handles the case when the extension mapping is used with the servlet
+        // see issue 506
+        // This solution does not require parsing the deployment descriptor,
+        // however still leaves it broken for the very rare case if a standard path
+        // servlet mapping would include dot in the last segment (e.g. /.webresources/*)
+        // and somebody would want to hit the root resource without the trailing slash
+        //            final int i = servletPath.lastIndexOf('/');
+        //            if (servletPath.substring(i + 1).indexOf('.') < 0) {
+        // TODO (+ handle request URL with invalid characters - see the creation of absoluteUriBuilder below)
+        //                if (webComponent.getResourceConfig().getFeature(ResourceConfig.FEATURE_REDIRECT)) {
+        //                    URI l = UriBuilder.fromUri(request.getRequestURL().toString()).
+        //                            path("/").
+        //                            replaceQuery(request.getQueryString()).build();
+        //
+        //                    response.setStatus(307);
+        //                    response.setHeader("Location", l.toASCIIString());
+        //                    return;
+        //                } else {
+        //                pathInfo = "/";
+        //                requestURL.append("/");
+        //                requestURI += "/";
+        //                }
+        //            }
+        //        }
+
+        /**
+         * The HttpServletRequest.getRequestURL() contains the complete URI
+         * minus the query and fragment components.
+         */
+        final UriBuilder absoluteUriBuilder;
+        try {
+            absoluteUriBuilder = UriBuilder.fromUri(requestUrl.toString());
+        } catch (final IllegalArgumentException iae) {
+            setResponseForInvalidUri(response, iae);
+            return;
+        }
+
+        /**
+         * The HttpServletRequest.getPathInfo() and
+         * HttpServletRequest.getServletPath() are in decoded form.
+         *
+         * On some servlet implementations the getPathInfo() removed
+         * contiguous '/' characters. This is problematic if URIs
+         * are embedded, for example as the last path segment.
+         * We need to work around this and not use getPathInfo
+         * for the decodedPath.
+         */
+        final String decodedBasePath = request.getContextPath() + servletPath + "/";
+
+        final String encodedBasePath = UriComponent.encode(decodedBasePath,
+                UriComponent.Type.PATH);
+
+        if (!decodedBasePath.equals(encodedBasePath)) {
+            throw new ProcessingException("The servlet context path and/or the "
+                    + "servlet path contain characters that are percent encoded");
+        }
+
+        final URI baseUri;
+        final URI requestUri;
+        try {
+            baseUri = absoluteUriBuilder.replacePath(encodedBasePath).build();
+            String queryParameters = ContainerUtils.encodeUnsafeCharacters(request.getQueryString());
+            if (queryParameters == null) {
+                queryParameters = "";
+            }
+
+            requestUri = absoluteUriBuilder.replacePath(requestURI)
+                    .replaceQuery(queryParameters)
+                    .build();
+        } catch (final UriBuilderException | IllegalArgumentException ex) {
+            setResponseForInvalidUri(response, ex);
+            return;
+        }
+
+        service(baseUri, requestUri, request, response);
+    }
+
+    private void setResponseForInvalidUri(final HttpServletResponse response, final Throwable throwable) throws IOException {
+        LOGGER.log(Level.FINER, "Error while processing request.", throwable);
+
+        final Response.Status badRequest = Response.Status.BAD_REQUEST;
+        if (webComponent.configSetStatusOverSendError) {
+            response.reset();
+            //noinspection deprecation
+            response.setStatus(badRequest.getStatusCode(), badRequest.getReasonPhrase());
+        } else {
+            response.sendError(badRequest.getStatusCode(), badRequest.getReasonPhrase());
+        }
+    }
+
+    @Override
+    public void destroy() {
+        super.destroy();
+
+        final ContainerLifecycleListener listener = containerListener;
+        if (listener != null) {
+            listener.onShutdown(this);
+        }
+    }
+
+    @Override
+    public void init() throws ServletException {
+        init(new WebServletConfig(this));
+    }
+
+    /**
+     * Dispatch client requests to a resource class.
+     *
+     * @param baseUri    the base URI of the request.
+     * @param requestUri the URI of the request.
+     * @param request    the {@link javax.servlet.http.HttpServletRequest} object that contains the request the client made to
+     *                   the Web component.
+     * @param response   the {@link javax.servlet.http.HttpServletResponse} object that contains the response the Web component
+     *                   returns to the client.
+     * @return lazily initialized response status code {@link Value value provider}. If not resolved in the moment of call to
+     * {@link Value#get()}, {@code -1} is returned.
+     * @throws IOException      if an input or output error occurs while the Web component is handling the HTTP request.
+     * @throws ServletException if the HTTP request cannot be handled.
+     */
+    public Value<Integer> service(final URI baseUri, final URI requestUri, final HttpServletRequest request,
+                                  final HttpServletResponse response) throws ServletException, IOException {
+        return webComponent.service(baseUri, requestUri, request, response);
+    }
+
+    /**
+     * Dispatch client requests to a resource class and returns {@link ResponseWriter},
+     * Servlet's {@link org.glassfish.jersey.server.spi.ContainerResponseWriter} implementation.
+     *
+     * @param baseUri    the base URI of the request.
+     * @param requestUri the URI of the request.
+     * @param request    the {@link javax.servlet.http.HttpServletRequest} object that contains the request the client made to
+     *                   the Web component.
+     * @param response   the {@link javax.servlet.http.HttpServletResponse} object that contains the response the Web component
+     *                   returns to the client.
+     * @return returns {@link ResponseWriter}, Servlet's {@link org.glassfish.jersey.server.spi.ContainerResponseWriter}
+     *         implementation, into which processed request response was written to.
+     * @throws IOException      if an input or output error occurs while the Web component is handling the HTTP request.
+     * @throws ServletException if the HTTP request cannot be handled.
+     */
+    private ResponseWriter serviceImpl(final URI baseUri, final URI requestUri, final HttpServletRequest request,
+                                       final HttpServletResponse response) throws ServletException, IOException {
+        return webComponent.serviceImpl(baseUri, requestUri, request, response);
+    }
+
+    // Filter
+    @Override
+    public void init(final FilterConfig filterConfig) throws ServletException {
+        this.filterConfig = filterConfig;
+        init(new WebFilterConfig(filterConfig));
+
+        final String regex = (String) getConfiguration().getProperty(ServletProperties.FILTER_STATIC_CONTENT_REGEX);
+        if (regex != null && !regex.isEmpty()) {
+            try {
+                staticContentPattern = Pattern.compile(regex);
+            } catch (final PatternSyntaxException ex) {
+                throw new ContainerException(LocalizationMessages.INIT_PARAM_REGEX_SYNTAX_INVALID(
+                        regex, ServletProperties.FILTER_STATIC_CONTENT_REGEX), ex);
+            }
+        }
+
+        this.filterContextPath = filterConfig.getInitParameter(ServletProperties.FILTER_CONTEXT_PATH);
+        if (filterContextPath != null) {
+            if (filterContextPath.isEmpty()) {
+                filterContextPath = null;
+            } else {
+                if (!filterContextPath.startsWith("/")) {
+                    filterContextPath = '/' + filterContextPath;
+                }
+                if (filterContextPath.endsWith("/")) {
+                    filterContextPath = filterContextPath.substring(0, filterContextPath.length() - 1);
+                }
+            }
+        }
+
+        // get the url-pattern defined (e.g.) in the filter-mapping section of web.xml
+        final FilterUrlMappingsProvider filterUrlMappingsProvider = getFilterUrlMappingsProvider();
+        if (filterUrlMappingsProvider != null) {
+            filterUrlMappings = filterUrlMappingsProvider.getFilterUrlMappings(filterConfig);
+        }
+
+        // we need either the url-pattern from the filter mapping (in case of Servlet 3) or specific init-param to
+        // determine the baseUri and request relative URI. If we do not have either one, the app will most likely
+        // not work (won't be accessible)
+        if (filterUrlMappings == null && filterContextPath == null) {
+            LOGGER.warning(LocalizationMessages.FILTER_CONTEXT_PATH_MISSING());
+        }
+    }
+
+    @Override
+    public void doFilter(final ServletRequest servletRequest,
+                         final ServletResponse servletResponse,
+                         final FilterChain filterChain)
+            throws IOException, ServletException {
+        try {
+            doFilter((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse, filterChain);
+        } catch (final ClassCastException e) {
+            throw new ServletException("non-HTTP request or response", e);
+        }
+    }
+
+    /**
+     * Get the servlet context for the servlet or filter, depending on
+     * how this class is registered.
+     *
+     * @return the servlet context for the servlet or filter.
+     */
+    @Override
+    public ServletContext getServletContext() {
+        if (filterConfig != null) {
+            return filterConfig.getServletContext();
+        }
+
+        return super.getServletContext();
+    }
+
+    /**
+     * Dispatches client requests to the
+     * {@link #service(URI, URI, HttpServletRequest, HttpServletResponse)} method.
+     * <p />
+     * If the servlet path matches the regular expression declared by the
+     * property {@link ServletProperties#FILTER_STATIC_CONTENT_REGEX} then the
+     * request is forwarded to the next filter in the filter chain so that the
+     * underlying servlet engine can process the request otherwise Jersey
+     * will process the request.
+     *
+     * @param request  the {@link HttpServletRequest} object that
+     *                 contains the request the client made to
+     *                 the servlet.
+     * @param response the {@link HttpServletResponse} object that
+     *                 contains the response the servlet returns
+     *                 to the client.
+     * @param chain    the chain of filters from which the next filter can be invoked.
+     * @throws java.io.IOException            in case of an I/O error.
+     * @throws javax.servlet.ServletException in case of an error while executing the
+     *                                        filter chain.
+     */
+    public void doFilter(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain)
+            throws IOException, ServletException {
+        if (request.getAttribute("javax.servlet.include.request_uri") != null) {
+            final String includeRequestURI = (String) request.getAttribute("javax.servlet.include.request_uri");
+
+            if (!includeRequestURI.equals(request.getRequestURI())) {
+                doFilter(request, response, chain,
+                        includeRequestURI,
+                        (String) request.getAttribute("javax.servlet.include.servlet_path"),
+                        (String) request.getAttribute("javax.servlet.include.query_string"));
+                return;
+            }
+        }
+
+        /**
+         * JERSEY-880 - WAS interprets HttpServletRequest#getServletPath() and HttpServletRequest#getPathInfo()
+         * differently when accessing a static resource.
+         */
+        final String servletPath = request.getServletPath()
+                + (request.getPathInfo() == null ? "" : request.getPathInfo());
+
+        doFilter(request, response, chain,
+                request.getRequestURI(),
+                servletPath,
+                request.getQueryString());
+    }
+
+    private void doFilter(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain,
+                          final String requestURI, final String servletPath, final String queryString)
+            throws IOException, ServletException {
+        // if we match the static content regular expression lets delegate to
+        // the filter chain to use the default container servlets & handlers
+        final Pattern p = getStaticContentPattern();
+        if (p != null && p.matcher(servletPath).matches()) {
+            chain.doFilter(request, response);
+            return;
+        }
+
+        if (filterContextPath != null) {
+            if (!servletPath.startsWith(filterContextPath)) {
+                throw new ContainerException(LocalizationMessages.SERVLET_PATH_MISMATCH(servletPath, filterContextPath));
+                //TODO:
+                //            } else if (servletPath.length() == filterContextPath.length()) {
+                //                // Path does not end in a slash, may need to redirect
+                //                if (webComponent.getResourceConfig().getFeature(ResourceConfig.FEATURE_REDIRECT)) {
+                //                    URI l = UriBuilder.fromUri(request.getRequestURL().toString()).
+                //                            path("/").
+                //                            replaceQuery(queryString).build();
+                //
+                //                    response.setStatus(307);
+                //                    response.setHeader("Location", l.toASCIIString());
+                //                    return;
+                //                } else {
+                //                    requestURI += "/";
+                //                }
+            }
+        }
+
+        final URI baseUri;
+        final URI requestUri;
+        try {
+            final UriBuilder absoluteUriBuilder = UriBuilder.fromUri(request.getRequestURL().toString());
+
+            // depending on circumstances, use the correct path to replace in the absolute request URI
+            final String pickedUrlMapping = pickUrlMapping(request.getRequestURL().toString(), filterUrlMappings);
+            final String replacingPath = pickedUrlMapping != null
+                    ? pickedUrlMapping
+                    : (filterContextPath != null ? filterContextPath : "");
+
+            baseUri = absoluteUriBuilder.replacePath(request.getContextPath()).path(replacingPath).path("/").build();
+
+
+            requestUri = absoluteUriBuilder.replacePath(requestURI)
+                    .replaceQuery(ContainerUtils.encodeUnsafeCharacters(queryString))
+                    .build();
+        } catch (final IllegalArgumentException iae) {
+            setResponseForInvalidUri(response, iae);
+            return;
+        }
+
+        final ResponseWriter responseWriter = serviceImpl(baseUri, requestUri, request, response);
+
+        // If forwarding is configured and response is a 404 with no entity
+        // body then call the next filter in the chain
+
+        if (webComponent.forwardOn404 && !response.isCommitted()) {
+            boolean hasEntity = false;
+            Response.StatusType status = null;
+            if (responseWriter.responseContextResolved()) {
+                final ContainerResponse responseContext = responseWriter.getResponseContext();
+                hasEntity = responseContext.hasEntity();
+                status = responseContext.getStatusInfo();
+            }
+            if (!hasEntity && status == Response.Status.NOT_FOUND) {
+                // lets clear the response to OK before we forward to the next in the chain
+                // as OK is the default set by servlet containers before filters/servlets do any work
+                // so lets hide our footsteps and pretend we were never in the chain at all and let the
+                // next filter or servlet return the 404 if they can't find anything to return
+                //
+                // We could add an optional flag to disable this step if anyone can ever find a case where
+                // this causes a problem, though I suspect any problems will really be with downstream
+                // servlets not correctly setting an error status if they cannot find something to return
+                response.setStatus(HttpServletResponse.SC_OK);
+                chain.doFilter(request, response);
+            }
+        }
+    }
+
+    /**
+     * Picks the most suitable url mapping (in case more than one is defined) based on the request URI.
+     *
+     * @param requestUri String representation of the request URI
+     * @param filterUrlMappings set of configured filter url-patterns
+     * @return the most suitable context path, or {@code null} if empty
+     */
+    private String pickUrlMapping(final String requestUri, final List<String> filterUrlMappings) {
+        if (filterUrlMappings == null || filterUrlMappings.isEmpty()) {
+            return null;
+        }
+
+        if (filterUrlMappings.size() == 1) {
+            return filterUrlMappings.get(0);
+        }
+
+        for (final String pattern : filterUrlMappings) {
+            if (requestUri.contains(pattern)) {
+                return pattern;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Resolve the {@link FilterUrlMappingsProvider} service via hk2.
+     *
+     * Will only work in Servlet 3 container, as the older API version
+     * does not provide access to the filter mapping structure.
+     *
+     * @return {@code FilterContextPath} instance, if available, {@code null} otherwise.
+     */
+    private FilterUrlMappingsProvider getFilterUrlMappingsProvider() {
+        FilterUrlMappingsProvider filterUrlMappingsProvider = null;
+        final Iterator<FilterUrlMappingsProvider> providers = Providers.getAllProviders(
+                getApplicationHandler().getInjectionManager(), FilterUrlMappingsProvider.class).iterator();
+        if (providers.hasNext()) {
+             filterUrlMappingsProvider = providers.next();
+        }
+        return filterUrlMappingsProvider;
+    }
+
+    /**
+     * Get the static content path pattern.
+     *
+     * @return the {@link Pattern} compiled from a regular expression that is
+     *         the property value of {@link ServletProperties#FILTER_STATIC_CONTENT_REGEX}.
+     *         A {@code null} value will be returned if the property is not set or is
+     *         an empty String.
+     */
+    protected Pattern getStaticContentPattern() {
+        return staticContentPattern;
+    }
+
+    @Override
+    public ResourceConfig getConfiguration() {
+        return webComponent.appHandler.getConfiguration();
+    }
+
+    @Override
+    public void reload() {
+        reload(getConfiguration());
+    }
+
+    @Override
+    public void reload(final ResourceConfig configuration) {
+        try {
+            containerListener.onShutdown(this);
+
+            webComponent = new WebComponent(webComponent.webConfig, configuration);
+            containerListener = webComponent.appHandler;
+            containerListener.onReload(this);
+            containerListener.onStartup(this);
+        } catch (final ServletException ex) {
+            LOGGER.log(Level.SEVERE, "Reload failed", ex);
+        }
+    }
+
+    @Override
+    public ApplicationHandler getApplicationHandler() {
+        return webComponent.appHandler;
+    }
+
+    /**
+     * Get {@link WebComponent} used by this servlet container.
+     *
+     * @return The web component.
+     */
+    @SuppressWarnings("UnusedDeclaration")
+    public WebComponent getWebComponent() {
+        return webComponent;
+    }
+}
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/ServletProperties.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/ServletProperties.java
new file mode 100644
index 0000000..e6ea54b
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/ServletProperties.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.servlet;
+
+import org.glassfish.jersey.internal.util.PropertiesClass;
+
+/**
+ * Jersey servlet container configuration properties.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+@PropertiesClass
+public final class ServletProperties {
+
+    /**
+     * If set, indicates the URL pattern of the Jersey servlet filter context path.
+     * <p>
+     * If the URL pattern of a filter is set to a base path and a wildcard,
+     * such as "/base/*", then this property can be used to declare a filter
+     * context path that behaves in the same manner as the Servlet context
+     * path for determining the base URI of the application. (Note that with
+     * the Servlet 2.x API it is not possible to determine the URL pattern
+     * without parsing the {@code web.xml}, hence why this property is necessary.)
+     * <p>
+     * The property is only applicable when {@link ServletContainer Jersey servlet
+     * container} is configured to run as a {@link javax.servlet.Filter}, otherwise this property
+     * will be ignored.
+     * <p>
+     * The value of the property may consist of one or more path segments separate by
+     * {@code '/'}.
+     * <p></p>
+     * A default value is not set.
+     * <p></p>
+     * The name of the configuration property is <tt>{@value}</tt>.
+     */
+    public static final String FILTER_CONTEXT_PATH = "jersey.config.servlet.filter.contextPath";
+
+    /**
+     * If set to {@code true} and a 404 response with no entity body is returned
+     * from either the runtime or the application then the runtime forwards the
+     * request to the next filter in the filter chain. This enables another filter
+     * or the underlying servlet engine to process the request. Before the request
+     * is forwarded the response status is set to 200.
+     * <p>
+     * This property is an alternative to setting a {@link #FILTER_STATIC_CONTENT_REGEX
+     * static content regular expression} and requires less configuration. However,
+     * application code, such as methods corresponding to sub-resource locators,
+     * may be invoked when this feature is enabled.
+     * <p></p>
+     * The property is only applicable when {@link ServletContainer Jersey servlet
+     * container} is configured to run as a {@link javax.servlet.Filter}, otherwise
+     * this property will be ignored.
+     * <p></p>
+     * Application code, such as methods corresponding to sub-resource locators
+     * may be invoked when this feature is enabled.
+     * <p>
+     * The default value is {@code false}.
+     * <p></p>
+     * The name of the configuration property is <tt>{@value}</tt>.
+     */
+    public static final String FILTER_FORWARD_ON_404 = "jersey.config.servlet.filter.forwardOn404";
+
+    /**
+     * If set the regular expression is used to match an incoming servlet path URI
+     * to some web page content such as static resources or JSPs to be handled
+     * by the underlying servlet engine.
+     * <p></p>
+     * The property is only applicable when {@link ServletContainer Jersey servlet
+     * container} is configured to run as a {@link javax.servlet.Filter}, otherwise
+     * this property will be ignored. If a servlet path matches this regular
+     * expression then the filter forwards the request to the next filter in the
+     * filter chain so that the underlying servlet engine can process the request
+     * otherwise Jersey will process the request. For example if you set the value
+     * to {@code /(image|css)/.*} then you can serve up images and CSS files
+     * for your Implicit or Explicit Views while still processing your JAX-RS
+     * resources.
+     * <p></p>
+     * The type of this property must be a String and the value must be a valid
+     * regular expression.
+     * <p></p>
+     * A default value is not set.
+     * <p></p>
+     * The name of the configuration property is <tt>{@value}</tt>.
+     */
+    public static final String FILTER_STATIC_CONTENT_REGEX = "jersey.config.servlet.filter.staticContentRegex";
+
+    /**
+     * Application configuration initialization property whose value is a fully
+     * qualified class name of a class that implements {@link javax.ws.rs.core.Application}.
+     * <p></p>
+     * A default value is not set.
+     * <p></p>
+     * The name of the configuration property is <tt>{@value}</tt>.
+     */
+    // TODO implement generic support
+    public static final String JAXRS_APPLICATION_CLASS = "javax.ws.rs.Application";
+
+    /**
+     * Indicates that Jersey should scan the whole web app for application-specific resources and
+     * providers. If the property is present and the value is not {@code false}, the whole web app
+     * will be scanned for JAX-RS root resources (annotated with {@link javax.ws.rs.Path @Path})
+     * and providers (annotated with {@link javax.ws.rs.ext.Provider @Provider}).
+     * <p></p>
+     * The property value MUST be an instance of {@link String}. The allowed values are {@code true}
+     * and {@code false}.
+     * <p></p>
+     * A default value is not set.
+     * <p></p>
+     * The name of the configuration property is <tt>{@value}</tt>.
+     */
+    public static final String PROVIDER_WEB_APP = "jersey.config.servlet.provider.webapp";
+
+    /**
+     * If {@code true} then query parameters will not be treated as form parameters (e.g. injectable using
+     * {@link javax.ws.rs.FormParam}) in case a Form request is processed by server.
+     * <p>
+     * The default value is {@code false}.
+     * </p>
+     * <p>
+     * The name of the configuration property is <tt>{@value}</tt>.
+     * </p>
+     *
+     * @since 2.16
+     */
+    public static final String QUERY_PARAMS_AS_FORM_PARAMS_DISABLED = "jersey.config.servlet.form.queryParams.disabled";
+
+    /**
+     * Identifies the object that will be used as a parent {@code HK2 ServiceLocator} in the Jersey
+     * {@link WebComponent}.
+     * <p></p>
+     * This property gives a possibility to use HK2 services that are registered and/or created
+     * outside of the Jersey server context.
+     * <p></p>
+     * By default this property is not set.
+     * <p></p>
+     * The name of the configuration property is <tt>{@value}</tt>.
+     */
+    public static final String SERVICE_LOCATOR = "jersey.config.servlet.context.serviceLocator";
+
+    private ServletProperties() {
+        // prevents instantiation
+    }
+}
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/ServletPropertiesDelegate.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/ServletPropertiesDelegate.java
new file mode 100644
index 0000000..7a17521
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/ServletPropertiesDelegate.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.servlet;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.glassfish.jersey.internal.PropertiesDelegate;
+
+/**
+ * @author Martin Matula
+ */
+class ServletPropertiesDelegate implements PropertiesDelegate {
+    private final HttpServletRequest request;
+
+    public ServletPropertiesDelegate(HttpServletRequest request) {
+        this.request = request;
+    }
+
+    @Override
+    public Object getProperty(String name) {
+        return request.getAttribute(name);
+    }
+
+    @Override
+    public Collection<String> getPropertyNames() {
+        //noinspection unchecked
+        return Collections.list(request.getAttributeNames());
+    }
+
+    @Override
+    public void setProperty(String name, Object object) {
+        request.setAttribute(name, object);
+    }
+
+    @Override
+    public void removeProperty(String name) {
+        request.removeAttribute(name);
+    }
+}
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/WebAppResourcesScanner.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/WebAppResourcesScanner.java
new file mode 100644
index 0000000..f80464a
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/WebAppResourcesScanner.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.servlet;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Deque;
+import java.util.LinkedList;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+import javax.servlet.ServletContext;
+
+import org.glassfish.jersey.server.internal.AbstractResourceFinderAdapter;
+import org.glassfish.jersey.server.internal.scanning.JarFileScanner;
+import org.glassfish.jersey.server.internal.scanning.ResourceFinderException;
+import org.glassfish.jersey.server.internal.scanning.CompositeResourceFinder;
+
+/**
+ * A scanner that recursively scans resources within a Web application.
+ *
+ * @author Paul Sandoz
+ */
+final class WebAppResourcesScanner extends AbstractResourceFinderAdapter {
+
+    private static final String[] paths = new String[] {"/WEB-INF/lib/", "/WEB-INF/classes/"};
+
+    private final ServletContext sc;
+    private CompositeResourceFinder compositeResourceFinder = new CompositeResourceFinder();
+
+    /**
+     * Scan from a set of web resource paths.
+     * <p/>
+     *
+     * @param sc {@link ServletContext}.
+     */
+    WebAppResourcesScanner(final ServletContext sc) {
+        this.sc = sc;
+
+        processPaths(paths);
+    }
+
+    private void processPaths(final String... paths) {
+        for (final String path : paths) {
+
+            final Set<String> resourcePaths = sc.getResourcePaths(path);
+            if (resourcePaths == null) {
+                break;
+            }
+
+            compositeResourceFinder.push(new AbstractResourceFinderAdapter() {
+
+                private final Deque<String> resourcePathsStack = new LinkedList<String>() {
+
+                    private static final long serialVersionUID = 3109256773218160485L;
+
+                    {
+                        for (final String resourcePath : resourcePaths) {
+                            push(resourcePath);
+                        }
+                    }
+                };
+
+                private String current;
+                private String next;
+
+                @Override
+                public boolean hasNext() {
+                    while (next == null && !resourcePathsStack.isEmpty()) {
+                        next = resourcePathsStack.pop();
+
+                        if (next.endsWith("/")) {
+                            processPaths(next);
+                            next = null;
+                        } else if (next.endsWith(".jar")) {
+                            try {
+                                compositeResourceFinder.push(new JarFileScanner(sc.getResourceAsStream(next), "", true));
+                            } catch (final IOException ioe) {
+                                throw new ResourceFinderException(ioe);
+                            }
+                            next = null;
+                        }
+                    }
+
+                    return next != null;
+                }
+
+                @Override
+                public String next() {
+                    if (next != null || hasNext()) {
+                        current = next;
+                        next = null;
+                        return current;
+                    }
+
+                    throw new NoSuchElementException();
+                }
+
+                @Override
+                public InputStream open() {
+                    return sc.getResourceAsStream(current);
+                }
+
+                @Override
+                public void reset() {
+                    throw new UnsupportedOperationException();
+                }
+            });
+
+        }
+    }
+
+    @Override
+    public boolean hasNext() {
+        return compositeResourceFinder.hasNext();
+    }
+
+    @Override
+    public String next() {
+        return compositeResourceFinder.next();
+    }
+
+    @Override
+    public InputStream open() {
+        return compositeResourceFinder.open();
+    }
+
+    @Override
+    public void close() {
+        compositeResourceFinder.close();
+    }
+
+    @Override
+    public void reset() {
+        compositeResourceFinder = new CompositeResourceFinder();
+        processPaths(paths);
+    }
+}
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/WebComponent.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/WebComponent.java
new file mode 100644
index 0000000..84962be
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/WebComponent.java
@@ -0,0 +1,648 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.servlet;
+
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.net.URI;
+import java.security.AccessController;
+import java.security.Principal;
+import java.security.PrivilegedActionException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.core.Form;
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.SecurityContext;
+
+import javax.inject.Inject;
+import javax.inject.Provider;
+import javax.inject.Singleton;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.glassfish.jersey.internal.ServiceFinderBinder;
+import org.glassfish.jersey.internal.inject.AbstractBinder;
+import org.glassfish.jersey.internal.inject.InjectionManager;
+import org.glassfish.jersey.internal.inject.Providers;
+import org.glassfish.jersey.internal.inject.ReferencingFactory;
+import org.glassfish.jersey.internal.util.ReflectionHelper;
+import org.glassfish.jersey.internal.util.collection.Ref;
+import org.glassfish.jersey.internal.util.collection.Value;
+import org.glassfish.jersey.internal.util.collection.Values;
+import org.glassfish.jersey.message.internal.HeaderValueException;
+import org.glassfish.jersey.message.internal.MediaTypes;
+import org.glassfish.jersey.process.internal.RequestScoped;
+import org.glassfish.jersey.server.ApplicationHandler;
+import org.glassfish.jersey.server.BackgroundSchedulerLiteral;
+import org.glassfish.jersey.server.ContainerRequest;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.ServerProperties;
+import org.glassfish.jersey.server.internal.InternalServerProperties;
+import org.glassfish.jersey.server.spi.RequestScopedInitializer;
+import org.glassfish.jersey.servlet.internal.LocalizationMessages;
+import org.glassfish.jersey.servlet.internal.PersistenceUnitBinder;
+import org.glassfish.jersey.servlet.internal.ResponseWriter;
+import org.glassfish.jersey.servlet.internal.ServletContainerProviderFactory;
+import org.glassfish.jersey.servlet.internal.Utils;
+import org.glassfish.jersey.servlet.internal.spi.ExtendedServletContainerProvider;
+import org.glassfish.jersey.servlet.internal.spi.RequestContextProvider;
+import org.glassfish.jersey.servlet.internal.spi.RequestScopedInitializerProvider;
+import org.glassfish.jersey.servlet.internal.spi.ServletContainerProvider;
+import org.glassfish.jersey.servlet.spi.AsyncContextDelegate;
+import org.glassfish.jersey.servlet.spi.AsyncContextDelegateProvider;
+import org.glassfish.jersey.servlet.spi.FilterUrlMappingsProvider;
+import org.glassfish.jersey.uri.UriComponent;
+
+/**
+ * An common Jersey web component that may be extended by a Servlet and/or
+ * Filter implementation, or encapsulated by a Servlet or Filter implementation.
+ *
+ * @author Paul Sandoz
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ * @author Martin Matula
+ * @author Libor Kramolis (libor.kramolis at oracle.com)
+ */
+public class WebComponent {
+
+    private static final Logger LOGGER = Logger.getLogger(WebComponent.class.getName());
+
+    private static final Type REQUEST_TYPE = (new GenericType<Ref<HttpServletRequest>>() {}).getType();
+    private static final Type RESPONSE_TYPE = (new GenericType<Ref<HttpServletResponse>>() {}).getType();
+
+    private static final AsyncContextDelegate DEFAULT_ASYNC_DELEGATE = new AsyncContextDelegate() {
+
+        @Override
+        public void suspend() throws IllegalStateException {
+            throw new UnsupportedOperationException(LocalizationMessages.ASYNC_PROCESSING_NOT_SUPPORTED());
+        }
+
+        @Override
+        public void complete() {
+        }
+    };
+
+    private final RequestScopedInitializerProvider requestScopedInitializer;
+    private final boolean requestResponseBindingExternalized;
+
+    private static final RequestScopedInitializerProvider DEFAULT_REQUEST_SCOPE_INITIALIZER_PROVIDER =
+            context -> (RequestScopedInitializer) injectionManager -> {
+                injectionManager.<Ref<HttpServletRequest>>getInstance(REQUEST_TYPE).set(context.getHttpServletRequest());
+                injectionManager.<Ref<HttpServletResponse>>getInstance(RESPONSE_TYPE).set(context.getHttpServletResponse());
+            };
+
+    /**
+     * Return the first found {@link AsyncContextDelegateProvider}
+     * (via {@link Providers#getAllProviders(InjectionManager, Class)}) or {@code #DEFAULT_ASYNC_DELEGATE} if
+     * other delegate cannot be found.
+     *
+     * @return a non-null AsyncContextDelegateProvider.
+     */
+    private AsyncContextDelegateProvider getAsyncExtensionDelegate() {
+        final Iterator<AsyncContextDelegateProvider> providers = Providers.getAllProviders(appHandler.getInjectionManager(),
+                AsyncContextDelegateProvider.class).iterator();
+        if (providers.hasNext()) {
+            return providers.next();
+        }
+
+        return (request, response) -> DEFAULT_ASYNC_DELEGATE;
+    }
+
+    @SuppressWarnings("JavaDoc")
+    private static class HttpServletRequestReferencingFactory extends ReferencingFactory<HttpServletRequest> {
+
+        @Inject
+        public HttpServletRequestReferencingFactory(final Provider<Ref<HttpServletRequest>> referenceFactory) {
+            super(referenceFactory);
+        }
+    }
+
+    @SuppressWarnings("JavaDoc")
+    private static class HttpServletResponseReferencingFactory extends ReferencingFactory<HttpServletResponse> {
+
+        @Inject
+        public HttpServletResponseReferencingFactory(final Provider<Ref<HttpServletResponse>> referenceFactory) {
+            super(referenceFactory);
+        }
+    }
+
+    private final class WebComponentBinder extends AbstractBinder {
+
+        private final Map<String, Object> applicationProperties;
+
+        /**
+         * Create binder for {@link WebComponent} passing a map of properties to determine whether certain features are allowed
+         * or
+         * not.
+         *
+         * @param applicationProperties map of properties to determine whether certain features are allowed or not.
+         */
+        private WebComponentBinder(final Map<String, Object> applicationProperties) {
+            this.applicationProperties = applicationProperties;
+        }
+
+        @Override
+        protected void configure() {
+
+            if (!requestResponseBindingExternalized) {
+
+                // request
+                bindFactory(HttpServletRequestReferencingFactory.class).to(HttpServletRequest.class)
+                        .proxy(true).proxyForSameScope(false).in(RequestScoped.class);
+
+                bindFactory(ReferencingFactory.referenceFactory())
+                        .to(new GenericType<Ref<HttpServletRequest>>() {}).in(RequestScoped.class);
+
+                // response
+                bindFactory(HttpServletResponseReferencingFactory.class).to(HttpServletResponse.class)
+                        .proxy(true).proxyForSameScope(false).in(RequestScoped.class);
+                bindFactory(ReferencingFactory.referenceFactory())
+                        .to(new GenericType<Ref<HttpServletResponse>>() {}).in(RequestScoped.class);
+            }
+
+            bindFactory(webConfig::getServletContext).to(ServletContext.class).in(Singleton.class);
+
+            final ServletConfig servletConfig = webConfig.getServletConfig();
+            if (webConfig.getConfigType() == WebConfig.ConfigType.ServletConfig) {
+                bindFactory(() -> servletConfig).to(ServletConfig.class).in(Singleton.class);
+
+                // @PersistenceUnit
+                final Enumeration initParams = servletConfig.getInitParameterNames();
+                while (initParams.hasMoreElements()) {
+                    final String initParamName = (String) initParams.nextElement();
+
+                    if (initParamName.startsWith(PersistenceUnitBinder.PERSISTENCE_UNIT_PREFIX)) {
+                        install(new PersistenceUnitBinder(servletConfig));
+                        break;
+                    }
+                }
+            } else {
+                bindFactory(webConfig::getFilterConfig).to(FilterConfig.class).in(Singleton.class);
+            }
+
+            bindFactory(() -> webConfig).to(WebConfig.class).in(Singleton.class);
+
+            install(new ServiceFinderBinder<>(AsyncContextDelegateProvider.class, applicationProperties, RuntimeType.SERVER));
+            install(new ServiceFinderBinder<>(FilterUrlMappingsProvider.class, applicationProperties, RuntimeType.SERVER));
+        }
+    }
+
+    /**
+     * Jersey application handler.
+     */
+    final ApplicationHandler appHandler;
+
+    /**
+     * Jersey background task scheduler - used for scheduling request timeout event handling tasks.
+     */
+    final ScheduledExecutorService backgroundTaskScheduler;
+
+    /**
+     * Web component configuration.
+     */
+    final WebConfig webConfig;
+
+    /**
+     * If {@code true} and deployed as filter, the unmatched requests will be forwarded.
+     */
+    final boolean forwardOn404;
+
+    /**
+     * Cached value of configuration property
+     * {@link org.glassfish.jersey.server.ServerProperties#RESPONSE_SET_STATUS_OVER_SEND_ERROR}.
+     * If {@code true} method {@link HttpServletResponse#setStatus} is used over {@link HttpServletResponse#sendError}.
+     */
+    final boolean configSetStatusOverSendError;
+
+    /**
+     * Asynchronous context delegate provider.
+     */
+    private final AsyncContextDelegateProvider asyncExtensionDelegate;
+
+    /**
+     * Flag whether query parameters should be kept as entity form params if a servlet filter consumes entity and
+     * Jersey has to retrieve form params from servlet request parameters.
+     */
+    private final boolean queryParamsAsFormParams;
+
+    /**
+     * Create and initialize new web component instance.
+     *
+     * @param webConfig      we component configuration.
+     * @param resourceConfig Jersey application configuration.
+     * @throws ServletException in case the Jersey application cannot be created from the supplied
+     *                          resource configuration.
+     */
+    public WebComponent(final WebConfig webConfig, ResourceConfig resourceConfig) throws ServletException {
+
+        this.webConfig = webConfig;
+
+        if (resourceConfig == null) {
+            resourceConfig = createResourceConfig(webConfig);
+        }
+
+
+        final ServletContainerProvider[] allServletContainerProviders =
+                ServletContainerProviderFactory.getAllServletContainerProviders();
+
+        // SPI/extension hook to configure ResourceConfig
+        configure(resourceConfig, allServletContainerProviders);
+
+        boolean rrbExternalized = false;
+        RequestScopedInitializerProvider rsiProvider = null;
+
+        for (final ServletContainerProvider servletContainerProvider : allServletContainerProviders) {
+            if (servletContainerProvider instanceof ExtendedServletContainerProvider) {
+                final ExtendedServletContainerProvider extendedProvider =
+                        (ExtendedServletContainerProvider) servletContainerProvider;
+
+                if (extendedProvider.bindsServletRequestResponse()) {
+                    rrbExternalized = true;
+                }
+                if (rsiProvider == null) { // try to take the first non-null provider
+                    rsiProvider = extendedProvider.getRequestScopedInitializerProvider();
+                }
+            }
+        }
+
+        requestScopedInitializer = rsiProvider != null ? rsiProvider : DEFAULT_REQUEST_SCOPE_INITIALIZER_PROVIDER;
+        requestResponseBindingExternalized = rrbExternalized;
+
+        final AbstractBinder webComponentBinder = new WebComponentBinder(resourceConfig.getProperties());
+        resourceConfig.register(webComponentBinder);
+
+        final Object locator = webConfig.getServletContext()
+                .getAttribute(ServletProperties.SERVICE_LOCATOR);
+
+        this.appHandler = new ApplicationHandler(resourceConfig, webComponentBinder, locator);
+
+        this.asyncExtensionDelegate = getAsyncExtensionDelegate();
+        this.forwardOn404 = webConfig.getConfigType() == WebConfig.ConfigType.FilterConfig
+                && resourceConfig.isProperty(ServletProperties.FILTER_FORWARD_ON_404);
+        this.queryParamsAsFormParams = !resourceConfig.isProperty(ServletProperties.QUERY_PARAMS_AS_FORM_PARAMS_DISABLED);
+        this.configSetStatusOverSendError = ServerProperties.getValue(resourceConfig.getProperties(),
+                ServerProperties.RESPONSE_SET_STATUS_OVER_SEND_ERROR, false, Boolean.class);
+        this.backgroundTaskScheduler = appHandler.getInjectionManager()
+                .getInstance(ScheduledExecutorService.class, BackgroundSchedulerLiteral.INSTANCE);
+    }
+
+    /**
+     * Dispatch client requests to a resource class.
+     *
+     * @param baseUri         the base URI of the request.
+     * @param requestUri      the URI of the request.
+     * @param servletRequest  the {@link javax.servlet.http.HttpServletRequest} object that
+     *                        contains the request the client made to
+     *                        the Web component.
+     * @param servletResponse the {@link javax.servlet.http.HttpServletResponse} object that
+     *                        contains the response the Web component returns
+     *                        to the client.
+     * @return lazily initialized response status code {@link Value value provider}. If not resolved in the moment of call to
+     * {@link Value#get()}, {@code -1} is returned.
+     * @throws java.io.IOException            if an input or output error occurs
+     *                                        while the Web component is handling the
+     *                                        HTTP request.
+     * @throws javax.servlet.ServletException if the HTTP request cannot be handled.
+     */
+    public Value<Integer> service(
+            final URI baseUri,
+            final URI requestUri,
+            final HttpServletRequest servletRequest,
+            final HttpServletResponse servletResponse) throws ServletException, IOException {
+        final ResponseWriter responseWriter = serviceImpl(baseUri, requestUri, servletRequest, servletResponse);
+        return Values.lazy(new Value<Integer>() {
+            @Override
+            public Integer get() {
+                return responseWriter.responseContextResolved() ? responseWriter.getResponseStatus() : -1;
+            }
+        });
+    }
+
+    /**
+     * Dispatch client requests to a resource class.
+     *
+     * @param baseUri         the base URI of the request.
+     * @param requestUri      the URI of the request.
+     * @param servletRequest  the {@link javax.servlet.http.HttpServletRequest} object that
+     *                        contains the request the client made to
+     *                        the Web component.
+     * @param servletResponse the {@link javax.servlet.http.HttpServletResponse} object that
+     *                        contains the response the Web component returns
+     *                        to the client.
+     * @return returns {@link ResponseWriter}, Servlet's {@link org.glassfish.jersey.server.spi.ContainerResponseWriter}
+     *         implementation, into which processed request response was written to.
+     * @throws java.io.IOException            if an input or output error occurs
+     *                                        while the Web component is handling the
+     *                                        HTTP request.
+     * @throws javax.servlet.ServletException if the HTTP request cannot be handled.
+     */
+     /* package */ ResponseWriter serviceImpl(
+            final URI baseUri,
+            final URI requestUri,
+            final HttpServletRequest servletRequest,
+            final HttpServletResponse servletResponse) throws ServletException, IOException {
+
+        final ResponseWriter responseWriter = new ResponseWriter(
+                forwardOn404,
+                configSetStatusOverSendError,
+                servletResponse,
+                asyncExtensionDelegate.createDelegate(servletRequest, servletResponse),
+                backgroundTaskScheduler);
+
+        try {
+            final ContainerRequest requestContext = new ContainerRequest(baseUri, requestUri, servletRequest.getMethod(),
+                    getSecurityContext(servletRequest), new ServletPropertiesDelegate(servletRequest));
+
+            initContainerRequest(requestContext, servletRequest, servletResponse, responseWriter);
+
+            appHandler.handle(requestContext);
+        } catch (final HeaderValueException hve) {
+            if (LOGGER.isLoggable(Level.FINE)) {
+                LOGGER.log(Level.FINE, LocalizationMessages.HEADER_VALUE_READ_FAILED(), hve);
+            }
+
+            final Response.Status status = Response.Status.BAD_REQUEST;
+
+            if (configSetStatusOverSendError) {
+                servletResponse.reset();
+                //noinspection deprecation
+                servletResponse.setStatus(status.getStatusCode(), status.getReasonPhrase());
+            } else {
+                servletResponse.sendError(status.getStatusCode(), status.getReasonPhrase());
+            }
+        } catch (final Exception e) {
+            throw new ServletException(e);
+        }
+        return responseWriter;
+    }
+
+    /**
+     * Initialize {@code ContainerRequest} instance to used used to handle {@code servletRequest}.
+     */
+    private void initContainerRequest(
+            final ContainerRequest requestContext,
+            final HttpServletRequest servletRequest,
+            final HttpServletResponse servletResponse,
+            final ResponseWriter responseWriter) throws IOException {
+
+        requestContext.setEntityStream(servletRequest.getInputStream());
+        requestContext.setRequestScopedInitializer(requestScopedInitializer.get(new RequestContextProvider() {
+            @Override
+            public HttpServletRequest getHttpServletRequest() {
+                return servletRequest;
+            }
+            @Override
+            public HttpServletResponse getHttpServletResponse() {
+                return servletResponse;
+            }
+        }));
+        requestContext.setWriter(responseWriter);
+
+        addRequestHeaders(servletRequest, requestContext);
+        // Check if any servlet filters have consumed a request entity
+        // of the media type application/x-www-form-urlencoded
+        // This can happen if a filter calls request.getParameter(...)
+        filterFormParameters(servletRequest, requestContext);
+    }
+
+    /**
+     * Get default {@link javax.ws.rs.core.SecurityContext} for given {@code request}.
+     *
+     * @param request http servlet request to create a security context for.
+     * @return a non-null security context instance.
+     */
+    private static SecurityContext getSecurityContext(final HttpServletRequest request) {
+        return new SecurityContext() {
+
+            @Override
+            public Principal getUserPrincipal() {
+                return request.getUserPrincipal();
+            }
+
+            @Override
+            public boolean isUserInRole(final String role) {
+                return request.isUserInRole(role);
+            }
+
+            @Override
+            public boolean isSecure() {
+                return request.isSecure();
+            }
+
+            @Override
+            public String getAuthenticationScheme() {
+                return request.getAuthType();
+            }
+        };
+    }
+
+    /**
+     * Create a {@link ResourceConfig} instance from given {@link WebConfig}.
+     *
+     * @param config web config to create resource config from.
+     * @return resource config instance.
+     * @throws ServletException if an error has occurred.
+     */
+    private static ResourceConfig createResourceConfig(final WebConfig config) throws ServletException {
+        final ServletContext servletContext = config.getServletContext();
+
+        // check if ResourceConfig has already been created, if so use it
+        ResourceConfig resourceConfig = Utils.retrieve(config.getServletContext(), config.getName());
+        if (resourceConfig != null) {
+            return resourceConfig;
+        }
+
+        final Map<String, Object> initParams = getInitParams(config);
+        final Map<String, Object> contextParams = Utils.getContextParams(servletContext);
+
+        // check if the JAX-RS application config class property is present
+        final String jaxrsApplicationClassName = config.getInitParameter(ServletProperties.JAXRS_APPLICATION_CLASS);
+
+        if (jaxrsApplicationClassName == null) {
+            // If no resource config class property is present, create default config
+            resourceConfig = new ResourceConfig().addProperties(initParams).addProperties(contextParams);
+
+            final String webApp = config.getInitParameter(ServletProperties.PROVIDER_WEB_APP);
+            if (webApp != null && !"false".equals(webApp)) {
+                resourceConfig.registerFinder(new WebAppResourcesScanner(servletContext));
+            }
+            return resourceConfig;
+        }
+
+        try {
+            final Class<? extends javax.ws.rs.core.Application> jaxrsApplicationClass = AccessController.doPrivileged(
+                    ReflectionHelper.<javax.ws.rs.core.Application>classForNameWithExceptionPEA(jaxrsApplicationClassName)
+            );
+
+            if (javax.ws.rs.core.Application.class.isAssignableFrom(jaxrsApplicationClass)) {
+                return ResourceConfig.forApplicationClass(jaxrsApplicationClass)
+                        .addProperties(initParams).addProperties(contextParams);
+            } else {
+                throw new ServletException(LocalizationMessages.RESOURCE_CONFIG_PARENT_CLASS_INVALID(
+                        jaxrsApplicationClassName, javax.ws.rs.core.Application.class));
+            }
+        } catch (final PrivilegedActionException e) {
+            throw new ServletException(
+                    LocalizationMessages.RESOURCE_CONFIG_UNABLE_TO_LOAD(jaxrsApplicationClassName), e.getCause());
+        } catch (final ClassNotFoundException e) {
+            throw new ServletException(LocalizationMessages.RESOURCE_CONFIG_UNABLE_TO_LOAD(jaxrsApplicationClassName), e);
+        }
+    }
+
+    /**
+     * SPI/extension hook to configure ResourceConfig.
+     *
+     * @param resourceConfig Jersey application configuration.
+     * @throws ServletException if an error has occurred.
+     */
+    private void configure(final ResourceConfig resourceConfig,
+                           final ServletContainerProvider[] allServletContainerProviders) throws ServletException {
+
+        for (final ServletContainerProvider servletContainerProvider : allServletContainerProviders) {
+            servletContainerProvider.configure(resourceConfig);
+        }
+    }
+
+    /**
+     * Copy request headers present in {@code request} into {@code requestContext} ignoring {@code null} values.
+     *
+     * @param request        http servlet request to copy headers from.
+     * @param requestContext container request to copy headers to.
+     */
+    @SuppressWarnings("unchecked")
+    private void addRequestHeaders(final HttpServletRequest request, final ContainerRequest requestContext) {
+        final Enumeration<String> names = request.getHeaderNames();
+        while (names.hasMoreElements()) {
+            final String name = names.nextElement();
+
+            final Enumeration<String> values = request.getHeaders(name);
+            while (values.hasMoreElements()) {
+                final String value = values.nextElement();
+                if (value != null) { // filter out null values
+                    requestContext.header(name, value);
+                }
+            }
+        }
+    }
+
+    /**
+     * Extract init params from {@link WebConfig}.
+     *
+     * @param webConfig actual servlet context.
+     * @return map representing current init parameters.
+     */
+    private static Map<String, Object> getInitParams(final WebConfig webConfig) {
+        final Map<String, Object> props = new HashMap<>();
+        final Enumeration names = webConfig.getInitParameterNames();
+        while (names.hasMoreElements()) {
+            final String name = (String) names.nextElement();
+            props.put(name, webConfig.getInitParameter(name));
+        }
+        return props;
+    }
+
+    /**
+     * Extract parameters contained in {@link HttpServletRequest servlet request} and put them into
+     * {@link ContainerRequest container request} under
+     * {@value org.glassfish.jersey.server.internal.InternalServerProperties#FORM_DECODED_PROPERTY} property (as {@link Form}
+     * instance).
+     *
+     * @param servletRequest   http servlet request to extract params from.
+     * @param containerRequest container request to put {@link Form} property to.
+     */
+    private void filterFormParameters(final HttpServletRequest servletRequest, final ContainerRequest containerRequest) {
+        if (MediaTypes.typeEqual(MediaType.APPLICATION_FORM_URLENCODED_TYPE, containerRequest.getMediaType())
+                && !containerRequest.hasEntity()) {
+            final Form form = new Form();
+            final Enumeration parameterNames = servletRequest.getParameterNames();
+
+            final String queryString = servletRequest.getQueryString();
+            final List<String> queryParams = queryString != null ? getDecodedQueryParamList(queryString)
+                    : Collections.<String>emptyList();
+
+            final boolean keepQueryParams = queryParamsAsFormParams || queryParams.isEmpty();
+            final MultivaluedMap<String, String> formMap = form.asMap();
+
+            while (parameterNames.hasMoreElements()) {
+                final String name = (String) parameterNames.nextElement();
+                final List<String> values = Arrays.asList(servletRequest.getParameterValues(name));
+
+                formMap.put(name, keepQueryParams ? values : filterQueryParams(name, values, queryParams));
+            }
+
+            if (!formMap.isEmpty()) {
+                containerRequest.setProperty(InternalServerProperties.FORM_DECODED_PROPERTY, form);
+
+                if (LOGGER.isLoggable(Level.WARNING)) {
+                    LOGGER.log(Level.WARNING, LocalizationMessages.FORM_PARAM_CONSUMED(containerRequest.getRequestUri()));
+                }
+            }
+        }
+    }
+
+    private List<String> getDecodedQueryParamList(final String queryString) {
+        final List<String> params = new ArrayList<>();
+        for (final String param : queryString.split("&")) {
+            params.add(UriComponent.decode(param, UriComponent.Type.QUERY_PARAM));
+        }
+        return params;
+    }
+
+    /**
+     * From given list of values remove values that represents values of query params of the same name as the processed form
+     * parameter.
+     *
+     * @param name   name of form/query parameter.
+     * @param values values of form/query parameter.
+     * @param params collection of unprocessed query parameters.
+     * @return list of form param values for given name without values of query param of the same name.
+     */
+    private List<String> filterQueryParams(final String name, final List<String> values, final Collection<String> params) {
+        return values.stream()
+                     .filter(s -> !params.remove(name + "=" + s) && !params.remove(name + "[]=" + s))
+                     .collect(Collectors.toList());
+    }
+
+    /**
+     * Get {@link ApplicationHandler} used by this web component.
+     *
+     * @return The application handler
+     */
+    public ApplicationHandler getAppHandler() {
+        return appHandler;
+    }
+}
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/WebConfig.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/WebConfig.java
new file mode 100644
index 0000000..bb469c6
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/WebConfig.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.servlet;
+
+import java.util.Enumeration;
+
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+
+/**
+ * The Web configuration for accessing initialization parameters of a Web
+ * component and the {@link ServletContext}.
+ *
+ * @author Paul Sandoz
+ */
+public interface WebConfig {
+
+    /**
+     * The web configuration type.
+     */
+    public static enum ConfigType {
+        /**
+         * A configuration type of servlet configuration.
+         */
+        ServletConfig,
+        /**
+         * A configuration type of filter configuration.
+         */
+        FilterConfig
+    }
+
+    /**
+     * Get the configuration type of this config.
+     *
+     * @return the configuration type.
+     */
+    ConfigType getConfigType();
+
+    /**
+     * Get the corresponding ServletConfig if this WebConfig represents a {@link ServletConfig}
+     *
+     * @return servlet config or null
+     */
+    ServletConfig getServletConfig();
+
+    /**
+     * Get the corresponding FilterConfig if this WebConfig represents a {@link FilterConfig}
+     *
+     * @return filter config or null
+     */
+    FilterConfig getFilterConfig();
+
+    /**
+     * Get the name of the Web component.
+     *
+     * @return the name of the Web component.
+     */
+    String getName();
+
+    /**
+     * Get an initialization parameter.
+     *
+     * @param name the parameter name.
+     * @return the parameter value, or null if the parameter is not present.
+     */
+    String getInitParameter(String name);
+
+    /**
+     * Get the enumeration of initialization parameter names.
+     *
+     * @return the enumeration of initialization parameter names.
+     */
+    Enumeration getInitParameterNames();
+
+    /**
+     * Get the {@link ServletContext}.
+     *
+     * @return the {@link ServletContext}.
+     */
+    ServletContext getServletContext();
+}
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/WebFilterConfig.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/WebFilterConfig.java
new file mode 100644
index 0000000..d375b85
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/WebFilterConfig.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.servlet;
+
+import java.util.Enumeration;
+
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+
+/**
+ * A filter based web config. Delegates all invocations to the filter
+ * configuration from the servlet api.
+ *
+ * @author Paul Sandoz
+ * @author Guilherme Silveira
+ */
+public final class WebFilterConfig implements WebConfig {
+
+    private final FilterConfig filterConfig;
+
+    public WebFilterConfig(final FilterConfig filterConfig) {
+        this.filterConfig = filterConfig;
+    }
+
+    @Override
+    public WebConfig.ConfigType getConfigType() {
+        return WebConfig.ConfigType.FilterConfig;
+    }
+
+    @Override
+    public ServletConfig getServletConfig() {
+        return null;
+    }
+
+    @Override
+    public FilterConfig getFilterConfig() {
+        return filterConfig;
+    }
+
+    @Override
+    public String getName() {
+        return filterConfig.getFilterName();
+    }
+
+    @Override
+    public String getInitParameter(final String name) {
+        return filterConfig.getInitParameter(name);
+    }
+
+    @Override
+    public Enumeration getInitParameterNames() {
+        return filterConfig.getInitParameterNames();
+    }
+
+    @Override
+    public ServletContext getServletContext() {
+        return filterConfig.getServletContext();
+    }
+}
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/WebServletConfig.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/WebServletConfig.java
new file mode 100644
index 0000000..5c2a7a7
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/WebServletConfig.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.servlet;
+
+import java.util.Enumeration;
+
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+
+/**
+ * A servlet based web config. Delegates all invocations to the servlet
+ * configuration from the servlet api.
+ *
+ * @author Paul Sandoz
+ * @author guilherme silveira
+ */
+public final class WebServletConfig implements WebConfig {
+
+    private final ServletContainer servlet;
+
+    public WebServletConfig(final ServletContainer servlet) {
+        this.servlet = servlet;
+    }
+
+    @Override
+    public WebConfig.ConfigType getConfigType() {
+        return WebConfig.ConfigType.ServletConfig;
+    }
+
+    @Override
+    public ServletConfig getServletConfig() {
+        return servlet.getServletConfig();
+    }
+
+    @Override
+    public FilterConfig getFilterConfig() {
+        return null;
+    }
+
+    @Override
+    public String getName() {
+        return servlet.getServletName();
+    }
+
+    @Override
+    public String getInitParameter(final String name) {
+        return servlet.getInitParameter(name);
+    }
+
+    @Override
+    public Enumeration getInitParameterNames() {
+        return servlet.getInitParameterNames();
+    }
+
+    @Override
+    public ServletContext getServletContext() {
+        return servlet.getServletContext();
+    }
+}
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/PersistenceUnitBinder.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/PersistenceUnitBinder.java
new file mode 100644
index 0000000..f1795c6
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/PersistenceUnitBinder.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.servlet.internal;
+
+import java.lang.reflect.Proxy;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.ws.rs.core.GenericType;
+
+import javax.inject.Singleton;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.PersistenceUnit;
+import javax.servlet.ServletConfig;
+
+import org.glassfish.jersey.internal.inject.AbstractBinder;
+import org.glassfish.jersey.internal.inject.Injectee;
+import org.glassfish.jersey.internal.inject.InjectionResolver;
+import org.glassfish.jersey.server.ContainerException;
+
+/**
+ * {@link PersistenceUnit Persistence unit} injection binder.
+ *
+ * @author Michal Gajdos
+ */
+public class PersistenceUnitBinder extends AbstractBinder {
+
+    private final ServletConfig servletConfig;
+
+    /**
+     * Prefix of the persistence unit init param.
+     */
+    public static final String PERSISTENCE_UNIT_PREFIX = "unit:";
+
+    /**
+     * Creates a new binder for {@link PersistenceUnitInjectionResolver}.
+     *
+     * @param servletConfig servlet config to find persistence units.
+     */
+    public PersistenceUnitBinder(ServletConfig servletConfig) {
+        this.servletConfig = servletConfig;
+    }
+
+    @Singleton
+    private static class PersistenceUnitInjectionResolver implements InjectionResolver<PersistenceUnit> {
+
+        private final Map<String, String> persistenceUnits = new HashMap<>();
+
+        private PersistenceUnitInjectionResolver(ServletConfig servletConfig) {
+            for (final Enumeration parameterNames = servletConfig.getInitParameterNames(); parameterNames.hasMoreElements(); ) {
+                final String key = (String) parameterNames.nextElement();
+
+                if (key.startsWith(PERSISTENCE_UNIT_PREFIX)) {
+                    persistenceUnits.put(key.substring(PERSISTENCE_UNIT_PREFIX.length()),
+                            "java:comp/env/" + servletConfig.getInitParameter(key));
+                }
+            }
+        }
+
+        @Override
+        public Object resolve(Injectee injectee) {
+            if (!injectee.getRequiredType().equals(EntityManagerFactory.class)) {
+                return null;
+            }
+
+            final PersistenceUnit annotation = injectee.getParent().getAnnotation(PersistenceUnit.class);
+            final String unitName = annotation.unitName();
+
+            if (!persistenceUnits.containsKey(unitName)) {
+                throw new ContainerException(LocalizationMessages.PERSISTENCE_UNIT_NOT_CONFIGURED(unitName));
+            }
+
+            return Proxy.newProxyInstance(
+                    this.getClass().getClassLoader(),
+                    new Class[] {EntityManagerFactory.class},
+                    new ThreadLocalNamedInvoker<EntityManagerFactory>(persistenceUnits.get(unitName)));
+        }
+
+        @Override
+        public boolean isConstructorParameterIndicator() {
+            return false;
+        }
+
+        @Override
+        public boolean isMethodParameterIndicator() {
+            return false;
+        }
+
+        @Override
+        public Class<PersistenceUnit> getAnnotation() {
+            return PersistenceUnit.class;
+        }
+    }
+
+    @Override
+    protected void configure() {
+        bind(new PersistenceUnitInjectionResolver(servletConfig))
+                .to(new GenericType<InjectionResolver<PersistenceUnit>>() {});
+    }
+}
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/ResponseWriter.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/ResponseWriter.java
new file mode 100644
index 0000000..b96ebb7
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/ResponseWriter.java
@@ -0,0 +1,314 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.servlet.internal;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.glassfish.jersey.server.ContainerException;
+import org.glassfish.jersey.server.ContainerResponse;
+import org.glassfish.jersey.server.internal.JerseyRequestTimeoutHandler;
+import org.glassfish.jersey.server.spi.ContainerResponseWriter;
+import org.glassfish.jersey.servlet.spi.AsyncContextDelegate;
+
+/**
+ * An internal implementation of {@link ContainerResponseWriter} for Servlet containers.
+ * The writer depends on provided {@link AsyncContextDelegate} to support async functionality.
+ *
+ * @author Paul Sandoz
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ * @author Martin Matula
+ * @author Libor Kramolis (libor.kramolis at oracle.com)
+ */
+public class ResponseWriter implements ContainerResponseWriter {
+
+    private static final Logger LOGGER = Logger.getLogger(ResponseWriter.class.getName());
+
+    private final HttpServletResponse response;
+    /**
+     * Cached value of configuration property
+     * {@link org.glassfish.jersey.servlet.ServletProperties#FILTER_FORWARD_ON_404}.
+     */
+    private final boolean useSetStatusOn404;
+    /**
+     * Cached value of configuration property
+     * {@link org.glassfish.jersey.server.ServerProperties#RESPONSE_SET_STATUS_OVER_SEND_ERROR}.
+     * If {@code true} method {@link HttpServletResponse#setStatus} is used over {@link HttpServletResponse#sendError}.
+     */
+    private final boolean configSetStatusOverSendError;
+    private final CompletableFuture<ContainerResponse> responseContext;
+    private final AsyncContextDelegate asyncExt;
+
+    private final JerseyRequestTimeoutHandler requestTimeoutHandler;
+
+    /**
+     * Creates a new instance to write a single Jersey response.
+     *
+     * @param useSetStatusOn404            true if status should be written explicitly when 404 is returned
+     * @param configSetStatusOverSendError if {@code true} method {@link HttpServletResponse#setStatus} is used over
+     *                                     {@link HttpServletResponse#sendError}
+     * @param response                     original HttpResponseRequest
+     * @param asyncExt                     delegate to use for async features implementation
+     * @param timeoutTaskExecutor          Jersey runtime executor used for background execution of timeout
+     *                                     handling tasks.
+     */
+    public ResponseWriter(final boolean useSetStatusOn404,
+                          final boolean configSetStatusOverSendError,
+                          final HttpServletResponse response,
+                          final AsyncContextDelegate asyncExt,
+                          final ScheduledExecutorService timeoutTaskExecutor) {
+        this.useSetStatusOn404 = useSetStatusOn404;
+        this.configSetStatusOverSendError = configSetStatusOverSendError;
+        this.response = response;
+        this.asyncExt = asyncExt;
+        this.responseContext = new CompletableFuture<>();
+
+        this.requestTimeoutHandler = new JerseyRequestTimeoutHandler(this, timeoutTaskExecutor);
+    }
+
+    @Override
+    public boolean suspend(final long timeOut, final TimeUnit timeUnit, final TimeoutHandler timeoutHandler) {
+        try {
+            // Suspend the servlet.
+            asyncExt.suspend();
+        } catch (final IllegalStateException ex) {
+            LOGGER.log(Level.WARNING, LocalizationMessages.SERVLET_REQUEST_SUSPEND_FAILED(), ex);
+            return false;
+        }
+        // Suspend the internal request timeout handler.
+        return requestTimeoutHandler.suspend(timeOut, timeUnit, timeoutHandler);
+    }
+
+    @Override
+    public void setSuspendTimeout(final long timeOut, final TimeUnit timeUnit) throws IllegalStateException {
+        requestTimeoutHandler.setSuspendTimeout(timeOut, timeUnit);
+    }
+
+    @Override
+    public OutputStream writeResponseStatusAndHeaders(final long contentLength, final ContainerResponse responseContext)
+            throws ContainerException {
+        this.responseContext.complete(responseContext);
+
+        // first set the content length, so that if headers have an explicit value, it takes precedence over this one
+        if (responseContext.hasEntity() && contentLength != -1 && contentLength < Integer.MAX_VALUE) {
+            response.setContentLength((int) contentLength);
+        }
+        // Note that the writing of headers MUST be performed before
+        // the invocation of sendError as on some Servlet implementations
+        // modification of the response headers will have no effect
+        // after the invocation of sendError.
+        final MultivaluedMap<String, String> headers = getResponseContext().getStringHeaders();
+        for (final Map.Entry<String, List<String>> e : headers.entrySet()) {
+            final Iterator<String> it = e.getValue().iterator();
+            if (!it.hasNext()) {
+                continue;
+            }
+            final String header = e.getKey();
+            if (response.containsHeader(header)) {
+                // replace any headers previously set with values from Jersey container response.
+                response.setHeader(header, it.next());
+            }
+
+            while (it.hasNext()) {
+                response.addHeader(header, it.next());
+            }
+        }
+
+        final String reasonPhrase = responseContext.getStatusInfo().getReasonPhrase();
+        if (reasonPhrase != null) {
+            response.setStatus(responseContext.getStatus(), reasonPhrase);
+        } else {
+            response.setStatus(responseContext.getStatus());
+        }
+
+        if (!responseContext.hasEntity()) {
+            return null;
+        } else {
+            try {
+                final OutputStream outputStream = response.getOutputStream();
+
+                // delegating output stream prevents closing the underlying servlet output stream,
+                // so that any Servlet filters in the chain can still write to the response after us.
+                return new NonCloseableOutputStreamWrapper(outputStream);
+            } catch (final IOException e) {
+                throw new ContainerException(e);
+            }
+        }
+    }
+
+    @Override
+    public void commit() {
+        try {
+            callSendError();
+        } finally {
+            requestTimeoutHandler.close();
+            asyncExt.complete();
+        }
+    }
+
+    /**
+     * According to configuration and response processing status it calls {@link HttpServletResponse#sendError(int, String)} over
+     * common {@link HttpServletResponse#setStatus(int)}.
+     */
+    private void callSendError() {
+        // call HttpServletResponse.sendError in case:
+        // - property ServerProperties#RESPONSE_SET_STATUS_OVER_SEND_ERROR == false (default)
+        // - response NOT yet committed
+        // - response entity NOT set
+        // - response status code is 4xx or 5xx
+        // plus in case of Jersey as a Filter (JaaF):
+        // - response status code is 404 (Not Found)
+        // - property ServletProperties#FILTER_FORWARD_ON_404 == false (default)
+        if (!configSetStatusOverSendError && !response.isCommitted()) {
+            final ContainerResponse responseContext = getResponseContext();
+            final boolean hasEntity = responseContext.hasEntity();
+            final Response.StatusType status = responseContext.getStatusInfo();
+            if (!hasEntity && status != null && status.getStatusCode() >= 400
+                && !(useSetStatusOn404 && status == Response.Status.NOT_FOUND)) {
+                final String reason = status.getReasonPhrase();
+                try {
+                    if (reason == null || reason.isEmpty()) {
+                        response.sendError(status.getStatusCode());
+                    } else {
+                        response.sendError(status.getStatusCode(), reason);
+                    }
+                } catch (final IOException ex) {
+                    throw new ContainerException(
+                            LocalizationMessages.EXCEPTION_SENDING_ERROR_RESPONSE(status, reason != null ? reason : "--"),
+                            ex);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void failure(final Throwable error) {
+        try {
+            if (!response.isCommitted()) {
+                try {
+                    if (configSetStatusOverSendError) {
+                        response.reset();
+                        //noinspection deprecation
+                        response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), "Request failed.");
+                    } else {
+                        response.sendError(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), "Request failed.");
+                    }
+                } catch (final IllegalStateException ex) {
+                    // a race condition externally committing the response can still occur...
+                    LOGGER.log(Level.FINER, "Unable to reset failed response.", ex);
+                } catch (final IOException ex) {
+                    throw new ContainerException(LocalizationMessages.EXCEPTION_SENDING_ERROR_RESPONSE(
+                            Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), "Request failed."), ex);
+                } finally {
+                    asyncExt.complete();
+                }
+            }
+        } finally {
+            requestTimeoutHandler.close();
+            rethrow(error);
+        }
+    }
+
+    @Override
+    public boolean enableResponseBuffering() {
+        return true;
+    }
+
+    /**
+     * Rethrow the original exception as required by JAX-RS, 3.3.4
+     *
+     * @param error throwable to be re-thrown
+     */
+    private void rethrow(final Throwable error) {
+        if (error instanceof RuntimeException) {
+            throw (RuntimeException) error;
+        } else {
+            throw new ContainerException(error);
+        }
+    }
+
+    /**
+     * Provides response status captured when
+     * {@link #writeResponseStatusAndHeaders(long, org.glassfish.jersey.server.ContainerResponse)} has been invoked.
+     * The method will block if the write method has not been called yet.
+     *
+     * @return response status
+     */
+    public int getResponseStatus() {
+        return getResponseContext().getStatus();
+    }
+
+    public boolean responseContextResolved() {
+        return responseContext.isDone();
+    }
+
+    public ContainerResponse getResponseContext() {
+        try {
+            return responseContext.get();
+        } catch (InterruptedException | ExecutionException ex) {
+            throw new ContainerException(ex);
+        }
+    }
+
+    private static class NonCloseableOutputStreamWrapper extends OutputStream {
+
+        private final OutputStream delegate;
+
+        public NonCloseableOutputStreamWrapper(final OutputStream delegate) {
+            this.delegate = delegate;
+        }
+
+        @Override
+        public void write(final int b) throws IOException {
+            delegate.write(b);
+        }
+
+        @Override
+        public void write(final byte[] b) throws IOException {
+            delegate.write(b);
+        }
+
+        @Override
+        public void write(final byte[] b, final int off, final int len) throws IOException {
+            delegate.write(b, off, len);
+        }
+
+        @Override
+        public void flush() throws IOException {
+            delegate.flush();
+        }
+
+        @Override
+        public void close() throws IOException {
+            // do not close - let the servlet container close the stream
+        }
+    }
+}
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/ServletContainerProviderFactory.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/ServletContainerProviderFactory.java
new file mode 100644
index 0000000..8dbeb80
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/ServletContainerProviderFactory.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.servlet.internal;
+
+import org.glassfish.jersey.internal.ServiceFinder;
+import org.glassfish.jersey.servlet.internal.spi.ServletContainerProvider;
+
+/**
+ * Factory class to get all "registered" implementations of {@link ServletContainerProvider}.
+ * Mentioned implementation classes are registered by {@code META-INF/services}.
+ *
+ * @author Libor Kramolis (libor.kramolis at oracle.com)
+ * @since 2.1
+ */
+public final class ServletContainerProviderFactory {
+
+    private ServletContainerProviderFactory() {
+    }
+
+    /**
+     * Returns array of all "registered" implementations of {@link ServletContainerProvider}.
+     *
+     * @return Array of registered providers. Never returns {@code null}.
+     *         If there is no implementation registered empty array is returned.
+     */
+    public static ServletContainerProvider[] getAllServletContainerProviders() {
+        // TODO Instances of ServletContainerProvider could be cached, maybe. ???
+        // TODO Check if META-INF/services lookup is enabled. ???
+        return ServiceFinder.find(ServletContainerProvider.class).toArray();
+    }
+
+}
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/ThreadLocalInvoker.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/ThreadLocalInvoker.java
new file mode 100644
index 0000000..9ef4724
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/ThreadLocalInvoker.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.servlet.internal;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * A proxy invocation handler that delegates all methods to a thread local instance.
+ *
+ * @author Paul Sandoz
+ */
+public class ThreadLocalInvoker<T> implements InvocationHandler {
+
+    private ThreadLocal<T> threadLocalInstance = new ThreadLocal<>();
+
+    public void set(final T threadLocalInstance) {
+        this.threadLocalInstance.set(threadLocalInstance);
+    }
+
+    public T get() {
+        return this.threadLocalInstance.get();
+    }
+
+    @Override
+    public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
+        if (threadLocalInstance.get() == null) {
+            throw new IllegalStateException(LocalizationMessages.PERSISTENCE_UNIT_NOT_CONFIGURED(proxy.getClass()));
+        }
+
+        try {
+            return method.invoke(threadLocalInstance.get(), args);
+        } catch (final IllegalAccessException ex) {
+            throw new IllegalStateException(ex);
+        } catch (final InvocationTargetException ex) {
+            throw ex.getTargetException();
+        }
+    }
+}
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/ThreadLocalNamedInvoker.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/ThreadLocalNamedInvoker.java
new file mode 100644
index 0000000..f8acc93
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/ThreadLocalNamedInvoker.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.servlet.internal;
+
+import java.lang.reflect.Method;
+
+import javax.naming.Context;
+import javax.naming.InitialContext;
+
+/**
+ * A proxy invocation handler that delegates all methods to a thread local instance from JNDI.
+ *
+ * @author Paul Sandoz
+ */
+public class ThreadLocalNamedInvoker<T> extends ThreadLocalInvoker<T> {
+
+    private final String name;
+
+    /**
+     * Create an instance.
+     *
+     * @param name the JNDI name at which an instance of T can be found.
+     */
+    public ThreadLocalNamedInvoker(final String name) {
+        this.name = name;
+    }
+
+    @Override
+    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+        // if no instance yet exists for the current thread then look one up and stash it
+        if (this.get() == null) {
+            Context ctx = new InitialContext();
+            T t = (T) ctx.lookup(name);
+            this.set(t);
+        }
+        return super.invoke(proxy, method, args);
+    }
+}
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/Utils.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/Utils.java
new file mode 100644
index 0000000..c125116
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/Utils.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.servlet.internal;
+
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.ServletContext;
+
+import org.glassfish.jersey.server.ResourceConfig;
+
+/**
+ * Utility class.
+ *
+ * @author Michal Gajdos
+ */
+public final class Utils {
+
+    /**
+     * Internal {@link javax.servlet.ServletContext servlet context} attribute name under which an instance of
+     * {@link org.glassfish.jersey.server.ResourceConfig resource config} can be stored. The instance is later used to initialize
+     * servlet in {@link org.glassfish.jersey.servlet.WebConfig} instead of creating a new one.
+     */
+    private static final String RESOURCE_CONFIG = "jersey.config.servlet.internal.resourceConfig";
+
+    /**
+     * Store {@link org.glassfish.jersey.server.ResourceConfig resource config} as an attribute of given
+     * {@link javax.servlet.ServletContext servlet context}. If {@code config} is {@code null} then the previously stored value
+     * (if any) is removed. The {@code configName} is used as an attribute name suffix.
+     *
+     * @param config resource config to be stored.
+     * @param context servlet context to store the config in.
+     * @param configName name or id of the resource config.
+     */
+    public static void store(final ResourceConfig config, final ServletContext context, final String configName) {
+        final String attributeName = RESOURCE_CONFIG + "_" + configName;
+        context.setAttribute(attributeName, config);
+    }
+
+    /**
+     * Load {@link org.glassfish.jersey.server.ResourceConfig resource config} from given
+     * {@link javax.servlet.ServletContext servlet context}. If found then the resource config is also removed from servlet
+     * context. The {@code configName} is used as an attribute name suffix.
+     *
+     * @param context servlet context to load resource config from.
+     * @param configName name or id of the resource config.
+     * @return previously stored resource config or {@code null} if no resource config has been stored.
+     */
+    public static ResourceConfig retrieve(final ServletContext context, final String configName) {
+        final String attributeName = RESOURCE_CONFIG + "_" + configName;
+        final ResourceConfig config = (ResourceConfig) context.getAttribute(attributeName);
+        context.removeAttribute(attributeName);
+        return config;
+    }
+
+    /**
+     * Extract context params from {@link ServletContext}.
+     *
+     * @param servletContext actual servlet context.
+     * @return map representing current context parameters.
+     */
+    public static Map<String, Object> getContextParams(final ServletContext servletContext) {
+        final Map<String, Object> props = new HashMap<>();
+        final Enumeration names = servletContext.getAttributeNames();
+        while (names.hasMoreElements()) {
+            final String name = (String) names.nextElement();
+            props.put(name, servletContext.getAttribute(name));
+        }
+        return props;
+    }
+
+    /**
+     * Prevents instantiation.
+     */
+    private Utils() {
+    }
+}
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/package-info.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/package-info.java
new file mode 100644
index 0000000..6f5d32e
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+/**
+ * Jersey internal Servlet API.
+ */
+package org.glassfish.jersey.servlet.internal;
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/spi/ExtendedServletContainerProvider.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/spi/ExtendedServletContainerProvider.java
new file mode 100644
index 0000000..f9fa19b
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/spi/ExtendedServletContainerProvider.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.servlet.internal.spi;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.spi.RequestScopedInitializer;
+
+/**
+ * Implementations could provide their own {@link HttpServletRequest} and {@link HttpServletResponse}
+ * binding implementation in HK2 locator and also an implementation of {@link RequestScopedInitializer}
+ * that is used to set actual request/response references in injection manager within each request.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ * @since 2.21
+ */
+public interface ExtendedServletContainerProvider extends ServletContainerProvider {
+
+    /**
+     * Give me a {@link RequestScopedInitializerProvider} instance, that will be utilized
+     * at runtime to set the actual HTTP Servlet request and response.
+     *
+     * The provider returned will be used at runtime for every and each incoming request
+     * so that the actual request/response instances could be made accessible
+     * from Jersey injection manager.
+     *
+     * @return request scoped initializer provider.
+     */
+    public RequestScopedInitializerProvider getRequestScopedInitializerProvider();
+
+    /**
+     * Used by Jersey runtime to tell if the extension covers HTTP Servlet request response
+     * handling with respect to underlying injection manager.
+     *
+     * Return {@code true}, if your implementation configures HK2 bindings
+     * for {@link HttpServletRequest} and {@link HttpServletResponse}
+     * in {@link #configure(ResourceConfig)} method
+     * and also provides a {@link RequestScopedInitializer} implementation
+     * via {@link #getRequestScopedInitializerProvider()}.
+     *
+     * @return {@code true} if the extension fully covers HTTP request/response handling.
+     */
+    public boolean bindsServletRequestResponse();
+}
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/spi/NoOpServletContainerProvider.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/spi/NoOpServletContainerProvider.java
new file mode 100644
index 0000000..d4147d8
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/spi/NoOpServletContainerProvider.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.servlet.internal.spi;
+
+import java.lang.reflect.Type;
+import java.util.Set;
+
+import javax.ws.rs.core.GenericType;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.glassfish.jersey.internal.util.collection.Ref;
+import org.glassfish.jersey.server.ResourceConfig;
+
+/**
+ * Basic {@link ExtendedServletContainerProvider} that provides
+ * dummy no-op method implementation. It should be convenient to extend if you only need to implement
+ * a subset of the original SPI methods.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public class NoOpServletContainerProvider implements ExtendedServletContainerProvider {
+
+    public final Type HTTP_SERVLET_REQUEST_TYPE = (new GenericType<Ref<HttpServletRequest>>() { }).getType();
+    public final Type HTTP_SERVLET_RESPONSE_TYPE = (new GenericType<Ref<HttpServletResponse>>() { }).getType();
+
+    @Override
+    public void preInit(final ServletContext servletContext, final Set<Class<?>> classes) throws ServletException {
+        // no-op
+    }
+
+    @Override
+    public void postInit(
+            final ServletContext servletContext, final Set<Class<?>> classes, final Set<String> servletNames) {
+        // no-op
+    }
+
+    @Override
+    public void onRegister(
+            final ServletContext servletContext, final Set<String> servletNames) throws ServletException {
+        // no-op
+    }
+
+    @Override
+    public void configure(final ResourceConfig resourceConfig) throws ServletException {
+        // no-op
+    }
+
+    @Override
+    public boolean bindsServletRequestResponse() {
+        return false;
+    }
+
+    @Override
+    public RequestScopedInitializerProvider getRequestScopedInitializerProvider() {
+        return null;
+    }
+}
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/spi/RequestContextProvider.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/spi/RequestContextProvider.java
new file mode 100644
index 0000000..49049f2
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/spi/RequestContextProvider.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.servlet.internal.spi;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Provide access to the actual servlet request/response.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ * @see {@link RequestScopedInitializerProvider}
+ */
+public interface RequestContextProvider {
+
+    /**
+     * Get me the actual HTTP Servlet request.
+     *
+     * @return actual HTTP Servlet request.
+     */
+    public HttpServletRequest getHttpServletRequest();
+
+    /**
+     * Get me the actual HTTP Servlet response.
+     *
+     * @return actual HTTP Servlet response.
+     */
+    public HttpServletResponse getHttpServletResponse();
+}
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/spi/RequestScopedInitializerProvider.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/spi/RequestScopedInitializerProvider.java
new file mode 100644
index 0000000..ea18626
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/spi/RequestScopedInitializerProvider.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.servlet.internal.spi;
+
+import org.glassfish.jersey.server.spi.RequestScopedInitializer;
+
+/**
+ * Produces {@link RequestScopedInitializer}
+ * based on provided {@link RequestContextProvider}.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public interface RequestScopedInitializerProvider {
+
+    /**
+     * Give me a request scope initializer that could be utilized
+     * to set the actual Servlet request data in injection manager.
+     *
+     * @param context of the actual request.
+     * @return initializer to be invoked at the start of request processing.
+     */
+    public RequestScopedInitializer get(RequestContextProvider context);
+}
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/spi/ServletContainerProvider.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/spi/ServletContainerProvider.java
new file mode 100644
index 0000000..2e9b697
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/spi/ServletContainerProvider.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.servlet.internal.spi;
+
+import java.util.Set;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.spi.RequestScopedInitializer;
+import org.glassfish.jersey.servlet.ServletContainer;
+
+/**
+ * This is internal Jersey SPI to hook to Jersey servlet initialization process driven by
+ * {@code org.glassfish.jersey.servlet.init.JerseyServletContainerInitializer}.
+ * The provider implementation class is registered via {@code META-INF/services}.
+ *
+ *
+ * @author Libor Kramolis (libor.kramolis at oracle.com)
+ * @since 2.4.1
+ */
+public interface ServletContainerProvider {
+
+    /**
+     * Do your pre-initialization job before Jersey starts its servlet initialization.
+     *
+     * It is allowed to configure {@link ServletContext} or add/remove servlet registrations.
+     * Parameter {@code servletNames} contains list of names of currently registered Jersey servlets.
+     *
+     * @param servletContext the {@code ServletContext} of the JAX-RS/Jersey web application that is being started.
+     * @param classes        the mutable Set of application classes that extend {@link javax.ws.rs.core.Application},
+     *                       implement, or have been annotated with the class types {@link javax.ws.rs.Path},
+     *                       {@link javax.ws.rs.ext.Provider} or {@link javax.ws.rs.ApplicationPath}.
+     *                       May be empty, never {@code null}.
+     * @throws ServletException if an error has occurred. {@code javax.servlet.ServletContainerInitializer.onStartup}
+     *                          is interrupted.
+     */
+    public void preInit(ServletContext servletContext, Set<Class<?>> classes) throws ServletException;
+
+    /**
+     * Do your post-initialization job after Jersey finished its servlet initialization.
+     *
+     * It is allowed to configure {@link ServletContext} or add/remove servlet registrations.
+     * Parameter {@code servletNames} contains list of names of currently registered Jersey servlets.
+     *
+     * @param servletContext the {@code ServletContext} of the JAX-RS/Jersey web application that is being started.
+     * @param classes        the mutable Set of application classes that extend {@link javax.ws.rs.core.Application},
+     *                       implement, or have been annotated with the class types {@link javax.ws.rs.Path},
+     *                       {@link javax.ws.rs.ext.Provider} or {@link javax.ws.rs.ApplicationPath}.
+     *                       May be empty, never {@code null}.
+     * @param servletNames   the Immutable set of Jersey's ServletContainer names. May be empty, never {@code null}.
+     * @throws ServletException if an error has occurred. {@code javax.servlet.ServletContainerInitializer.onStartup}
+     *                          is interrupted.
+     */
+    public void postInit(ServletContext servletContext, Set<Class<?>> classes, final Set<String> servletNames)
+            throws ServletException;
+
+    /**
+     * Notifies the provider about all registered Jersey servlets by its names.
+     *
+     * It is allowed to configure {@link ServletContext}. Do not add/remove any servlet registrations here.
+     *
+     * Parameter {@code servletNames} contains list of names of registered Jersey servlets.
+     * Currently it is {@link ServletContainer} or
+     * {@code org.glassfish.jersey.servlet.portability.PortableServletContainer} servlets.
+     *
+     * It does not matter servlet container is configured in {@code web.xml},
+     * by {@code org.glassfish.jersey.servlet.init.JerseyServletContainerInitializer}
+     * or by customer direct Servlet API calls.
+     *
+     * @param servletContext the {@code ServletContext} of the JAX-RS/Jersey web application that is being started.
+     * @param servletNames   the Immutable set of Jersey's ServletContainer names. May be empty, never {@code null}.
+     * @throws ServletException if an error has occurred. {@code javax.servlet.ServletContainerInitializer.onStartup}
+     *                          is interrupted.
+     */
+    public void onRegister(ServletContext servletContext, final Set<String> servletNames) throws ServletException;
+
+    /**
+     * This method is called for each {@link ServletContainer} instance initialization,
+     * i.e. during {@link org.glassfish.jersey.servlet.WebComponent} initialization.
+     *
+     * The method is also called during {@link ServletContainer#reload()} or
+     * {@link ServletContainer#reload(ResourceConfig)} methods invocation.
+     *
+     * It does not matter servlet container is configured in {@code web.xml},
+     * by {@code org.glassfish.jersey.servlet.init.JerseyServletContainerInitializer}
+     * or by customer direct Servlet API calls.
+     *
+     * @param resourceConfig Jersey application configuration.
+     * @throws ServletException if an error has occurred. {@code org.glassfish.jersey.servlet.WebComponent} construction
+     *                          is interrupted.
+     */
+    public void configure(ResourceConfig resourceConfig) throws ServletException;
+}
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/spi/package-info.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/spi/package-info.java
new file mode 100644
index 0000000..e0875ca
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/internal/spi/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+/**
+ * Jersey internal Servlet SPI.
+ */
+package org.glassfish.jersey.servlet.internal.spi;
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/package-info.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/package-info.java
new file mode 100644
index 0000000..8e99e29
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+/**
+ * Jersey generic Servlet container integration classes.
+ */
+package org.glassfish.jersey.servlet;
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/spi/AsyncContextDelegate.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/spi/AsyncContextDelegate.java
new file mode 100644
index 0000000..99a02a8
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/spi/AsyncContextDelegate.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.servlet.spi;
+
+import org.glassfish.jersey.server.spi.ContainerResponseWriter;
+
+/**
+ * Utilized by the Servlet container response writer to deal with the container async features.
+ * Individual instances are created by {@link AsyncContextDelegateProvider}.
+ *
+ * @see AsyncContextDelegateProvider
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public interface AsyncContextDelegate {
+
+    /**
+     * Invoked by the superior {@link ContainerResponseWriter} responsible for writing the response when processing is to be
+     * suspended. An implementation can throw an {@link UnsupportedOperationException} if suspend is not supported (the default
+     * behavior).
+     *
+     * @see ContainerResponseWriter#suspend(long, java.util.concurrent.TimeUnit, org.glassfish.jersey.server.spi.ContainerResponseWriter.TimeoutHandler)
+     * @throws IllegalStateException if underlying {@link javax.servlet.ServletRequest servlet request} throws an exception.
+     */
+    public void suspend() throws IllegalStateException;
+
+    /**
+     * Invoked upon a response writing completion when the response write is either committed or canceled.
+     */
+    public void complete();
+}
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/spi/AsyncContextDelegateProvider.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/spi/AsyncContextDelegateProvider.java
new file mode 100644
index 0000000..8503ff1
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/spi/AsyncContextDelegateProvider.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.servlet.spi;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Factory to create {@link AsyncContextDelegate} to deal with asynchronous
+ * features added in Servlet version 3.0.
+ * If no such a factory is registered via the {@code META-INF/services} mechanism
+ * a default factory for Servlet versions prior to 3.0 will be utilized with no async support.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public interface AsyncContextDelegateProvider {
+
+    /**
+     * Factory method to create instances of Servlet container response writer extension,
+     * {@link AsyncContextDelegate}, for response processing.
+     *
+     * @param request original request.
+     * @param response original response.
+     * @return an instance to be used throughout a single response write processing.
+     */
+    public AsyncContextDelegate createDelegate(final HttpServletRequest request, final HttpServletResponse response);
+}
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/spi/FilterUrlMappingsProvider.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/spi/FilterUrlMappingsProvider.java
new file mode 100644
index 0000000..a68b5ea
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/spi/FilterUrlMappingsProvider.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.servlet.spi;
+
+import javax.servlet.FilterConfig;
+import java.util.List;
+
+/**
+ * Provides an access to context path from the filter configuration.
+ *
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ */
+public interface FilterUrlMappingsProvider {
+
+    /**
+     * Return configured context path from the filter configuration.
+     *
+     * @param filterConfig the {@link FilterConfig} object
+     * @returns the {@code List} of url-patterns
+     */
+    List<String> getFilterUrlMappings(final FilterConfig filterConfig);
+}
diff --git a/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/spi/package-info.java b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/spi/package-info.java
new file mode 100644
index 0000000..c8a1825
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/java/org/glassfish/jersey/servlet/spi/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+/**
+ * SPI for Jersey generic Servlet container support.
+ */
+package org.glassfish.jersey.servlet.spi;
diff --git a/containers/jersey-servlet-core/src/main/resources/org/glassfish/jersey/servlet/internal/localization.properties b/containers/jersey-servlet-core/src/main/resources/org/glassfish/jersey/servlet/internal/localization.properties
new file mode 100644
index 0000000..4250a68
--- /dev/null
+++ b/containers/jersey-servlet-core/src/main/resources/org/glassfish/jersey/servlet/internal/localization.properties
@@ -0,0 +1,34 @@
+#
+# Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Eclipse Public License v. 2.0, which is available at
+# http://www.eclipse.org/legal/epl-2.0.
+#
+# This Source Code may also be made available under the following Secondary
+# Licenses when the conditions for such availability set forth in the
+# Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+# version 2 with the GNU Classpath Exception, which is available at
+# https://www.gnu.org/software/classpath/license.html.
+#
+# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+#
+
+async.processing.not.supported=Asynchronous processing not supported on Servlet 2.x container.
+# {0} - status code; {1} - status reason message
+exception.sending.error.response=I/O exception occurred while sending "{0}/{1}" error response.
+form.param.consumed=A servlet request to the URI {0} contains form parameters in the request body but the request body has been consumed by the servlet or a servlet filter accessing the request parameters. Only resource methods using @FormParam will work as expected. Resource methods consuming the request body by other means will not work as expected.
+init.param.regex.syntax.invalid=The syntax is invalid for the regular expression "{0}" associated with the initialization parameter "{1}".
+# {0} - name (e.g. 'BookmarkPU')
+persistence.unit.not.configured=Persistence unit "{0}" is not configured as a servlet parameter in web.xml.
+# {0} - class name
+no.thread.local.value=No thread local value in scope for proxy of {0}.
+resource.config.parent.class.invalid=Resource configuration class {0} is not a subclass of {1}.
+resource.config.unable.to.load=Resource configuration class {0} could not be loaded.
+servlet.path.mismatch=The servlet path {0} does not start with the filter context path {1}.
+servlet.request.suspend.failed=Attempt to put servlet request into asynchronous mode has failed. Please check your servlet configuration \
+  - all Servlet instances and Servlet filters involved in the request processing must explicitly declare support for asynchronous request processing.
+header.value.read.failed=Attempt to read the header value failed.
+filter.context.path.missing=The root of the app was not properly defined. Either use a Servlet 3.x container or add \
+  an init-param 'jersey.config.servlet.filter.contextPath' to the filter configuration. Due to Servlet 2.x API, Jersey cannot \
+  determine the request base URI solely from the ServletContext. The application will most likely not work.
diff --git a/containers/jersey-servlet-core/src/test/java/org/glassfish/jersey/servlet/internal/ThreadLocalInvokerTest.java b/containers/jersey-servlet-core/src/test/java/org/glassfish/jersey/servlet/internal/ThreadLocalInvokerTest.java
new file mode 100644
index 0000000..b4476fc
--- /dev/null
+++ b/containers/jersey-servlet-core/src/test/java/org/glassfish/jersey/servlet/internal/ThreadLocalInvokerTest.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.servlet.internal;
+
+import java.lang.reflect.Proxy;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author Michal Gajdos
+ */
+public class ThreadLocalInvokerTest {
+
+    public static class CheckedException extends Exception {
+
+    }
+
+    public static interface X {
+
+        public String checked() throws CheckedException;
+
+        public String runtime();
+    }
+
+    @Test
+    public void testIllegalState() {
+        final ThreadLocalInvoker<X> tli = new ThreadLocalInvoker<>();
+
+        final X x = (X) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{X.class}, tli);
+
+        boolean caught = false;
+        try {
+            x.checked();
+        } catch (final Exception ex) {
+            caught = true;
+            assertEquals(IllegalStateException.class, ex.getClass());
+        }
+        assertTrue(caught);
+
+        caught = false;
+        try {
+            x.runtime();
+        } catch (final Exception ex) {
+            caught = true;
+            assertEquals(IllegalStateException.class, ex.getClass());
+        }
+        assertTrue(caught);
+    }
+
+    @Test
+    public void testExceptions() {
+        final ThreadLocalInvoker<X> tli = new ThreadLocalInvoker<>();
+
+        final X x = (X) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{X.class}, tli);
+
+        tli.set(new X() {
+            public String checked() throws CheckedException {
+                throw new CheckedException();
+            }
+
+            public String runtime() {
+                throw new RuntimeException();
+            }
+        });
+
+        boolean caught = false;
+        try {
+            x.checked();
+        } catch (final Exception ex) {
+            caught = true;
+            assertEquals(CheckedException.class, ex.getClass());
+        }
+        assertTrue(caught);
+
+        caught = false;
+        try {
+            x.runtime();
+        } catch (final Exception ex) {
+            caught = true;
+            assertEquals(RuntimeException.class, ex.getClass());
+        }
+        assertTrue(caught);
+    }
+}
diff --git a/containers/jersey-servlet/pom.xml b/containers/jersey-servlet/pom.xml
new file mode 100644
index 0000000..a9a6634
--- /dev/null
+++ b/containers/jersey-servlet/pom.xml
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.glassfish.jersey.containers</groupId>
+        <artifactId>project</artifactId>
+        <version>2.28-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>jersey-container-servlet</artifactId>
+    <packaging>jar</packaging>
+    <name>jersey-container-servlet</name>
+
+    <description>Jersey core Servlet 3.x implementation</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>javax.servlet-api</artifactId>
+            <version>${servlet3.version}</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.glassfish.jersey.containers</groupId>
+            <artifactId>jersey-container-servlet-core</artifactId>
+            <version>${project.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>javax.servlet</groupId>
+                    <artifactId>servlet-api</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>com.sun.istack</groupId>
+                <artifactId>maven-istack-commons-plugin</artifactId>
+                <inherited>true</inherited>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>build-helper-maven-plugin</artifactId>
+                <inherited>true</inherited>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <inherited>true</inherited>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <!-- Note: When you're changing these properties change them also in bundles/jax-rs-ri/bundle/pom.xml. -->
+                        <Import-Package>
+                            javax.servlet.*;version="[3.0,5.0)",
+                            javax.annotation.*;version=!,
+                            *
+                        </Import-Package>
+                    </instructions>
+                    <unpackBundle>true</unpackBundle>
+                </configuration>
+             </plugin>
+         </plugins>
+    </build>
+
+</project>
diff --git a/containers/jersey-servlet/src/main/java/org/glassfish/jersey/servlet/async/AsyncContextDelegateProviderImpl.java b/containers/jersey-servlet/src/main/java/org/glassfish/jersey/servlet/async/AsyncContextDelegateProviderImpl.java
new file mode 100644
index 0000000..25ad283
--- /dev/null
+++ b/containers/jersey-servlet/src/main/java/org/glassfish/jersey/servlet/async/AsyncContextDelegateProviderImpl.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.servlet.async;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.glassfish.jersey.servlet.init.internal.LocalizationMessages;
+import org.glassfish.jersey.servlet.spi.AsyncContextDelegate;
+import org.glassfish.jersey.servlet.spi.AsyncContextDelegateProvider;
+
+/**
+ * Servlet 3.x container response writer async extension and related extension factory implementation.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class AsyncContextDelegateProviderImpl implements AsyncContextDelegateProvider {
+
+    private static final Logger LOGGER = Logger.getLogger(AsyncContextDelegateProviderImpl.class.getName());
+
+    @Override
+    public final AsyncContextDelegate createDelegate(final HttpServletRequest request, final HttpServletResponse response) {
+        return new ExtensionImpl(request, response);
+    }
+
+    private static final class ExtensionImpl implements AsyncContextDelegate {
+
+        private static final int NEVER_TIMEOUT_VALUE = -1;
+
+        private final HttpServletRequest request;
+        private final HttpServletResponse response;
+        private final AtomicReference<AsyncContext> asyncContextRef;
+        private final AtomicBoolean completed;
+
+        /**
+         * Create a Servlet 3.x {@link AsyncContextDelegate} with given {@code request} and {@code response}.
+         *
+         * @param request  request to create {@link AsyncContext} for.
+         * @param response response to create {@link AsyncContext} for.
+         */
+        private ExtensionImpl(final HttpServletRequest request, final HttpServletResponse response) {
+            this.request = request;
+            this.response = response;
+            this.asyncContextRef = new AtomicReference<>();
+            this.completed = new AtomicBoolean(false);
+        }
+
+        @Override
+        public void suspend() throws IllegalStateException {
+            // Suspend only if not completed and not suspended before.
+            if (!completed.get() && asyncContextRef.get() == null) {
+                asyncContextRef.set(getAsyncContext());
+            }
+        }
+
+        private AsyncContext getAsyncContext() {
+            final AsyncContext asyncContext;
+            if (request.isAsyncStarted()) {
+                asyncContext = request.getAsyncContext();
+                try {
+                    asyncContext.setTimeout(NEVER_TIMEOUT_VALUE);
+                } catch (IllegalStateException ex) {
+                    // Let's hope the time out is set properly, otherwise JAX-RS AsyncResponse time-out support
+                    // may not work as expected... At least we can log this at fine level...
+                    LOGGER.log(Level.FINE, LocalizationMessages.SERVLET_ASYNC_CONTEXT_ALREADY_STARTED(), ex);
+                }
+            } else {
+                asyncContext = request.startAsync(request, response);
+                // Tell underlying asyncContext to never time out.
+                asyncContext.setTimeout(NEVER_TIMEOUT_VALUE);
+            }
+            return asyncContext;
+        }
+
+        @Override
+        public void complete() {
+            completed.set(true);
+
+            final AsyncContext asyncContext = asyncContextRef.getAndSet(null);
+            if (asyncContext != null) {
+                asyncContext.complete();
+            }
+        }
+    }
+}
diff --git a/containers/jersey-servlet/src/main/java/org/glassfish/jersey/servlet/async/package-info.java b/containers/jersey-servlet/src/main/java/org/glassfish/jersey/servlet/async/package-info.java
new file mode 100644
index 0000000..0e02af9
--- /dev/null
+++ b/containers/jersey-servlet/src/main/java/org/glassfish/jersey/servlet/async/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+/**
+ * Jersey servlet container asynchronous support classes.
+ */
+package org.glassfish.jersey.servlet.async;
diff --git a/containers/jersey-servlet/src/main/java/org/glassfish/jersey/servlet/init/FilterUrlMappingsProviderImpl.java b/containers/jersey-servlet/src/main/java/org/glassfish/jersey/servlet/init/FilterUrlMappingsProviderImpl.java
new file mode 100644
index 0000000..e632f72
--- /dev/null
+++ b/containers/jersey-servlet/src/main/java/org/glassfish/jersey/servlet/init/FilterUrlMappingsProviderImpl.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.servlet.init;
+
+import org.glassfish.jersey.servlet.spi.FilterUrlMappingsProvider;
+
+import javax.servlet.FilterConfig;
+import javax.servlet.FilterRegistration;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Provide all configured context paths (url mappings) of the application deployed using filter.
+ * <p>
+ * The url patterns are returned without the eventual trailing asterisk.
+ * <p>
+ * The functionality is available in Servlet 3.x environment only, so this
+ * implementation of {@link FilterUrlMappingsProvider} interface is Servlet 3 specific.
+ *
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ */
+public class FilterUrlMappingsProviderImpl implements FilterUrlMappingsProvider {
+    @Override
+    public List<String> getFilterUrlMappings(FilterConfig filterConfig) {
+        FilterRegistration filterRegistration =
+          filterConfig.getServletContext().getFilterRegistration(filterConfig.getFilterName());
+
+        Collection<String> urlPatternMappings = filterRegistration.getUrlPatternMappings();
+        List<String> result = new ArrayList<>();
+
+        for (String pattern : urlPatternMappings) {
+            result.add(pattern.endsWith("*") ? pattern.substring(0, pattern.length() - 1) : pattern);
+        }
+
+        return result;
+    }
+}
diff --git a/containers/jersey-servlet/src/main/java/org/glassfish/jersey/servlet/init/JerseyServletContainerInitializer.java b/containers/jersey-servlet/src/main/java/org/glassfish/jersey/servlet/init/JerseyServletContainerInitializer.java
new file mode 100644
index 0000000..5d58730
--- /dev/null
+++ b/containers/jersey-servlet/src/main/java/org/glassfish/jersey/servlet/init/JerseyServletContainerInitializer.java
@@ -0,0 +1,378 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.servlet.init;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.ApplicationPath;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.ext.Provider;
+
+import javax.servlet.Registration;
+import javax.servlet.ServletContainerInitializer;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRegistration;
+import javax.servlet.annotation.HandlesTypes;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.servlet.ServletContainer;
+import org.glassfish.jersey.servlet.ServletProperties;
+import org.glassfish.jersey.servlet.init.internal.LocalizationMessages;
+import org.glassfish.jersey.servlet.internal.ServletContainerProviderFactory;
+import org.glassfish.jersey.servlet.internal.Utils;
+import org.glassfish.jersey.servlet.internal.spi.ServletContainerProvider;
+
+/*
+ It is RECOMMENDED that implementations support the Servlet 3 framework
+ pluggability mechanism to enable portability between containers and to avail
+ themselves of container-supplied class scanning facilities.
+ When using the pluggability mechanism the following conditions MUST be met:
+
+ - If no Application subclass is present the added servlet MUST be
+ named "javax.ws.rs.core.Application" and all root resource classes and
+ providers packaged in the web application MUST be included in the published
+ JAX-RS application. The application MUST be packaged with a web.xml that
+ specifies a servlet mapping for the added servlet.
+
+ - If an Application subclass is present and there is already a servlet defined
+ that has a servlet initialization parameter named "javax.ws.rs.Application"
+ whose value is the fully qualified name of the Application subclass then no
+ servlet should be added by the JAX-RS implementation's ContainerInitializer
+ since the application is already being handled by an existing servlet.
+
+ - If an application subclass is present that is not being handled by an
+ existing servlet then the servlet added by the ContainerInitializer MUST be
+ named with the fully qualified name of the Application subclass.  If the
+ Application subclass is annotated with @PathPrefix and no servlet-mapping
+ exists for the added servlet then a new servlet mapping is added with the
+ value of the @PathPrefix  annotation with "/*" appended otherwise the existing
+ mapping is used. If the Application subclass is not annotated with @PathPrefix
+ then the application MUST be packaged with a web.xml that specifies a servlet
+ mapping for the added servlet. It is an error for more than one Application
+ to be deployed at the same effective servlet mapping.
+
+ In either of the latter two cases, if both Application#getClasses and
+ Application#getSingletons return an empty list then all root resource classes
+ and providers packaged in the web application MUST be included in the
+ published JAX-RS application. If either getClasses or getSingletons return a
+ non-empty list then only those classes or singletons returned MUST be included
+ in the published JAX-RS application.
+
+ If not using the Servlet 3 framework pluggability mechanism
+ (e.g. in a pre-Servlet 3.0 container), the servlet-class or filter-class
+ element of the web.xml descriptor SHOULD name the JAX-RS
+ implementation-supplied Servlet or Filter class respectively. The
+ application-supplied subclass of Application SHOULD be identified using an
+ init-param with a param-name of javax.ws.rs.Application.
+ */
+
+/**
+ * {@link ServletContainerInitializer} implementation used for Servlet 3.x deployment.
+ *
+ * @author Paul Sandoz
+ * @author Martin Matula
+ * @author Libor Kramolis (libor.kramolis at oracle.com)
+ */
+@HandlesTypes({ Path.class, Provider.class, Application.class, ApplicationPath.class })
+public final class JerseyServletContainerInitializer implements ServletContainerInitializer {
+
+    private static final Logger LOGGER = Logger.getLogger(JerseyServletContainerInitializer.class.getName());
+
+    @Override
+    public void onStartup(Set<Class<?>> classes, final ServletContext servletContext) throws ServletException {
+        final ServletContainerProvider[] allServletContainerProviders =
+                ServletContainerProviderFactory.getAllServletContainerProviders();
+
+        if (classes == null) {
+            classes = Collections.emptySet();
+        }
+        // PRE INIT
+        for (final ServletContainerProvider servletContainerProvider : allServletContainerProviders) {
+            servletContainerProvider.preInit(servletContext, classes);
+        }
+        // INIT IMPL
+        onStartupImpl(classes, servletContext);
+        // POST INIT
+        for (final ServletContainerProvider servletContainerProvider : allServletContainerProviders) {
+            servletContainerProvider.postInit(servletContext, classes, findJerseyServletNames(servletContext));
+        }
+        // ON REGISTER
+        for (final ServletContainerProvider servletContainerProvider : allServletContainerProviders) {
+            servletContainerProvider.onRegister(servletContext, findJerseyServletNames(servletContext));
+        }
+    }
+
+    private void onStartupImpl(final Set<Class<?>> classes, final ServletContext servletContext) throws ServletException {
+        // first see if there are any application classes in the web app
+        for (final Class<? extends Application> applicationClass : getApplicationClasses(classes)) {
+            final ServletRegistration servletRegistration = servletContext.getServletRegistration(applicationClass.getName());
+
+            if (servletRegistration != null) {
+                addServletWithExistingRegistration(servletContext, servletRegistration, applicationClass, classes);
+            } else {
+                // Servlet is not registered with app name or the app name is used to register a different servlet
+                // check if some servlet defines the app in init params
+                final List<Registration> srs = getInitParamDeclaredRegistrations(servletContext, applicationClass);
+                if (!srs.isEmpty()) {
+                    // app handled by at least one servlet or filter
+                    // fix the registrations if needed (i.e. add servlet class)
+                    for (final Registration sr : srs) {
+                        if (sr instanceof ServletRegistration) {
+                            addServletWithExistingRegistration(servletContext, (ServletRegistration) sr,
+                                    applicationClass, classes);
+                        }
+                    }
+                } else {
+                    // app not handled by any servlet/filter -> add it
+                    addServletWithApplication(servletContext, applicationClass, classes);
+                }
+            }
+        }
+
+        // check for javax.ws.rs.core.Application registration
+        addServletWithDefaultConfiguration(servletContext, classes);
+    }
+
+    /**
+     * Returns names of all registered Jersey servlets.
+     *
+     * Servlets are configured in {@code web.xml} or managed via Servlet API.
+     *
+     * @param servletContext the {@link ServletContext} of the web application that is being started
+     * @return list of Jersey servlet names or empty array, never returns {@code null}
+     */
+    private static Set<String> findJerseyServletNames(final ServletContext servletContext) {
+        final Set<String> jerseyServletNames = new HashSet<>();
+
+        for (final ServletRegistration servletRegistration : servletContext.getServletRegistrations().values()) {
+            if (isJerseyServlet(servletRegistration.getClassName())) {
+                jerseyServletNames.add(servletRegistration.getName());
+            }
+        }
+        return Collections.unmodifiableSet(jerseyServletNames);
+    }
+
+    /**
+     * Check if the {@code className} is an implementation of a Jersey Servlet container.
+     *
+     * @return {@code true} if the class is a Jersey servlet container class, {@code false} otherwise.
+     */
+    private static boolean isJerseyServlet(final String className) {
+        return ServletContainer.class.getName().equals(className)
+                || "org.glassfish.jersey.servlet.portability.PortableServletContainer".equals(className);
+    }
+
+    private static List<Registration> getInitParamDeclaredRegistrations(final ServletContext context,
+                                                                        final Class<? extends Application> clazz) {
+        final List<Registration> registrations = new ArrayList<>();
+        collectJaxRsRegistrations(context.getServletRegistrations(), registrations, clazz);
+        collectJaxRsRegistrations(context.getFilterRegistrations(), registrations, clazz);
+        return registrations;
+    }
+
+    private static void collectJaxRsRegistrations(final Map<String, ? extends Registration> registrations,
+                                                  final List<Registration> collected, final Class<? extends Application> a) {
+        for (final Registration sr : registrations.values()) {
+            final Map<String, String> ips = sr.getInitParameters();
+            if (ips.containsKey(ServletProperties.JAXRS_APPLICATION_CLASS)) {
+                if (ips.get(ServletProperties.JAXRS_APPLICATION_CLASS).equals(a.getName())) {
+                    collected.add(sr);
+                }
+            }
+        }
+    }
+
+    /**
+     * Enhance default servlet (named {@link Application}) configuration.
+     */
+    private static void addServletWithDefaultConfiguration(final ServletContext context,
+                                                           final Set<Class<?>> classes) throws ServletException {
+
+        ServletRegistration registration = context.getServletRegistration(Application.class.getName());
+
+        if (registration != null) {
+            final Set<Class<?>> appClasses = getRootResourceAndProviderClasses(classes);
+            final ResourceConfig resourceConfig = ResourceConfig.forApplicationClass(ResourceConfig.class, appClasses)
+                    .addProperties(getInitParams(registration))
+                    .addProperties(Utils.getContextParams(context));
+
+            if (registration.getClassName() != null) {
+                // class name present - complete servlet registration from container point of view
+                Utils.store(resourceConfig, context, registration.getName());
+            } else {
+                // no class name - no complete servlet registration from container point of view
+                final ServletContainer servlet = new ServletContainer(resourceConfig);
+                registration = context.addServlet(registration.getName(), servlet);
+                ((ServletRegistration.Dynamic) registration).setLoadOnStartup(1);
+
+                if (registration.getMappings().isEmpty()) {
+                    // Error
+                    LOGGER.log(Level.WARNING, LocalizationMessages.JERSEY_APP_NO_MAPPING(registration.getName()));
+                } else {
+                    LOGGER.log(Level.CONFIG,
+                            LocalizationMessages.JERSEY_APP_REGISTERED_CLASSES(registration.getName(), appClasses));
+                }
+            }
+        }
+    }
+
+    /**
+     * Add new servlet according to {@link Application} subclass with {@link ApplicationPath} annotation or existing
+     * {@code servlet-mapping}.
+     */
+    private static void addServletWithApplication(final ServletContext context,
+                                                  final Class<? extends Application> clazz,
+                                                  final Set<Class<?>> defaultClasses) throws ServletException {
+        final ApplicationPath ap = clazz.getAnnotation(ApplicationPath.class);
+        if (ap != null) {
+            // App is annotated with ApplicationPath
+            final ResourceConfig resourceConfig = ResourceConfig.forApplicationClass(clazz, defaultClasses)
+                    .addProperties(Utils.getContextParams(context));
+            final ServletContainer s = new ServletContainer(resourceConfig);
+            final ServletRegistration.Dynamic dsr = context.addServlet(clazz.getName(), s);
+            dsr.setAsyncSupported(true);
+            dsr.setLoadOnStartup(1);
+
+            final String mapping = createMappingPath(ap);
+            if (!mappingExists(context, mapping)) {
+                dsr.addMapping(mapping);
+
+                LOGGER.log(Level.CONFIG, LocalizationMessages.JERSEY_APP_REGISTERED_MAPPING(clazz.getName(), mapping));
+            } else {
+                LOGGER.log(Level.WARNING, LocalizationMessages.JERSEY_APP_MAPPING_CONFLICT(clazz.getName(), mapping));
+            }
+        }
+    }
+
+    /**
+     * Enhance existing servlet configuration.
+     */
+    private static void addServletWithExistingRegistration(final ServletContext context,
+                                                           ServletRegistration registration,
+                                                           final Class<? extends Application> clazz,
+                                                           final Set<Class<?>> classes) throws ServletException {
+        // create a new servlet container for a given app.
+        final ResourceConfig resourceConfig = ResourceConfig.forApplicationClass(clazz, classes)
+                .addProperties(getInitParams(registration))
+                .addProperties(Utils.getContextParams(context));
+
+        if (registration.getClassName() != null) {
+            // class name present - complete servlet registration from container point of view
+            Utils.store(resourceConfig, context, registration.getName());
+        } else {
+            // no class name - no complete servlet registration from container point of view
+            final ServletContainer servlet = new ServletContainer(resourceConfig);
+            final ServletRegistration.Dynamic dynamicRegistration = context.addServlet(clazz.getName(), servlet);
+            dynamicRegistration.setAsyncSupported(true);
+            dynamicRegistration.setLoadOnStartup(1);
+            registration = dynamicRegistration;
+        }
+        if (registration.getMappings().isEmpty()) {
+            final ApplicationPath ap = clazz.getAnnotation(ApplicationPath.class);
+            if (ap != null) {
+                final String mapping = createMappingPath(ap);
+                if (!mappingExists(context, mapping)) {
+                    registration.addMapping(mapping);
+
+                    LOGGER.log(Level.CONFIG, LocalizationMessages.JERSEY_APP_REGISTERED_MAPPING(clazz.getName(), mapping));
+                } else {
+                    LOGGER.log(Level.WARNING, LocalizationMessages.JERSEY_APP_MAPPING_CONFLICT(clazz.getName(), mapping));
+                }
+            } else {
+                // Error
+                LOGGER.log(Level.WARNING, LocalizationMessages.JERSEY_APP_NO_MAPPING_OR_ANNOTATION(clazz.getName(),
+                        ApplicationPath.class.getSimpleName()));
+            }
+        } else {
+            LOGGER.log(Level.CONFIG, LocalizationMessages.JERSEY_APP_REGISTERED_APPLICATION(clazz.getName()));
+        }
+    }
+
+    private static Map<String, Object> getInitParams(final ServletRegistration sr) {
+        final Map<String, Object> initParams = new HashMap<>();
+        for (final Map.Entry<String, String> entry : sr.getInitParameters().entrySet()) {
+            initParams.put(entry.getKey(), entry.getValue());
+        }
+        return initParams;
+    }
+
+    private static boolean mappingExists(final ServletContext sc, final String mapping) {
+        for (final ServletRegistration sr : sc.getServletRegistrations().values()) {
+            for (final String declaredMapping : sr.getMappings()) {
+                if (mapping.equals(declaredMapping)) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+
+
+    private static String createMappingPath(final ApplicationPath ap) {
+        String path = ap.value();
+        if (!path.startsWith("/")) {
+            path = "/" + path;
+        }
+
+        if (!path.endsWith("/*")) {
+            if (path.endsWith("/")) {
+                path += "*";
+            } else {
+                path += "/*";
+            }
+        }
+
+        return path;
+    }
+
+    private static Set<Class<? extends Application>> getApplicationClasses(final Set<Class<?>> classes) {
+        final Set<Class<? extends Application>> s = new LinkedHashSet<>();
+        for (final Class<?> c : classes) {
+            if (Application.class != c && Application.class.isAssignableFrom(c)) {
+                s.add(c.asSubclass(Application.class));
+            }
+        }
+
+        return s;
+    }
+
+    private static Set<Class<?>> getRootResourceAndProviderClasses(final Set<Class<?>> classes) {
+        // TODO filter out any classes from the Jersey jars
+        final Set<Class<?>> s = new LinkedHashSet<>();
+        for (final Class<?> c : classes) {
+            if (c.isAnnotationPresent(Path.class) || c.isAnnotationPresent(Provider.class)) {
+                s.add(c);
+            }
+        }
+
+        return s;
+    }
+
+}
diff --git a/containers/jersey-servlet/src/main/java/org/glassfish/jersey/servlet/init/package-info.java b/containers/jersey-servlet/src/main/java/org/glassfish/jersey/servlet/init/package-info.java
new file mode 100644
index 0000000..c19811d
--- /dev/null
+++ b/containers/jersey-servlet/src/main/java/org/glassfish/jersey/servlet/init/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+/**
+ * Jersey servlet container initialization classes.
+ */
+package org.glassfish.jersey.servlet.init;
diff --git a/containers/jersey-servlet/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer b/containers/jersey-servlet/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer
new file mode 100644
index 0000000..4052e74
--- /dev/null
+++ b/containers/jersey-servlet/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer
@@ -0,0 +1 @@
+org.glassfish.jersey.servlet.init.JerseyServletContainerInitializer
\ No newline at end of file
diff --git a/containers/jersey-servlet/src/main/resources/META-INF/services/org.glassfish.jersey.servlet.spi.AsyncContextDelegateProvider b/containers/jersey-servlet/src/main/resources/META-INF/services/org.glassfish.jersey.servlet.spi.AsyncContextDelegateProvider
new file mode 100644
index 0000000..959ec7c
--- /dev/null
+++ b/containers/jersey-servlet/src/main/resources/META-INF/services/org.glassfish.jersey.servlet.spi.AsyncContextDelegateProvider
@@ -0,0 +1 @@
+org.glassfish.jersey.servlet.async.AsyncContextDelegateProviderImpl
\ No newline at end of file
diff --git a/containers/jersey-servlet/src/main/resources/META-INF/services/org.glassfish.jersey.servlet.spi.FilterUrlMappingsProvider b/containers/jersey-servlet/src/main/resources/META-INF/services/org.glassfish.jersey.servlet.spi.FilterUrlMappingsProvider
new file mode 100644
index 0000000..6f15689
--- /dev/null
+++ b/containers/jersey-servlet/src/main/resources/META-INF/services/org.glassfish.jersey.servlet.spi.FilterUrlMappingsProvider
@@ -0,0 +1 @@
+org.glassfish.jersey.servlet.init.FilterUrlMappingsProviderImpl
\ No newline at end of file
diff --git a/containers/jersey-servlet/src/main/resources/org/glassfish/jersey/servlet/init/internal/localization.properties b/containers/jersey-servlet/src/main/resources/org/glassfish/jersey/servlet/init/internal/localization.properties
new file mode 100644
index 0000000..6f1b074
--- /dev/null
+++ b/containers/jersey-servlet/src/main/resources/org/glassfish/jersey/servlet/init/internal/localization.properties
@@ -0,0 +1,24 @@
+#
+# Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Eclipse Public License v. 2.0, which is available at
+# http://www.eclipse.org/legal/epl-2.0.
+#
+# This Source Code may also be made available under the following Secondary
+# Licenses when the conditions for such availability set forth in the
+# Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+# version 2 with the GNU Classpath Exception, which is available at
+# https://www.gnu.org/software/classpath/license.html.
+#
+# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+#
+
+jersey.app.mapping.conflict=Mapping conflict. A Servlet registration exists with same mapping as the Jersey servlet application, named {0}, at the servlet mapping, {1}.
+jersey.app.no.mapping=The Jersey servlet application, named {0}, has no servlet mapping.
+jersey.app.no.mapping.or.annotation=The Jersey servlet application, named {0}, is not annotated with {1} and has no servlet mapping.
+jersey.app.registered.classes=Registering the Jersey servlet application, named {0}, with the following root resource and provider classes: {1}
+jersey.app.registered.mapping=Registering the Jersey servlet application, named {0}, at the servlet mapping {1}, with the Application class of the same name.
+jersey.app.registered.application=Registering the Jersey servlet application, named {0}, with the Application class of the same name.
+servlet.async.context.already.started=Servlet request has been put into asynchronous mode by an external force. \
+  Proceeding with the existing AsyncContext instance, but cannot guarantee the correct behavior of JAX-RS AsyncResponse time-out support.
diff --git a/containers/jetty-http/pom.xml b/containers/jetty-http/pom.xml
new file mode 100644
index 0000000..eb3d0b8
--- /dev/null
+++ b/containers/jetty-http/pom.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <artifactId>project</artifactId>
+        <groupId>org.glassfish.jersey.containers</groupId>
+        <version>2.28-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>jersey-container-jetty-http</artifactId>
+    <packaging>jar</packaging>
+    <name>jersey-container-jetty-http</name>
+
+    <description>Jetty Http Container</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.glassfish.hk2.external</groupId>
+            <artifactId>javax.inject</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-server</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-util</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-continuation</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>com.sun.istack</groupId>
+                <artifactId>maven-istack-commons-plugin</artifactId>
+                <inherited>true</inherited>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>build-helper-maven-plugin</artifactId>
+                <inherited>true</inherited>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <inherited>true</inherited>
+            </plugin>
+
+        </plugins>
+
+        <resources>
+            <resource>
+                <directory>${basedir}/src/main/java</directory>
+                <includes>
+                    <include>META-INF/**/*</include>
+                </includes>
+            </resource>
+            <resource>
+                <directory>${basedir}/src/main/resources</directory>
+                <filtering>true</filtering>
+            </resource>
+        </resources>
+    </build>
+
+    <profiles>
+        <profile>
+            <id>testsSkipJdk6</id>
+            <activation>
+                <jdk>1.6</jdk>
+            </activation>
+            <properties>
+                <skip.tests>true</skip.tests>
+            </properties>
+        </profile>
+    </profiles>
+
+</project>
diff --git a/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainer.java b/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainer.java
new file mode 100644
index 0000000..fa93775
--- /dev/null
+++ b/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainer.java
@@ -0,0 +1,459 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jetty;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.reflect.Type;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.Principal;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.SecurityContext;
+
+import javax.inject.Inject;
+import javax.inject.Provider;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.glassfish.jersey.internal.MapPropertiesDelegate;
+import org.glassfish.jersey.internal.inject.AbstractBinder;
+import org.glassfish.jersey.internal.inject.ReferencingFactory;
+import org.glassfish.jersey.internal.util.ExtendedLogger;
+import org.glassfish.jersey.internal.util.collection.Ref;
+import org.glassfish.jersey.jetty.internal.LocalizationMessages;
+import org.glassfish.jersey.process.internal.RequestScoped;
+import org.glassfish.jersey.server.ApplicationHandler;
+import org.glassfish.jersey.server.ContainerException;
+import org.glassfish.jersey.server.ContainerRequest;
+import org.glassfish.jersey.server.ContainerResponse;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.ServerProperties;
+import org.glassfish.jersey.server.internal.ContainerUtils;
+import org.glassfish.jersey.server.spi.Container;
+import org.glassfish.jersey.server.spi.ContainerResponseWriter;
+
+import org.eclipse.jetty.continuation.Continuation;
+import org.eclipse.jetty.continuation.ContinuationListener;
+import org.eclipse.jetty.continuation.ContinuationSupport;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+
+/**
+ * Jersey {@code Container} implementation based on Jetty {@link org.eclipse.jetty.server.Handler}.
+ *
+ * @author Arul Dhesiaseelan (aruld@acm.org)
+ * @author Libor Kramolis (libor.kramolis at oracle.com)
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public final class JettyHttpContainer extends AbstractHandler implements Container {
+
+    private static final ExtendedLogger LOGGER =
+            new ExtendedLogger(Logger.getLogger(JettyHttpContainer.class.getName()), Level.FINEST);
+
+    private static final Type REQUEST_TYPE = (new GenericType<Ref<Request>>() {}).getType();
+    private static final Type RESPONSE_TYPE = (new GenericType<Ref<Response>>() {}).getType();
+
+    private static final int INTERNAL_SERVER_ERROR = javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR.getStatusCode();
+
+    /**
+     * Cached value of configuration property
+     * {@link org.glassfish.jersey.server.ServerProperties#RESPONSE_SET_STATUS_OVER_SEND_ERROR}.
+     * If {@code true} method {@link HttpServletResponse#setStatus} is used over {@link HttpServletResponse#sendError}.
+     */
+    private boolean configSetStatusOverSendError;
+
+    /**
+     * Referencing factory for Jetty request.
+     */
+    private static class JettyRequestReferencingFactory extends ReferencingFactory<Request> {
+        @Inject
+        public JettyRequestReferencingFactory(final Provider<Ref<Request>> referenceFactory) {
+            super(referenceFactory);
+        }
+    }
+
+    /**
+     * Referencing factory for Jetty response.
+     */
+    private static class JettyResponseReferencingFactory extends ReferencingFactory<Response> {
+        @Inject
+        public JettyResponseReferencingFactory(final Provider<Ref<Response>> referenceFactory) {
+            super(referenceFactory);
+        }
+    }
+
+    /**
+     * An internal binder to enable Jetty HTTP container specific types injection.
+     * This binder allows to inject underlying Jetty HTTP request and response instances.
+     * Note that since Jetty {@code Request} class is not proxiable as it does not expose an empty constructor,
+     * the injection of Jetty request instance into singleton JAX-RS and Jersey providers is only supported via
+     * {@link javax.inject.Provider injection provider}.
+     */
+    private static class JettyBinder extends AbstractBinder {
+
+        @Override
+        protected void configure() {
+            bindFactory(JettyRequestReferencingFactory.class).to(Request.class)
+                    .proxy(false).in(RequestScoped.class);
+            bindFactory(ReferencingFactory.<Request>referenceFactory()).to(new GenericType<Ref<Request>>() {})
+                    .in(RequestScoped.class);
+
+            bindFactory(JettyResponseReferencingFactory.class).to(Response.class)
+                    .proxy(false).in(RequestScoped.class);
+            bindFactory(ReferencingFactory.<Response>referenceFactory()).to(new GenericType<Ref<Response>>() {})
+                    .in(RequestScoped.class);
+        }
+    }
+
+    private volatile ApplicationHandler appHandler;
+
+    @Override
+    public void handle(final String target, final Request request, final HttpServletRequest httpServletRequest,
+                       final HttpServletResponse httpServletResponse) throws IOException, ServletException {
+
+        final Response response = request.getResponse();
+        final ResponseWriter responseWriter = new ResponseWriter(request, response, configSetStatusOverSendError);
+        final URI baseUri = getBaseUri(request);
+        final URI requestUri = getRequestUri(request, baseUri);
+        try {
+            final ContainerRequest requestContext = new ContainerRequest(
+                    baseUri,
+                    requestUri,
+                    request.getMethod(),
+                    getSecurityContext(request),
+                    new MapPropertiesDelegate());
+            requestContext.setEntityStream(request.getInputStream());
+            final Enumeration<String> headerNames = request.getHeaderNames();
+            while (headerNames.hasMoreElements()) {
+                final String headerName = headerNames.nextElement();
+                String headerValue = request.getHeader(headerName);
+                requestContext.headers(headerName, headerValue == null ? "" : headerValue);
+            }
+            requestContext.setWriter(responseWriter);
+            requestContext.setRequestScopedInitializer(injectionManager -> {
+                injectionManager.<Ref<Request>>getInstance(REQUEST_TYPE).set(request);
+                injectionManager.<Ref<Response>>getInstance(RESPONSE_TYPE).set(response);
+            });
+
+            // Mark the request as handled before generating the body of the response
+            request.setHandled(true);
+            appHandler.handle(requestContext);
+        } catch (final Exception ex) {
+            throw new RuntimeException(ex);
+        }
+
+    }
+
+    private URI getRequestUri(final Request request, final URI baseUri) {
+        try {
+            final String serverAddress = getServerAddress(baseUri);
+            String uri = request.getRequestURI();
+
+            final String queryString = request.getQueryString();
+            if (queryString != null) {
+                uri = uri + "?" + ContainerUtils.encodeUnsafeCharacters(queryString);
+            }
+
+            return new URI(serverAddress + uri);
+        } catch (URISyntaxException ex) {
+            throw new IllegalArgumentException(ex);
+        }
+    }
+
+    private String getServerAddress(URI baseUri) {
+        String serverAddress = baseUri.toString();
+        if (serverAddress.charAt(serverAddress.length() - 1) == '/') {
+            return serverAddress.substring(0, serverAddress.length() - 1);
+        }
+        return serverAddress;
+    }
+
+    private SecurityContext getSecurityContext(final Request request) {
+        return new SecurityContext() {
+
+            @Override
+            public boolean isUserInRole(final String role) {
+                return request.isUserInRole(role);
+            }
+
+            @Override
+            public boolean isSecure() {
+                return request.isSecure();
+            }
+
+            @Override
+            public Principal getUserPrincipal() {
+                return request.getUserPrincipal();
+            }
+
+            @Override
+            public String getAuthenticationScheme() {
+                return request.getAuthType();
+            }
+        };
+    }
+
+
+    private URI getBaseUri(final Request request) {
+        try {
+            return new URI(request.getScheme(), null, request.getServerName(),
+                    request.getServerPort(), getBasePath(request), null, null);
+        } catch (final URISyntaxException ex) {
+            throw new IllegalArgumentException(ex);
+        }
+    }
+
+    private String getBasePath(final Request request) {
+        final String contextPath = request.getContextPath();
+
+        if (contextPath == null || contextPath.isEmpty()) {
+            return "/";
+        } else if (contextPath.charAt(contextPath.length() - 1) != '/') {
+            return contextPath + "/";
+        } else {
+            return contextPath;
+        }
+    }
+
+    private static final class ResponseWriter implements ContainerResponseWriter {
+
+        private final Response response;
+        private final Continuation continuation;
+        private final boolean configSetStatusOverSendError;
+
+        ResponseWriter(final Request request, final Response response, final boolean configSetStatusOverSendError) {
+            this.response = response;
+            this.continuation = ContinuationSupport.getContinuation(request);
+            this.configSetStatusOverSendError = configSetStatusOverSendError;
+        }
+
+        @Override
+        public OutputStream writeResponseStatusAndHeaders(final long contentLength, final ContainerResponse context)
+                throws ContainerException {
+
+            final javax.ws.rs.core.Response.StatusType statusInfo = context.getStatusInfo();
+
+            final int code = statusInfo.getStatusCode();
+            final String reason = statusInfo.getReasonPhrase() == null
+                    ? HttpStatus.getMessage(code) : statusInfo.getReasonPhrase();
+
+            response.setStatusWithReason(code, reason);
+
+            if (contentLength != -1 && contentLength < Integer.MAX_VALUE) {
+                response.setContentLength((int) contentLength);
+            }
+            for (final Map.Entry<String, List<String>> e : context.getStringHeaders().entrySet()) {
+                for (final String value : e.getValue()) {
+                    response.addHeader(e.getKey(), value);
+                }
+            }
+
+            try {
+                return response.getOutputStream();
+            } catch (final IOException ioe) {
+                throw new ContainerException("Error during writing out the response headers.", ioe);
+            }
+        }
+
+        @Override
+        public boolean suspend(final long timeOut, final TimeUnit timeUnit, final TimeoutHandler timeoutHandler) {
+            try {
+                if (timeOut > 0) {
+                    final long timeoutMillis = TimeUnit.MILLISECONDS.convert(timeOut, timeUnit);
+                    continuation.setTimeout(timeoutMillis);
+                }
+                continuation.addContinuationListener(new ContinuationListener() {
+                    @Override
+                    public void onComplete(final Continuation continuation) {
+                    }
+
+                    @Override
+                    public void onTimeout(final Continuation continuation) {
+                        if (timeoutHandler != null) {
+                            timeoutHandler.onTimeout(ResponseWriter.this);
+                        }
+                    }
+                });
+                continuation.suspend(response);
+                return true;
+            } catch (final Exception ex) {
+                return false;
+            }
+        }
+
+        @Override
+        public void setSuspendTimeout(final long timeOut, final TimeUnit timeUnit) throws IllegalStateException {
+            if (timeOut > 0) {
+                final long timeoutMillis = TimeUnit.MILLISECONDS.convert(timeOut, timeUnit);
+                continuation.setTimeout(timeoutMillis);
+            }
+        }
+
+        @Override
+        public void commit() {
+            try {
+                response.closeOutput();
+            } catch (final IOException e) {
+                LOGGER.log(Level.WARNING, LocalizationMessages.UNABLE_TO_CLOSE_RESPONSE(), e);
+            } finally {
+                if (continuation.isSuspended()) {
+                    continuation.complete();
+                }
+                LOGGER.log(Level.FINEST, "commit() called");
+            }
+        }
+
+        @Override
+        public void failure(final Throwable error) {
+            try {
+                if (!response.isCommitted()) {
+                    try {
+                        if (configSetStatusOverSendError) {
+                            response.reset();
+                            //noinspection deprecation
+                            response.setStatus(INTERNAL_SERVER_ERROR, "Request failed.");
+                        } else {
+                            response.sendError(INTERNAL_SERVER_ERROR, "Request failed.");
+                        }
+                    } catch (final IllegalStateException ex) {
+                        // a race condition externally committing the response can still occur...
+                        LOGGER.log(Level.FINER, "Unable to reset failed response.", ex);
+                    } catch (final IOException ex) {
+                        throw new ContainerException(LocalizationMessages.EXCEPTION_SENDING_ERROR_RESPONSE(INTERNAL_SERVER_ERROR,
+                                "Request failed."), ex);
+                    }
+                }
+            } finally {
+                LOGGER.log(Level.FINEST, "failure(...) called");
+                commit();
+                rethrow(error);
+            }
+        }
+
+        @Override
+        public boolean enableResponseBuffering() {
+            return false;
+        }
+
+        /**
+         * Rethrow the original exception as required by JAX-RS, 3.3.4.
+         *
+         * @param error throwable to be re-thrown
+         */
+        private void rethrow(final Throwable error) {
+            if (error instanceof RuntimeException) {
+                throw (RuntimeException) error;
+            } else {
+                throw new ContainerException(error);
+            }
+        }
+
+    }
+
+    @Override
+    public ResourceConfig getConfiguration() {
+        return appHandler.getConfiguration();
+    }
+
+    @Override
+    public void reload() {
+        reload(getConfiguration());
+    }
+
+    @Override
+    public void reload(final ResourceConfig configuration) {
+        appHandler.onShutdown(this);
+
+        appHandler = new ApplicationHandler(configuration.register(new JettyBinder()));
+        appHandler.onReload(this);
+        appHandler.onStartup(this);
+        cacheConfigSetStatusOverSendError();
+    }
+
+    @Override
+    public ApplicationHandler getApplicationHandler() {
+        return appHandler;
+    }
+
+    /**
+     * Inform this container that the server has been started.
+     * This method must be implicitly called after the server containing this container is started.
+     *
+     * @throws java.lang.Exception if a problem occurred during server startup.
+     */
+    @Override
+    protected void doStart() throws Exception {
+        super.doStart();
+        appHandler.onStartup(this);
+    }
+
+    /**
+     * Inform this container that the server is being stopped.
+     * This method must be implicitly called before the server containing this container is stopped.
+     *
+     * @throws java.lang.Exception if a problem occurred during server shutdown.
+     */
+    @Override
+    public void doStop() throws Exception {
+        super.doStop();
+        appHandler.onShutdown(this);
+        appHandler = null;
+    }
+
+    /**
+     * Create a new Jetty HTTP container.
+     *
+     * @param application   JAX-RS / Jersey application to be deployed on Jetty HTTP container.
+     * @param parentContext DI provider specific context with application's registered bindings.
+     */
+    JettyHttpContainer(final Application application, final Object parentContext) {
+        this.appHandler = new ApplicationHandler(application, new JettyBinder(), parentContext);
+    }
+
+    /**
+     * Create a new Jetty HTTP container.
+     *
+     * @param application JAX-RS / Jersey application to be deployed on Jetty HTTP container.
+     */
+    JettyHttpContainer(final Application application) {
+        this.appHandler = new ApplicationHandler(application, new JettyBinder());
+
+        cacheConfigSetStatusOverSendError();
+    }
+
+    /**
+     * The method reads and caches value of configuration property
+     * {@link ServerProperties#RESPONSE_SET_STATUS_OVER_SEND_ERROR} for future purposes.
+     */
+    private void cacheConfigSetStatusOverSendError() {
+        this.configSetStatusOverSendError = ServerProperties.getValue(getConfiguration().getProperties(),
+                ServerProperties.RESPONSE_SET_STATUS_OVER_SEND_ERROR, false, Boolean.class);
+    }
+
+}
diff --git a/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainerFactory.java b/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainerFactory.java
new file mode 100644
index 0000000..8155ea7
--- /dev/null
+++ b/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainerFactory.java
@@ -0,0 +1,300 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jetty;
+
+import java.net.URI;
+import java.util.concurrent.ThreadFactory;
+
+import javax.ws.rs.ProcessingException;
+
+import org.glassfish.jersey.internal.guava.ThreadFactoryBuilder;
+import org.glassfish.jersey.jetty.internal.LocalizationMessages;
+import org.glassfish.jersey.process.JerseyProcessingUncaughtExceptionHandler;
+import org.glassfish.jersey.server.ContainerFactory;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.spi.Container;
+
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.SecureRequestCustomizer;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.SslConnectionFactory;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+
+/**
+ * Factory for creating and starting Jetty server handlers. This returns
+ * a handle to the started server as {@link Server} instances, which allows
+ * the server to be stopped by invoking the {@link org.eclipse.jetty.server.Server#stop()} method.
+ * <p/>
+ * To start the server in HTTPS mode an {@link SslContextFactory} can be provided.
+ * This will be used to decrypt and encrypt information sent over the
+ * connected TCP socket channel.
+ *
+ * @author Arul Dhesiaseelan (aruld@acm.org)
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public final class JettyHttpContainerFactory {
+
+    private JettyHttpContainerFactory() {
+    }
+
+    /**
+     * Creates a {@link Server} instance that registers an {@link org.eclipse.jetty.server.Handler}.
+     *
+     * @param uri uri on which the {@link org.glassfish.jersey.server.ApplicationHandler} will be deployed. Only first path
+     *            segment will be used as context path, the rest will be ignored.
+     * @return newly created {@link Server}.
+     *
+     * @throws ProcessingException      in case of any failure when creating a new Jetty {@code Server} instance.
+     * @throws IllegalArgumentException if {@code uri} is {@code null}.
+     */
+    public static Server createServer(final URI uri) throws ProcessingException {
+        return createServer(uri, null, null, true);
+    }
+
+    /**
+     * Creates a {@link Server} instance that registers an {@link org.eclipse.jetty.server.Handler}.
+     *
+     * @param uri   uri on which the {@link org.glassfish.jersey.server.ApplicationHandler} will be deployed. Only first path
+     *              segment will be used as context path, the rest will be ignored.
+     * @param start if set to false, server will not get started, which allows to configure the underlying transport
+     *              layer, see above for details.
+     * @return newly created {@link Server}.
+     *
+     * @throws ProcessingException      in case of any failure when creating a new Jetty {@code Server} instance.
+     * @throws IllegalArgumentException if {@code uri} is {@code null}.
+     */
+    public static Server createServer(final URI uri, final boolean start) throws ProcessingException {
+        return createServer(uri, null, null, start);
+    }
+
+    /**
+     * Create a {@link Server} that registers an {@link org.eclipse.jetty.server.Handler} that
+     * in turn manages all root resource and provider classes declared by the
+     * resource configuration.
+     * <p/>
+     * This implementation defers to the
+     * {@link org.glassfish.jersey.server.ContainerFactory#createContainer(Class, javax.ws.rs.core.Application)} method
+     * for creating an Container that manages the root resources.
+     *
+     * @param uri    the URI to create the http server. The URI scheme must be
+     *               equal to "http". The URI user information and host
+     *               are ignored If the URI port is not present then port 80 will be
+     *               used. The URI path, query and fragment components are ignored.
+     * @param config the resource configuration.
+     * @return newly created {@link Server}.
+     *
+     * @throws ProcessingException      in case of any failure when creating a new Jetty {@code Server} instance.
+     * @throws IllegalArgumentException if {@code uri} is {@code null}.
+     */
+    public static Server createServer(final URI uri, final ResourceConfig config)
+            throws ProcessingException {
+
+        final JettyHttpContainer container = ContainerFactory.createContainer(JettyHttpContainer.class, config);
+        return createServer(uri, null, container, true);
+    }
+
+    /**
+     * Create a {@link Server} that registers an {@link org.eclipse.jetty.server.Handler} that
+     * in turn manages all root resource and provider classes declared by the
+     * resource configuration.
+     * <p/>
+     * This implementation defers to the
+     * {@link org.glassfish.jersey.server.ContainerFactory#createContainer(Class, javax.ws.rs.core.Application)} method
+     * for creating an Container that manages the root resources.
+     *
+     * @param uri           URI on which the Jersey web application will be deployed. Only first path segment will be
+     *                      used as context path, the rest will be ignored.
+     * @param configuration web application configuration.
+     * @param start         if set to false, server will not get started, which allows to configure the underlying
+     *                      transport layer, see above for details.
+     * @return newly created {@link Server}.
+     *
+     * @throws ProcessingException      in case of any failure when creating a new Jetty {@code Server} instance.
+     * @throws IllegalArgumentException if {@code uri} is {@code null}.
+     */
+    public static Server createServer(final URI uri, final ResourceConfig configuration, final boolean start)
+            throws ProcessingException {
+        return createServer(uri, null, ContainerFactory.createContainer(JettyHttpContainer.class, configuration), start);
+    }
+
+
+    /**
+     * Create a {@link Server} that registers an {@link org.eclipse.jetty.server.Handler} that
+     * in turn manages all root resource and provider classes declared by the
+     * resource configuration.
+     *
+     * @param uri           the URI to create the http server. The URI scheme must be
+     *                      equal to "https". The URI user information and host
+     *                      are ignored If the URI port is not present then port 143 will be
+     *                      used. The URI path, query and fragment components are ignored.
+     * @param config        the resource configuration.
+     * @param parentContext DI provider specific context with application's registered bindings.
+     * @param start         if set to false, server will not get started, this allows end users to set
+     *                      additional properties on the underlying listener.
+     * @return newly created {@link Server}.
+     *
+     * @throws ProcessingException      in case of any failure when creating a new Jetty {@code Server} instance.
+     * @throws IllegalArgumentException if {@code uri} is {@code null}.
+     * @see JettyHttpContainer
+     * @since 2.12
+     */
+    public static Server createServer(final URI uri, final ResourceConfig config, final boolean start,
+                                      final Object parentContext) {
+        return createServer(uri, null, new JettyHttpContainer(config, parentContext), start);
+    }
+
+
+    /**
+     * Create a {@link Server} that registers an {@link org.eclipse.jetty.server.Handler} that
+     * in turn manages all root resource and provider classes declared by the
+     * resource configuration.
+     *
+     * @param uri           the URI to create the http server. The URI scheme must be
+     *                      equal to "https". The URI user information and host
+     *                      are ignored If the URI port is not present then port 143 will be
+     *                      used. The URI path, query and fragment components are ignored.
+     * @param config        the resource configuration.
+     * @param parentContext DI provider specific context with application's registered bindings.
+     * @return newly created {@link Server}.
+     *
+     * @throws ProcessingException      in case of any failure when creating a new Jetty {@code Server} instance.
+     * @throws IllegalArgumentException if {@code uri} is {@code null}.
+     * @see JettyHttpContainer
+     * @since 2.12
+     */
+    public static Server createServer(final URI uri, final ResourceConfig config, final Object parentContext) {
+        return createServer(uri, null, new JettyHttpContainer(config, parentContext), true);
+    }
+
+    /**
+     * Create a {@link Server} that registers an {@link org.eclipse.jetty.server.Handler} that
+     * in turn manages all root resource and provider classes declared by the
+     * resource configuration.
+     * <p/>
+     * This implementation defers to the
+     * {@link ContainerFactory#createContainer(Class, javax.ws.rs.core.Application)} method
+     * for creating an Container that manages the root resources.
+     *
+     * @param uri               the URI to create the http server. The URI scheme must be
+     *                          equal to {@code https}. The URI user information and host
+     *                          are ignored. If the URI port is not present then port
+     *                          {@value org.glassfish.jersey.server.spi.Container#DEFAULT_HTTPS_PORT} will be
+     *                          used. The URI path, query and fragment components are ignored.
+     * @param sslContextFactory this is the SSL context factory used to configure SSL connector
+     * @param config            the resource configuration.
+     * @return newly created {@link Server}.
+     *
+     * @throws ProcessingException      in case of any failure when creating a new Jetty {@code Server} instance.
+     * @throws IllegalArgumentException if {@code uri} is {@code null}.
+     */
+    public static Server createServer(final URI uri, final SslContextFactory sslContextFactory, final ResourceConfig config)
+            throws ProcessingException {
+        final JettyHttpContainer container = ContainerFactory.createContainer(JettyHttpContainer.class, config);
+        return createServer(uri, sslContextFactory, container, true);
+    }
+
+    /**
+     * Create a {@link Server} that registers an {@link org.eclipse.jetty.server.Handler} that
+     * in turn manages all root resource and provider classes found by searching the
+     * classes referenced in the java classpath.
+     *
+     * @param uri               the URI to create the http server. The URI scheme must be
+     *                          equal to {@code https}. The URI user information and host
+     *                          are ignored. If the URI port is not present then port
+     *                          {@value org.glassfish.jersey.server.spi.Container#DEFAULT_HTTPS_PORT} will be
+     *                          used. The URI path, query and fragment components are ignored.
+     * @param sslContextFactory this is the SSL context factory used to configure SSL connector
+     * @param handler           the container that handles all HTTP requests
+     * @param start             if set to false, server will not get started, this allows end users to set
+     *                          additional properties on the underlying listener.
+     * @return newly created {@link Server}.
+     *
+     * @throws ProcessingException      in case of any failure when creating a new Jetty {@code Server} instance.
+     * @throws IllegalArgumentException if {@code uri} is {@code null}.
+     * @see JettyHttpContainer
+     */
+    public static Server createServer(final URI uri,
+                                      final SslContextFactory sslContextFactory,
+                                      final JettyHttpContainer handler,
+                                      final boolean start) {
+        if (uri == null) {
+            throw new IllegalArgumentException(LocalizationMessages.URI_CANNOT_BE_NULL());
+        }
+        final String scheme = uri.getScheme();
+        int defaultPort = Container.DEFAULT_HTTP_PORT;
+
+        if (sslContextFactory == null) {
+            if (!"http".equalsIgnoreCase(scheme)) {
+                throw new IllegalArgumentException(LocalizationMessages.WRONG_SCHEME_WHEN_USING_HTTP());
+            }
+        } else {
+            if (!"https".equalsIgnoreCase(scheme)) {
+                throw new IllegalArgumentException(LocalizationMessages.WRONG_SCHEME_WHEN_USING_HTTPS());
+            }
+            defaultPort = Container.DEFAULT_HTTPS_PORT;
+        }
+        final int port = (uri.getPort() == -1) ? defaultPort : uri.getPort();
+
+        final Server server = new Server(new JettyConnectorThreadPool());
+        final HttpConfiguration config = new HttpConfiguration();
+        if (sslContextFactory != null) {
+            config.setSecureScheme("https");
+            config.setSecurePort(port);
+            config.addCustomizer(new SecureRequestCustomizer());
+
+            final ServerConnector https = new ServerConnector(server,
+                    new SslConnectionFactory(sslContextFactory, "http/1.1"),
+                    new HttpConnectionFactory(config));
+            https.setPort(port);
+            server.setConnectors(new Connector[]{https});
+
+        } else {
+            final ServerConnector http = new ServerConnector(server, new HttpConnectionFactory(config));
+            http.setPort(port);
+            server.setConnectors(new Connector[]{http});
+        }
+        if (handler != null) {
+            server.setHandler(handler);
+        }
+
+        if (start) {
+            try {
+                // Start the server.
+                server.start();
+            } catch (final Exception e) {
+                throw new ProcessingException(LocalizationMessages.ERROR_WHEN_CREATING_SERVER(), e);
+            }
+        }
+        return server;
+    }
+
+    private static final class JettyConnectorThreadPool extends QueuedThreadPool {
+        private final ThreadFactory threadFactory = new ThreadFactoryBuilder()
+                .setNameFormat("jetty-http-server-%d")
+                .setUncaughtExceptionHandler(new JerseyProcessingUncaughtExceptionHandler())
+                .build();
+
+        @Override
+        protected Thread newThread(Runnable runnable) {
+            return threadFactory.newThread(runnable);
+        }
+    }
+}
diff --git a/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainerProvider.java b/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainerProvider.java
new file mode 100644
index 0000000..15afcbf
--- /dev/null
+++ b/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/JettyHttpContainerProvider.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jetty;
+
+import javax.ws.rs.ProcessingException;
+import javax.ws.rs.core.Application;
+
+import org.glassfish.jersey.server.spi.ContainerProvider;
+
+import org.eclipse.jetty.server.Handler;
+
+/**
+ * Container provider for containers based on Jetty Server {@link org.eclipse.jetty.server.Handler}.
+ *
+ * @author Arul Dhesiaseelan (aruld@acm.org)
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public final class JettyHttpContainerProvider implements ContainerProvider {
+
+    @Override
+    public <T> T createContainer(final Class<T> type, final Application application) throws ProcessingException {
+        if (Handler.class == type || JettyHttpContainer.class == type) {
+            return type.cast(new JettyHttpContainer(application));
+        }
+        return null;
+    }
+
+}
diff --git a/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/package-info.java b/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/package-info.java
new file mode 100644
index 0000000..4ea14bf
--- /dev/null
+++ b/containers/jetty-http/src/main/java/org/glassfish/jersey/jetty/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+/**
+ * Jersey Jetty container classes.
+ */
+package org.glassfish.jersey.jetty;
diff --git a/containers/jetty-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ContainerProvider b/containers/jetty-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ContainerProvider
new file mode 100644
index 0000000..1496edb
--- /dev/null
+++ b/containers/jetty-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ContainerProvider
@@ -0,0 +1 @@
+org.glassfish.jersey.jetty.JettyHttpContainerProvider
\ No newline at end of file
diff --git a/containers/jetty-http/src/main/resources/org/glassfish/jersey/jetty/internal/localization.properties b/containers/jetty-http/src/main/resources/org/glassfish/jersey/jetty/internal/localization.properties
new file mode 100644
index 0000000..b1528f8
--- /dev/null
+++ b/containers/jetty-http/src/main/resources/org/glassfish/jersey/jetty/internal/localization.properties
@@ -0,0 +1,23 @@
+#
+# Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Eclipse Public License v. 2.0, which is available at
+# http://www.eclipse.org/legal/epl-2.0.
+#
+# This Source Code may also be made available under the following Secondary
+# Licenses when the conditions for such availability set forth in the
+# Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+# version 2 with the GNU Classpath Exception, which is available at
+# https://www.gnu.org/software/classpath/license.html.
+#
+# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+#
+
+# {0} - status code; {1} - status reason message
+exception.sending.error.response=I/O exception occurred while sending "{0}/{1}" error response.
+error.when.creating.server=Exception thrown when trying to create jetty server.
+unable.to.close.response=Unable to close response output.
+uri.cannot.be.null=The URI must not be null.
+wrong.scheme.when.using.http=The URI scheme should be 'http' when not using SSL.
+wrong.scheme.when.using.https=The URI scheme should be 'https' when using SSL.
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
new file mode 100644
index 0000000..dcfe4b7
--- /dev/null
+++ b/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/AbstractJettyServerTester.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jetty;
+
+import java.net.URI;
+import java.security.AccessController;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.core.UriBuilder;
+
+import org.glassfish.jersey.logging.LoggingFeature;
+import org.glassfish.jersey.internal.util.PropertiesHelper;
+import org.glassfish.jersey.server.ResourceConfig;
+
+import org.eclipse.jetty.server.Server;
+import org.junit.After;
+
+/**
+ * Abstract Jetty Server unit tester.
+ *
+ * @author Paul Sandoz
+ * @author Arul Dhesiaseelan (aruld at acm.org)
+ * @author Miroslav Fuksa
+ */
+public abstract class AbstractJettyServerTester {
+
+    private static final Logger LOGGER = Logger.getLogger(AbstractJettyServerTester.class.getName());
+
+    public static final String CONTEXT = "";
+    private static final int DEFAULT_PORT = 9998;
+
+    /**
+     * Get the port to be used for test application deployments.
+     *
+     * @return The HTTP port of the URI
+     */
+    protected final int getPort() {
+        final String value = AccessController
+                .doPrivileged(PropertiesHelper.getSystemProperty("jersey.config.test.container.port"));
+        if (value != null) {
+
+            try {
+                final int i = Integer.parseInt(value);
+                if (i <= 0) {
+                    throw new NumberFormatException("Value not positive.");
+                }
+                return i;
+            } catch (NumberFormatException e) {
+                LOGGER.log(Level.CONFIG,
+                        "Value of 'jersey.config.test.container.port'"
+                                + " property is not a valid positive integer [" + value + "]."
+                                + " Reverting to default [" + DEFAULT_PORT + "].",
+                        e);
+            }
+        }
+        return DEFAULT_PORT;
+    }
+
+    private volatile Server server;
+
+    public UriBuilder getUri() {
+        return UriBuilder.fromUri("http://localhost").port(getPort()).path(CONTEXT);
+    }
+
+    public void startServer(Class... resources) {
+        ResourceConfig config = new ResourceConfig(resources);
+        config.register(new LoggingFeature(LOGGER, LoggingFeature.Verbosity.PAYLOAD_ANY));
+        final URI baseUri = getBaseUri();
+        server = JettyHttpContainerFactory.createServer(baseUri, config);
+        LOGGER.log(Level.INFO, "Jetty-http server started on base uri: " + baseUri);
+    }
+
+    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);
+    }
+
+    public URI getBaseUri() {
+        return UriBuilder.fromUri("http://localhost/").port(getPort()).build();
+    }
+
+    public void stopServer() {
+        try {
+            server.stop();
+            server = null;
+            LOGGER.log(Level.INFO, "Jetty-http server stopped.");
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @After
+    public void tearDown() {
+        if (server != null) {
+            stopServer();
+        }
+    }
+}
diff --git a/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/AsyncTest.java b/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/AsyncTest.java
new file mode 100644
index 0000000..b4d6b81
--- /dev/null
+++ b/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/AsyncTest.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jetty;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.container.AsyncResponse;
+import javax.ws.rs.container.Suspended;
+import javax.ws.rs.container.TimeoutHandler;
+import javax.ws.rs.core.Response;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author Arul Dhesiaseelan (aruld at acm.org)
+ * @author Michal Gajdos
+ */
+public class AsyncTest extends AbstractJettyServerTester {
+
+    @Path("/async")
+    @SuppressWarnings("VoidMethodAnnotatedWithGET")
+    public static class AsyncResource {
+
+        public static AtomicInteger INVOCATION_COUNT = new AtomicInteger(0);
+
+        @GET
+        public void asyncGet(@Suspended final AsyncResponse asyncResponse) {
+            new Thread(new Runnable() {
+
+                @Override
+                public void run() {
+                    final String result = veryExpensiveOperation();
+                    asyncResponse.resume(result);
+                }
+
+                private String veryExpensiveOperation() {
+                    // ... very expensive operation that typically finishes within 5 seconds, simulated using sleep()
+                    try {
+                        Thread.sleep(5000);
+                    } catch (final InterruptedException e) {
+                        // ignore
+                    }
+                    return "DONE";
+                }
+            }).start();
+        }
+
+        @GET
+        @Path("timeout")
+        public void asyncGetWithTimeout(@Suspended final AsyncResponse asyncResponse) {
+            asyncResponse.setTimeoutHandler(new TimeoutHandler() {
+
+                @Override
+                public void handleTimeout(final AsyncResponse asyncResponse) {
+                    asyncResponse.resume(Response.status(Response.Status.SERVICE_UNAVAILABLE).entity("Operation time out.")
+                            .build());
+                }
+            });
+            asyncResponse.setTimeout(3, TimeUnit.SECONDS);
+
+            new Thread(new Runnable() {
+
+                @Override
+                public void run() {
+                    final String result = veryExpensiveOperation();
+                    asyncResponse.resume(result);
+                }
+
+                private String veryExpensiveOperation() {
+                    // ... very expensive operation that typically finishes within 10 seconds, simulated using sleep()
+                    try {
+                        Thread.sleep(7000);
+                    } catch (final InterruptedException e) {
+                        // ignore
+                    }
+                    return "DONE";
+                }
+            }).start();
+        }
+
+        @GET
+        @Path("multiple-invocations")
+        public void asyncMultipleInvocations(@Suspended final AsyncResponse asyncResponse) {
+            INVOCATION_COUNT.incrementAndGet();
+
+            new Thread(new Runnable() {
+                @Override
+                public void run() {
+                    asyncResponse.resume("OK");
+                }
+            }).start();
+        }
+    }
+
+    private Client client;
+
+    @Before
+    public void setUp() throws Exception {
+        startServer(AsyncResource.class);
+        client = ClientBuilder.newClient();
+    }
+
+    @Override
+    @After
+    public void tearDown() {
+        super.tearDown();
+        client = null;
+    }
+
+    @Test
+    public void testAsyncGet() throws ExecutionException, InterruptedException {
+        final Future<Response> responseFuture = client.target(getUri().path("/async")).request().async().get();
+        // Request is being processed asynchronously.
+        final Response response = responseFuture.get();
+        // get() waits for the response
+        assertEquals("DONE", response.readEntity(String.class));
+    }
+
+    @Test
+    public void testAsyncGetWithTimeout() throws ExecutionException, InterruptedException, TimeoutException {
+        final Future<Response> responseFuture = client.target(getUri().path("/async/timeout")).request().async().get();
+        // Request is being processed asynchronously.
+        final Response response = responseFuture.get();
+
+        // get() waits for the response
+        assertEquals(503, response.getStatus());
+        assertEquals("Operation time out.", response.readEntity(String.class));
+    }
+
+    /**
+     * JERSEY-2616 reproducer. Make sure resource method is only invoked once per one request.
+     */
+    @Test
+    public void testAsyncMultipleInvocations() throws Exception {
+        final Response response = client.target(getUri().path("/async/multiple-invocations")).request().get();
+
+        assertThat(AsyncResource.INVOCATION_COUNT.get(), is(1));
+
+        assertThat(response.getStatus(), is(200));
+        assertThat(response.readEntity(String.class), is("OK"));
+    }
+}
diff --git a/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/ExceptionTest.java b/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/ExceptionTest.java
new file mode 100644
index 0000000..e934e6e
--- /dev/null
+++ b/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/ExceptionTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jetty;
+
+import org.junit.Test;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Response;
+
+import java.io.IOException;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author Paul Sandoz
+ */
+public class ExceptionTest extends AbstractJettyServerTester {
+    @Path("{status}")
+    public static class ExceptionResource {
+        @GET
+        public String get(@PathParam("status") int status) {
+            throw new WebApplicationException(status);
+        }
+
+    }
+
+    @Test
+    public void test400StatusCode() throws IOException {
+        startServer(ExceptionResource.class);
+        Client client = ClientBuilder.newClient();
+        WebTarget r = client.target(getUri().path("400").build());
+        assertEquals(400, r.request().get(Response.class).getStatus());
+    }
+
+    @Test
+    public void test500StatusCode() {
+        startServer(ExceptionResource.class);
+        Client client = ClientBuilder.newClient();
+        WebTarget r = client.target(getUri().path("500").build());
+
+        assertEquals(500, r.request().get(Response.class).getStatus());
+    }
+}
diff --git a/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/LifecycleListenerTest.java b/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/LifecycleListenerTest.java
new file mode 100644
index 0000000..1377b88
--- /dev/null
+++ b/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/LifecycleListenerTest.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jetty;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.spi.AbstractContainerLifecycleListener;
+import org.glassfish.jersey.server.spi.Container;
+import org.junit.Test;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Response;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+
+/**
+ * Reload and ContainerLifecycleListener support test.
+ *
+ * @author Paul Sandoz
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class LifecycleListenerTest extends AbstractJettyServerTester {
+
+    @Path("/one")
+    public static class One {
+        @GET
+        public String get() {
+            return "one";
+        }
+    }
+
+    @Path("/two")
+    public static class Two {
+        @GET
+        public String get() {
+            return "two";
+        }
+    }
+
+    public static class Reloader extends AbstractContainerLifecycleListener {
+        Container container;
+
+        public void reload(ResourceConfig newConfig) {
+            container.reload(newConfig);
+        }
+
+        public void reload() {
+            container.reload();
+        }
+
+        @Override
+        public void onStartup(Container container) {
+            this.container = container;
+        }
+
+    }
+
+    @Test
+    public void testReload() {
+        final ResourceConfig rc = new ResourceConfig(One.class);
+
+        Reloader reloader = new Reloader();
+        rc.registerInstances(reloader);
+
+        startServer(rc);
+
+        Client client = ClientBuilder.newClient();
+        WebTarget r = client.target(getUri().path("/").build());
+
+        assertEquals("one", r.path("one").request().get(String.class));
+        assertEquals(404, r.path("two").request().get(Response.class).getStatus());
+
+        // add Two resource
+        reloader.reload(new ResourceConfig(One.class, Two.class));
+
+        assertEquals("one", r.path("one").request().get(String.class));
+        assertEquals("two", r.path("two").request().get(String.class));
+    }
+
+    static class StartStopListener extends AbstractContainerLifecycleListener {
+        volatile boolean started;
+        volatile boolean stopped;
+
+        @Override
+        public void onStartup(Container container) {
+            started = true;
+        }
+
+        @Override
+        public void onShutdown(Container container) {
+            stopped = true;
+        }
+    }
+
+    @Test
+    public void testStartupShutdownHooks() {
+        final StartStopListener listener = new StartStopListener();
+
+        startServer(new ResourceConfig(One.class).register(listener));
+
+        Client client = ClientBuilder.newClient();
+        WebTarget r = client.target(getUri().path("/").build());
+
+        assertThat(r.path("one").request().get(String.class), equalTo("one"));
+        assertThat(r.path("two").request().get(Response.class).getStatus(), equalTo(404));
+
+        stopServer();
+
+        assertTrue("ContainerLifecycleListener.onStartup has not been called.", listener.started);
+        assertTrue("ContainerLifecycleListener.onShutdown has not been called.", listener.stopped);
+    }
+}
diff --git a/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/OptionsTest.java b/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/OptionsTest.java
new file mode 100644
index 0000000..8866c33
--- /dev/null
+++ b/containers/jetty-http/src/test/java/org/glassfish/jersey/jetty/OptionsTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jetty;
+
+import org.junit.Test;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.core.Response;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class OptionsTest extends AbstractJettyServerTester {
+
+    @Path("helloworld")
+    public static class HelloWorldResource {
+        public static final String CLICHED_MESSAGE = "Hello World!";
+
+        @GET
+        @Produces("text/plain")
+        public String getHello() {
+            return CLICHED_MESSAGE;
+        }
+    }
+
+    @Test
+    public void testFooBarOptions() {
+        startServer(HelloWorldResource.class);
+        Client client = ClientBuilder.newClient();
+        Response response = client.target(getUri()).path("helloworld").request().header("Accept", "foo/bar").options();
+        assertEquals(200, response.getStatus());
+        final String allowHeader = response.getHeaderString("Allow");
+        _checkAllowContent(allowHeader);
+        assertEquals(0, response.getLength());
+        assertEquals("foo/bar", response.getMediaType().toString());
+    }
+
+    private void _checkAllowContent(final String content) {
+        assertTrue(content.contains("GET"));
+        assertTrue(content.contains("HEAD"));
+        assertTrue(content.contains("OPTIONS"));
+    }
+
+}
diff --git a/containers/jetty-servlet/pom.xml b/containers/jetty-servlet/pom.xml
new file mode 100644
index 0000000..8ddf7b4
--- /dev/null
+++ b/containers/jetty-servlet/pom.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.glassfish.jersey.containers</groupId>
+        <artifactId>project</artifactId>
+        <version>2.28-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>jersey-container-jetty-servlet</artifactId>
+    <packaging>jar</packaging>
+    <name>jersey-container-jetty-servlet</name>
+
+    <description>Jetty Servlet Container</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.glassfish.jersey.containers</groupId>
+            <artifactId>jersey-container-servlet</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.glassfish.jersey.containers</groupId>
+            <artifactId>jersey-container-jetty-http</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-webapp</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <resources>
+            <resource>
+                <directory>${basedir}/src/main/java</directory>
+                <includes>
+                    <include>META-INF/**/*</include>
+                </includes>
+            </resource>
+            <resource>
+                <directory>${basedir}/src/main/resources</directory>
+                <filtering>true</filtering>
+            </resource>
+        </resources>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <inherited>true</inherited>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Import-Package>
+                            javax.servlet.*;version="[2.4,5.0)",
+                            *
+                        </Import-Package>
+                    </instructions>
+                    <unpackBundle>true</unpackBundle>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/containers/jetty-servlet/src/main/java/org/glassfish/jersey/jetty/servlet/JettyWebContainerFactory.java b/containers/jetty-servlet/src/main/java/org/glassfish/jersey/jetty/servlet/JettyWebContainerFactory.java
new file mode 100644
index 0000000..dd585ce
--- /dev/null
+++ b/containers/jetty-servlet/src/main/java/org/glassfish/jersey/jetty/servlet/JettyWebContainerFactory.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.jetty.servlet;
+
+import java.net.URI;
+import java.util.Map;
+
+import javax.servlet.Servlet;
+
+import org.glassfish.jersey.jetty.JettyHttpContainerFactory;
+import org.glassfish.jersey.servlet.ServletContainer;
+import org.glassfish.jersey.uri.UriComponent;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.webapp.Configuration;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.eclipse.jetty.webapp.WebXmlConfiguration;
+
+/**
+ * Factory for creating and starting Jetty {@link Server} instances
+ * for deploying a Servlet.
+ * <p/>
+ * The default deployed server is an instance of {@link ServletContainer}.
+ * <p/>
+ * If no initialization parameters are declared (or is null) then root
+ * resource and provider classes will be found by searching the classes
+ * referenced in the java classpath.
+ *
+ * @author Arul Dhesiaseelan (aruld at acm.org)
+ */
+public final class JettyWebContainerFactory {
+
+    private JettyWebContainerFactory() {
+    }
+
+    /**
+     * Create a {@link Server} that registers the {@link ServletContainer}.
+     *
+     * @param u the URI to create the http server. The URI scheme must be
+     *          equal to "http". The URI user information and host
+     *          are ignored If the URI port is not present then port 80 will be
+     *          used. The URI query and fragment components are ignored. Only first path segment will be used
+     *          as context path, the rest will be ignored.
+     * @return the http server, with the endpoint started.
+     * @throws Exception                if an error occurs creating the container.
+     * @throws IllegalArgumentException if HTTP server URI is {@code null}.
+     */
+    public static Server create(String u)
+            throws Exception {
+        if (u == null) {
+            throw new IllegalArgumentException("The URI must not be null");
+        }
+
+        return create(URI.create(u));
+    }
+
+    /**
+     * Create a {@link Server} that registers the {@link ServletContainer}.
+     *
+     * @param u          the URI to create the http server. The URI scheme must be
+     *                   equal to "http". The URI user information and host
+     *                   are ignored If the URI port is not present then port 80 will be
+     *                   used. The URI query and fragment components are ignored. Only first path segment will be used
+     *                   as context path, the rest will be ignored.
+     * @param initParams the servlet initialization parameters.
+     * @return the http server, with the endpoint started.
+     * @throws Exception                if an error occurs creating the container.
+     * @throws IllegalArgumentException if HTTP server URI is {@code null}.
+     */
+    public static Server create(String u, Map<String, String> initParams)
+            throws Exception {
+        if (u == null) {
+            throw new IllegalArgumentException("The URI must not be null");
+        }
+
+        return create(URI.create(u), initParams);
+    }
+
+    /**
+     * Create a {@link Server} that registers the {@link ServletContainer}.
+     *
+     * @param u the URI to create the http server. The URI scheme must be
+     *          equal to "http". The URI user information and host
+     *          are ignored If the URI port is not present then port 80 will be
+     *          used. The URI query and fragment components are ignored. Only first path segment will be used
+     *          as context path, the rest will be ignored.
+     * @return the http server, with the endpoint started.
+     * @throws Exception                if an error occurs creating the container.
+     * @throws IllegalArgumentException if HTTP server URI is {@code null}.
+     */
+    public static Server create(URI u)
+            throws Exception {
+        return create(u, ServletContainer.class);
+    }
+
+    /**
+     * Create a {@link Server} that registers the {@link ServletContainer}.
+     *
+     * @param u          the URI to create the http server. The URI scheme must be
+     *                   equal to "http". The URI user information and host
+     *                   are ignored If the URI port is not present then port 80 will be
+     *                   used. The URI query and fragment components are ignored. Only first path segment will be used
+     *                   as context path, the rest will be ignored.
+     * @param initParams the servlet initialization parameters.
+     * @return the http server, with the endpoint started.
+     * @throws Exception                if an error occurs creating the container.
+     * @throws IllegalArgumentException if HTTP server URI is {@code null}.
+     */
+    public static Server create(URI u, Map<String, String> initParams)
+            throws Exception {
+        return create(u, ServletContainer.class, initParams);
+    }
+
+    /**
+     * Create a {@link Server} that registers the declared
+     * servlet class.
+     *
+     * @param u the URI to create the http server. The URI scheme must be
+     *          equal to "http". The URI user information and host
+     *          are ignored If the URI port is not present then port 80 will be
+     *          used. The URI query and fragment components are ignored. Only first path segment will be used
+     *          as context path, the rest will be ignored.
+     * @param c the servlet class.
+     * @return the http server, with the endpoint started.
+     * @throws Exception                if an error occurs creating the container.
+     * @throws IllegalArgumentException if HTTP server URI is {@code null}.
+     */
+    public static Server create(String u, Class<? extends Servlet> c)
+            throws Exception {
+        if (u == null) {
+            throw new IllegalArgumentException("The URI must not be null");
+        }
+
+        return create(URI.create(u), c);
+    }
+
+    /**
+     * Create a {@link Server} that registers the declared
+     * servlet class.
+     *
+     * @param u          the URI to create the http server. The URI scheme must be
+     *                   equal to "http". The URI user information and host
+     *                   are ignored If the URI port is not present then port 80 will be
+     *                   used. The URI query and fragment components are ignored. Only first path segment will be used
+     *                   as context path, the rest will be ignored.
+     * @param c          the servlet class.
+     * @param initParams the servlet initialization parameters.
+     * @return the http server, with the endpoint started.
+     * @throws Exception                if an error occurs creating the container.
+     * @throws IllegalArgumentException if HTTP server URI is {@code null}.
+     */
+    public static Server create(String u, Class<? extends Servlet> c,
+                                Map<String, String> initParams)
+            throws Exception {
+        if (u == null) {
+            throw new IllegalArgumentException("The URI must not be null");
+        }
+
+        return create(URI.create(u), c, initParams);
+    }
+
+    /**
+     * Create a {@link Server} that registers the declared
+     * servlet class.
+     *
+     * @param u the URI to create the http server. The URI scheme must be
+     *          equal to "http". The URI user information and host
+     *          are ignored If the URI port is not present then port 80 will be
+     *          used. The URI query and fragment components are ignored. Only first path segment will be used
+     *          as context path, the rest will be ignored.
+     * @param c the servlet class.
+     * @return the http server, with the endpoint started.
+     * @throws Exception                if an error occurs creating the container.
+     * @throws IllegalArgumentException if HTTP server URI is {@code null}.
+     */
+    public static Server create(URI u, Class<? extends Servlet> c)
+            throws Exception {
+        return create(u, c, null);
+    }
+
+    /**
+     * Create a {@link Server} that registers the declared
+     * servlet class.
+     *
+     * @param u          the URI to create the http server. The URI scheme must be
+     *                   equal to "http". The URI user information and host
+     *                   are ignored If the URI port is not present then port 80 will be
+     *                   used. The URI query and fragment components are ignored. Only first path segment will be used
+     *                   as context path, the rest will be ignored.
+     * @param c          the servlet class.
+     * @param initParams the servlet initialization parameters.
+     * @return the http server, with the endpoint started.
+     * @throws Exception                if an error occurs creating the container.
+     * @throws IllegalArgumentException if HTTP server URI is {@code null}.
+     */
+    public static Server create(URI u, Class<? extends Servlet> c, Map<String, String> initParams)
+            throws Exception {
+        return create(u, c, null, initParams, null);
+    }
+
+    private static Server create(URI u, Class<? extends Servlet> c, Servlet servlet,
+                                 Map<String, String> initParams, Map<String, String> contextInitParams)
+            throws Exception {
+        if (u == null) {
+            throw new IllegalArgumentException("The URI must not be null");
+        }
+
+        String path = u.getPath();
+        if (path == null) {
+            throw new IllegalArgumentException("The URI path, of the URI " + u + ", must be non-null");
+        } else if (path.isEmpty()) {
+            throw new IllegalArgumentException("The URI path, of the URI " + u + ", must be present");
+        } else if (path.charAt(0) != '/') {
+            throw new IllegalArgumentException("The URI path, of the URI " + u + ". must start with a '/'");
+        }
+
+        path = String.format("/%s", UriComponent.decodePath(u.getPath(), true).get(1).toString());
+        WebAppContext context = new WebAppContext();
+        context.setDisplayName("JettyContext");
+        context.setContextPath(path);
+        context.setConfigurations(new Configuration[]{new WebXmlConfiguration()});
+        ServletHolder holder;
+        if (c != null) {
+            holder = context.addServlet(c, "/*");
+        } else {
+            holder = new ServletHolder(servlet);
+            context.addServlet(holder, "/*");
+        }
+
+        if (contextInitParams != null) {
+            for (Map.Entry<String, String> e : contextInitParams.entrySet()) {
+                context.setInitParameter(e.getKey(), e.getValue());
+            }
+        }
+
+        if (initParams != null) {
+            holder.setInitParameters(initParams);
+        }
+
+        Server server = JettyHttpContainerFactory.createServer(u, false);
+        server.setHandler(context);
+        server.start();
+        return server;
+    }
+
+    /**
+     * Create a {@link Server} that registers the declared
+     * servlet instance.
+     *
+     * @param u                 the URI to create the HTTP server. The URI scheme must be
+     *                          equal to "http". The URI user information and host
+     *                          are ignored If the URI port is not present then port 80 will be
+     *                          used. The URI query and fragment components are ignored. Only first path segment will be used
+     *                          as context path, the rest will be ignored.
+     * @param servlet           the servlet instance.
+     * @param initParams        the servlet initialization parameters.
+     * @param contextInitParams the servlet context initialization parameters.
+     * @return the http server, with the endpoint started.
+     * @throws Exception                if an error occurs creating the container.
+     * @throws IllegalArgumentException if HTTP server URI is {@code null}.
+     */
+    public static Server create(URI u, Servlet servlet, Map<String, String> initParams, Map<String, String> contextInitParams)
+            throws Exception {
+        if (servlet == null) {
+            throw new IllegalArgumentException("The servlet must not be null");
+        }
+        return create(u, null, servlet, initParams, contextInitParams);
+    }
+}
diff --git a/containers/jetty-servlet/src/main/java/org/glassfish/jersey/jetty/servlet/package-info.java b/containers/jetty-servlet/src/main/java/org/glassfish/jersey/jetty/servlet/package-info.java
new file mode 100644
index 0000000..0a643c3
--- /dev/null
+++ b/containers/jetty-servlet/src/main/java/org/glassfish/jersey/jetty/servlet/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+/**
+ * Jersey Jetty Servlet container classes.
+ */
+package org.glassfish.jersey.jetty.servlet;
diff --git a/containers/netty-http/pom.xml b/containers/netty-http/pom.xml
new file mode 100644
index 0000000..beb0545
--- /dev/null
+++ b/containers/netty-http/pom.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Copyright (c) 2016, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.glassfish.jersey.containers</groupId>
+        <artifactId>project</artifactId>
+        <version>2.28-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>jersey-container-netty-http</artifactId>
+    <packaging>jar</packaging>
+    <name>jersey-container-netty-http</name>
+
+    <description>Netty Http Container.</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.glassfish.hk2.external</groupId>
+            <artifactId>javax.inject</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.jersey.connectors</groupId>
+            <artifactId>jersey-netty-connector</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>com.sun.istack</groupId>
+                <artifactId>maven-istack-commons-plugin</artifactId>
+                <inherited>true</inherited>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>build-helper-maven-plugin</artifactId>
+                <inherited>true</inherited>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <inherited>true</inherited>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/HttpVersionChooser.java b/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/HttpVersionChooser.java
new file mode 100644
index 0000000..22660e2
--- /dev/null
+++ b/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/HttpVersionChooser.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2016, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.netty.httpserver;
+
+import java.net.URI;
+
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.http.HttpServerCodec;
+import io.netty.handler.codec.http2.Http2Codec;
+import io.netty.handler.ssl.ApplicationProtocolNames;
+import io.netty.handler.ssl.ApplicationProtocolNegotiationHandler;
+import io.netty.handler.stream.ChunkedWriteHandler;
+
+/**
+ * Choose the handler implementation based on Http protocol.
+ *
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ */
+class HttpVersionChooser extends ApplicationProtocolNegotiationHandler {
+
+    private final URI baseUri;
+    private final NettyHttpContainer container;
+
+    HttpVersionChooser(URI baseUri, NettyHttpContainer container) {
+        super(ApplicationProtocolNames.HTTP_1_1);
+
+        this.baseUri = baseUri;
+        this.container = container;
+    }
+
+    @Override
+    protected void configurePipeline(ChannelHandlerContext ctx, String protocol) throws Exception {
+        if (ApplicationProtocolNames.HTTP_2.equals(protocol)) {
+            ctx.pipeline().addLast(new Http2Codec(true, new JerseyHttp2ServerHandler(baseUri, container)));
+            return;
+        }
+
+        if (ApplicationProtocolNames.HTTP_1_1.equals(protocol)) {
+            ctx.pipeline().addLast(new HttpServerCodec(),
+                                   new ChunkedWriteHandler(),
+                                   new JerseyServerHandler(baseUri, container));
+            return;
+        }
+
+        throw new IllegalStateException("Unknown protocol: " + protocol);
+    }
+}
diff --git a/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/JerseyHttp2ServerHandler.java b/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/JerseyHttp2ServerHandler.java
new file mode 100644
index 0000000..ddb1089
--- /dev/null
+++ b/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/JerseyHttp2ServerHandler.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (c) 2016, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.netty.httpserver;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.LinkedBlockingDeque;
+
+import javax.ws.rs.core.SecurityContext;
+
+import io.netty.buffer.ByteBufInputStream;
+import io.netty.channel.ChannelDuplexHandler;
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.http.HttpRequest;
+import io.netty.handler.codec.http2.Http2DataFrame;
+import io.netty.handler.codec.http2.Http2HeadersFrame;
+import io.netty.util.concurrent.Future;
+import io.netty.util.concurrent.GenericFutureListener;
+import org.glassfish.jersey.internal.PropertiesDelegate;
+import org.glassfish.jersey.netty.connector.internal.NettyInputStream;
+import org.glassfish.jersey.server.ContainerRequest;
+import org.glassfish.jersey.server.internal.ContainerUtils;
+
+/**
+ * Jersey Netty HTTP/2 handler.
+ * <p>
+ * Note that this implementation cannot be more experimental. Any contributions / feedback is welcomed.
+ *
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ */
+@ChannelHandler.Sharable
+class JerseyHttp2ServerHandler extends ChannelDuplexHandler {
+
+    private final URI baseUri;
+    private final LinkedBlockingDeque<InputStream> isList = new LinkedBlockingDeque<>();
+    private final NettyHttpContainer container;
+
+    /**
+     * Constructor.
+     *
+     * @param baseUri   base {@link URI} of the container (includes context path, if any).
+     * @param container Netty container implementation.
+     */
+    JerseyHttp2ServerHandler(URI baseUri, NettyHttpContainer container) {
+        this.baseUri = baseUri;
+        this.container = container;
+    }
+
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
+        ctx.close();
+    }
+
+    @Override
+    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+        if (msg instanceof Http2HeadersFrame) {
+            onHeadersRead(ctx, (Http2HeadersFrame) msg);
+        } else if (msg instanceof Http2DataFrame) {
+            onDataRead(ctx, (Http2DataFrame) msg);
+        } else {
+            super.channelRead(ctx, msg);
+        }
+    }
+
+    /**
+     * Process incoming data.
+     */
+    private void onDataRead(ChannelHandlerContext ctx, Http2DataFrame data) throws Exception {
+        isList.add(new ByteBufInputStream(data.content()));
+        if (data.isEndStream()) {
+            isList.add(NettyInputStream.END_OF_INPUT);
+        }
+    }
+
+    /**
+     * Process incoming request (just a headers in this case, entity is processed separately).
+     */
+    private void onHeadersRead(ChannelHandlerContext ctx, Http2HeadersFrame headers) throws Exception {
+
+        final ContainerRequest requestContext = createContainerRequest(ctx, headers);
+
+        requestContext.setWriter(new NettyHttp2ResponseWriter(ctx, headers, container));
+
+        // must be like this, since there is a blocking read from Jersey
+        container.getExecutorService().execute(new Runnable() {
+            @Override
+            public void run() {
+                container.getApplicationHandler().handle(requestContext);
+            }
+        });
+    }
+
+    /**
+     * Create Jersey {@link ContainerRequest} based on Netty {@link HttpRequest}.
+     *
+     * @param ctx          Netty channel context.
+     * @param http2Headers Netty Http/2 headers.
+     * @return created Jersey Container Request.
+     */
+    private ContainerRequest createContainerRequest(ChannelHandlerContext ctx, Http2HeadersFrame http2Headers) {
+
+        String path = http2Headers.headers().path().toString();
+
+        String s = path.startsWith("/") ? path.substring(1) : path;
+        URI requestUri = URI.create(baseUri + ContainerUtils.encodeUnsafeCharacters(s));
+
+        ContainerRequest requestContext = new ContainerRequest(
+                baseUri, requestUri, http2Headers.headers().method().toString(), getSecurityContext(),
+                new PropertiesDelegate() {
+
+                    private final Map<String, Object> properties = new HashMap<>();
+
+                    @Override
+                    public Object getProperty(String name) {
+                        return properties.get(name);
+                    }
+
+                    @Override
+                    public Collection<String> getPropertyNames() {
+                        return properties.keySet();
+                    }
+
+                    @Override
+                    public void setProperty(String name, Object object) {
+                        properties.put(name, object);
+                    }
+
+                    @Override
+                    public void removeProperty(String name) {
+                        properties.remove(name);
+                    }
+                });
+
+        // request entity handling.
+        if (!http2Headers.isEndStream()) {
+
+            ctx.channel().closeFuture().addListener(new GenericFutureListener<Future<? super Void>>() {
+                @Override
+                public void operationComplete(Future<? super Void> future) throws Exception {
+                    isList.add(NettyInputStream.END_OF_INPUT_ERROR);
+                }
+            });
+
+            requestContext.setEntityStream(new NettyInputStream(isList));
+        } else {
+            requestContext.setEntityStream(new InputStream() {
+                @Override
+                public int read() throws IOException {
+                    return -1;
+                }
+            });
+        }
+
+        // copying headers from netty request to jersey container request context.
+        for (CharSequence name : http2Headers.headers().names()) {
+            requestContext.headers(name.toString(), mapToString(http2Headers.headers().getAll(name)));
+        }
+
+        return requestContext;
+    }
+
+    private List<String> mapToString(List<CharSequence> list) {
+        ArrayList<String> result = new ArrayList<>(list.size());
+
+        for (CharSequence sequence : list) {
+            result.add(sequence.toString());
+        }
+
+        return result;
+    }
+
+    private SecurityContext getSecurityContext() {
+        return new SecurityContext() {
+
+            @Override
+            public boolean isUserInRole(final String role) {
+                return false;
+            }
+
+            @Override
+            public boolean isSecure() {
+                return false;
+            }
+
+            @Override
+            public Principal getUserPrincipal() {
+                return null;
+            }
+
+            @Override
+            public String getAuthenticationScheme() {
+                return null;
+            }
+        };
+    }
+}
diff --git a/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/JerseyServerHandler.java b/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/JerseyServerHandler.java
new file mode 100644
index 0000000..33437e2
--- /dev/null
+++ b/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/JerseyServerHandler.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (c) 2016, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.netty.httpserver;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.security.Principal;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.LinkedBlockingDeque;
+
+import javax.ws.rs.core.SecurityContext;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufInputStream;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.handler.codec.http.DefaultFullHttpResponse;
+import io.netty.handler.codec.http.HttpContent;
+import io.netty.handler.codec.http.HttpHeaderNames;
+import io.netty.handler.codec.http.HttpRequest;
+import io.netty.handler.codec.http.HttpResponseStatus;
+import io.netty.handler.codec.http.HttpUtil;
+import io.netty.handler.codec.http.HttpVersion;
+import io.netty.handler.codec.http.LastHttpContent;
+import io.netty.util.concurrent.Future;
+import io.netty.util.concurrent.GenericFutureListener;
+import org.glassfish.jersey.internal.PropertiesDelegate;
+import org.glassfish.jersey.netty.connector.internal.NettyInputStream;
+import org.glassfish.jersey.server.ContainerRequest;
+import org.glassfish.jersey.server.internal.ContainerUtils;
+
+/**
+ * {@link io.netty.channel.ChannelInboundHandler} which servers as a bridge
+ * between Netty and Jersey.
+ *
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ */
+class JerseyServerHandler extends ChannelInboundHandlerAdapter {
+
+    private final URI baseUri;
+    private final LinkedBlockingDeque<InputStream> isList = new LinkedBlockingDeque<>();
+    private final NettyHttpContainer container;
+
+    /**
+     * Constructor.
+     *
+     * @param baseUri   base {@link URI} of the container (includes context path, if any).
+     * @param container Netty container implementation.
+     */
+    public JerseyServerHandler(URI baseUri, NettyHttpContainer container) {
+        this.baseUri = baseUri;
+        this.container = container;
+    }
+
+    @Override
+    public void channelRead(final ChannelHandlerContext ctx, Object msg) {
+
+        if (msg instanceof HttpRequest) {
+            final HttpRequest req = (HttpRequest) msg;
+
+            if (HttpUtil.is100ContinueExpected(req)) {
+                ctx.write(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE));
+            }
+
+            isList.clear(); // clearing the content - possible leftover from previous request processing.
+            final ContainerRequest requestContext = createContainerRequest(ctx, req);
+
+            requestContext.setWriter(new NettyResponseWriter(ctx, req, container));
+
+            // must be like this, since there is a blocking read from Jersey
+            container.getExecutorService().execute(new Runnable() {
+                @Override
+                public void run() {
+                    container.getApplicationHandler().handle(requestContext);
+                }
+            });
+        }
+
+        if (msg instanceof HttpContent) {
+            HttpContent httpContent = (HttpContent) msg;
+
+            ByteBuf content = httpContent.content();
+
+            if (content.isReadable()) {
+                isList.add(new ByteBufInputStream(content));
+            }
+
+            if (msg instanceof LastHttpContent) {
+                isList.add(NettyInputStream.END_OF_INPUT);
+            }
+        }
+    }
+
+    /**
+     * Create Jersey {@link ContainerRequest} based on Netty {@link HttpRequest}.
+     *
+     * @param ctx Netty channel context.
+     * @param req Netty Http request.
+     * @return created Jersey Container Request.
+     */
+    private ContainerRequest createContainerRequest(ChannelHandlerContext ctx, HttpRequest req) {
+
+        String s = req.uri().startsWith("/") ? req.uri().substring(1) : req.uri();
+        URI requestUri = URI.create(baseUri + ContainerUtils.encodeUnsafeCharacters(s));
+
+        ContainerRequest requestContext = new ContainerRequest(
+                baseUri, requestUri, req.method().name(), getSecurityContext(),
+                new PropertiesDelegate() {
+
+                    private final Map<String, Object> properties = new HashMap<>();
+
+                    @Override
+                    public Object getProperty(String name) {
+                        return properties.get(name);
+                    }
+
+                    @Override
+                    public Collection<String> getPropertyNames() {
+                        return properties.keySet();
+                    }
+
+                    @Override
+                    public void setProperty(String name, Object object) {
+                        properties.put(name, object);
+                    }
+
+                    @Override
+                    public void removeProperty(String name) {
+                        properties.remove(name);
+                    }
+                });
+
+        // request entity handling.
+        if ((req.headers().contains(HttpHeaderNames.CONTENT_LENGTH) && HttpUtil.getContentLength(req) > 0)
+                || HttpUtil.isTransferEncodingChunked(req)) {
+
+            ctx.channel().closeFuture().addListener(new GenericFutureListener<Future<? super Void>>() {
+                @Override
+                public void operationComplete(Future<? super Void> future) throws Exception {
+                    isList.add(NettyInputStream.END_OF_INPUT_ERROR);
+                }
+            });
+
+            requestContext.setEntityStream(new NettyInputStream(isList));
+        } else {
+            requestContext.setEntityStream(new InputStream() {
+                @Override
+                public int read() throws IOException {
+                    return -1;
+                }
+            });
+        }
+
+        // copying headers from netty request to jersey container request context.
+        for (String name : req.headers().names()) {
+            requestContext.headers(name, req.headers().getAll(name));
+        }
+
+        return requestContext;
+    }
+
+    private SecurityContext getSecurityContext() {
+        return new SecurityContext() {
+
+            @Override
+            public boolean isUserInRole(final String role) {
+                return false;
+            }
+
+            @Override
+            public boolean isSecure() {
+                return false;
+            }
+
+            @Override
+            public Principal getUserPrincipal() {
+                return null;
+            }
+
+            @Override
+            public String getAuthenticationScheme() {
+                return null;
+            }
+        };
+    }
+
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
+        ctx.close();
+    }
+
+}
diff --git a/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/JerseyServerInitializer.java b/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/JerseyServerInitializer.java
new file mode 100644
index 0000000..16a6ce0
--- /dev/null
+++ b/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/JerseyServerInitializer.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2016, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.netty.httpserver;
+
+import java.net.URI;
+
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelPipeline;
+import io.netty.channel.SimpleChannelInboundHandler;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.handler.codec.http.HttpMessage;
+import io.netty.handler.codec.http.HttpServerCodec;
+import io.netty.handler.codec.http.HttpServerUpgradeHandler;
+import io.netty.handler.codec.http2.Http2Codec;
+import io.netty.handler.codec.http2.Http2CodecUtil;
+import io.netty.handler.codec.http2.Http2ServerUpgradeCodec;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import io.netty.util.AsciiString;
+
+/**
+ * Jersey {@link ChannelInitializer}.
+ * <p>
+ * Adds {@link HttpServerCodec}, {@link ChunkedWriteHandler} and {@link JerseyServerHandler} to the channels pipeline.
+ *
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ */
+class JerseyServerInitializer extends ChannelInitializer<SocketChannel> {
+
+    private final URI baseUri;
+    private final SslContext sslCtx;
+    private final NettyHttpContainer container;
+    private final boolean http2;
+
+    /**
+     * Constructor.
+     *
+     * @param baseUri   base {@link URI} of the container (includes context path, if any).
+     * @param sslCtx    SSL context.
+     * @param container Netty container implementation.
+     */
+    public JerseyServerInitializer(URI baseUri, SslContext sslCtx, NettyHttpContainer container) {
+        this(baseUri, sslCtx, container, false);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param baseUri   base {@link URI} of the container (includes context path, if any).
+     * @param sslCtx    SSL context.
+     * @param container Netty container implementation.
+     * @param http2     Http/2 protocol support.
+     */
+    public JerseyServerInitializer(URI baseUri, SslContext sslCtx, NettyHttpContainer container, boolean http2) {
+        this.baseUri = baseUri;
+        this.sslCtx = sslCtx;
+        this.container = container;
+        this.http2 = http2;
+    }
+
+    @Override
+    public void initChannel(SocketChannel ch) {
+        if (http2) {
+
+            if (sslCtx != null) {
+                configureSsl(ch);
+            } else {
+                configureClearText(ch);
+            }
+
+        } else {
+            ChannelPipeline p = ch.pipeline();
+            if (sslCtx != null) {
+                p.addLast(sslCtx.newHandler(ch.alloc()));
+            }
+            p.addLast(new HttpServerCodec());
+            p.addLast(new ChunkedWriteHandler());
+            p.addLast(new JerseyServerHandler(baseUri, container));
+        }
+    }
+
+    /**
+     * Configure the pipeline for TLS NPN negotiation to HTTP/2.
+     */
+    private void configureSsl(SocketChannel ch) {
+        ch.pipeline().addLast(sslCtx.newHandler(ch.alloc()), new HttpVersionChooser(baseUri, container));
+    }
+
+    /**
+     * Configure the pipeline for a cleartext upgrade from HTTP to HTTP/2.
+     */
+    private void configureClearText(SocketChannel ch) {
+        final ChannelPipeline p = ch.pipeline();
+        final HttpServerCodec sourceCodec = new HttpServerCodec();
+
+        p.addLast(sourceCodec);
+        p.addLast(new HttpServerUpgradeHandler(sourceCodec, new HttpServerUpgradeHandler.UpgradeCodecFactory() {
+            @Override
+            public HttpServerUpgradeHandler.UpgradeCodec newUpgradeCodec(CharSequence protocol) {
+                if (AsciiString.contentEquals(Http2CodecUtil.HTTP_UPGRADE_PROTOCOL_NAME, protocol)) {
+                    return new Http2ServerUpgradeCodec(new Http2Codec(true, new JerseyHttp2ServerHandler(baseUri, container)));
+                } else {
+                    return null;
+                }
+            }
+        }));
+        p.addLast(new SimpleChannelInboundHandler<HttpMessage>() {
+            @Override
+            protected void channelRead0(ChannelHandlerContext ctx, HttpMessage msg) throws Exception {
+                // If this handler is hit then no upgrade has been attempted and the client is just talking HTTP.
+                // "Directly talking: " + msg.protocolVersion() + " (no upgrade was attempted)");
+
+                ChannelPipeline pipeline = ctx.pipeline();
+                ChannelHandlerContext thisCtx = pipeline.context(this);
+                pipeline.addAfter(thisCtx.name(), null, new JerseyServerHandler(baseUri, container));
+                pipeline.replace(this, null, new ChunkedWriteHandler());
+                ctx.fireChannelRead(msg);
+            }
+        });
+    }
+}
diff --git a/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/NettyHttp2ResponseWriter.java b/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/NettyHttp2ResponseWriter.java
new file mode 100644
index 0000000..327d894
--- /dev/null
+++ b/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/NettyHttp2ResponseWriter.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) 2016, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.netty.httpserver;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import org.glassfish.jersey.server.ContainerException;
+import org.glassfish.jersey.server.ContainerResponse;
+import org.glassfish.jersey.server.spi.ContainerResponseWriter;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.http.DefaultFullHttpResponse;
+import io.netty.handler.codec.http.HttpHeaderNames;
+import io.netty.handler.codec.http.HttpMethod;
+import io.netty.handler.codec.http.HttpResponseStatus;
+import io.netty.handler.codec.http2.DefaultHttp2DataFrame;
+import io.netty.handler.codec.http2.DefaultHttp2Headers;
+import io.netty.handler.codec.http2.DefaultHttp2HeadersFrame;
+import io.netty.handler.codec.http2.Http2HeadersFrame;
+
+/**
+ * Netty implementation of {@link ContainerResponseWriter}.
+ *
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ */
+class NettyHttp2ResponseWriter implements ContainerResponseWriter {
+
+    private final ChannelHandlerContext ctx;
+    private final Http2HeadersFrame headersFrame;
+    private final NettyHttpContainer container;
+
+    private volatile ScheduledFuture<?> suspendTimeoutFuture;
+    private volatile Runnable suspendTimeoutHandler;
+
+    NettyHttp2ResponseWriter(ChannelHandlerContext ctx, Http2HeadersFrame headersFrame, NettyHttpContainer container) {
+        this.ctx = ctx;
+        this.headersFrame = headersFrame;
+        this.container = container;
+    }
+
+    @Override
+    public OutputStream writeResponseStatusAndHeaders(long contentLength, ContainerResponse responseContext)
+            throws ContainerException {
+
+        String reasonPhrase = responseContext.getStatusInfo().getReasonPhrase();
+        int statusCode = responseContext.getStatus();
+
+        HttpResponseStatus status = reasonPhrase == null
+                ? HttpResponseStatus.valueOf(statusCode)
+                : new HttpResponseStatus(statusCode, reasonPhrase);
+
+        DefaultHttp2Headers response = new DefaultHttp2Headers();
+        response.status(Integer.toString(responseContext.getStatus()));
+
+        for (final Map.Entry<String, List<String>> e : responseContext.getStringHeaders().entrySet()) {
+            response.add(e.getKey().toLowerCase(), e.getValue());
+        }
+
+        response.set(HttpHeaderNames.CONTENT_LENGTH, Long.toString(contentLength));
+
+        ctx.writeAndFlush(new DefaultHttp2HeadersFrame(response));
+
+        if (!headersFrame.headers().method().equals(HttpMethod.HEAD.asciiName())
+            && (contentLength > 0 || contentLength == -1)) {
+
+            return new OutputStream() {
+                @Override
+                public void write(int b) throws IOException {
+                    write(new byte[]{(byte) b});
+                }
+
+                @Override
+                public void write(byte[] b) throws IOException {
+                    write(b, 0, b.length);
+                }
+
+                @Override
+                public void write(byte[] b, int off, int len) throws IOException {
+
+                    ByteBuf buffer = ctx.alloc().buffer(len);
+                    buffer.writeBytes(b, off, len);
+
+                    ctx.writeAndFlush(new DefaultHttp2DataFrame(buffer, false));
+                }
+
+                @Override
+                public void flush() throws IOException {
+                    ctx.flush();
+                }
+
+                @Override
+                public void close() throws IOException {
+                    ctx.write(new DefaultHttp2DataFrame(true)).addListener(NettyResponseWriter.FLUSH_FUTURE);
+                }
+            };
+
+        } else {
+            ctx.writeAndFlush(new DefaultHttp2DataFrame(true));
+            return null;
+        }
+    }
+
+    @Override
+    public boolean suspend(long timeOut, TimeUnit timeUnit, final ContainerResponseWriter.TimeoutHandler
+            timeoutHandler) {
+
+        suspendTimeoutHandler = new Runnable() {
+            @Override
+            public void run() {
+                timeoutHandler.onTimeout(NettyHttp2ResponseWriter.this);
+            }
+        };
+
+        if (timeOut <= 0) {
+            return true;
+        }
+
+        suspendTimeoutFuture =
+                container.getScheduledExecutorService().schedule(suspendTimeoutHandler, timeOut, timeUnit);
+
+        return true;
+    }
+
+    @Override
+    public void setSuspendTimeout(long timeOut, TimeUnit timeUnit) throws IllegalStateException {
+
+        // suspend(0, .., ..) was called, so suspendTimeoutFuture is null.
+        if (suspendTimeoutFuture != null) {
+            suspendTimeoutFuture.cancel(true);
+        }
+
+        if (timeOut <= 0) {
+            return;
+        }
+
+        suspendTimeoutFuture =
+                container.getScheduledExecutorService().schedule(suspendTimeoutHandler, timeOut, timeUnit);
+    }
+
+    @Override
+    public void commit() {
+        ctx.flush();
+    }
+
+    @Override
+    public void failure(Throwable error) {
+        ctx.writeAndFlush(new DefaultHttp2Headers().status(HttpResponseStatus.INTERNAL_SERVER_ERROR.codeAsText()))
+           .addListener(ChannelFutureListener.CLOSE);
+    }
+
+    @Override
+    public boolean enableResponseBuffering() {
+        return true;
+    }
+}
diff --git a/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/NettyHttpContainer.java b/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/NettyHttpContainer.java
new file mode 100644
index 0000000..c825a82
--- /dev/null
+++ b/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/NettyHttpContainer.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2016, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.netty.httpserver;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ScheduledExecutorService;
+
+import javax.ws.rs.core.Application;
+
+import org.glassfish.jersey.server.ApplicationHandler;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.spi.Container;
+import org.glassfish.jersey.spi.ExecutorServiceProvider;
+import org.glassfish.jersey.spi.ScheduledExecutorServiceProvider;
+
+/**
+ * Netty based implementation of a {@link Container}.
+ *
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ */
+class NettyHttpContainer implements Container {
+
+    private volatile ApplicationHandler appHandler;
+
+    public NettyHttpContainer(Application application) {
+        this.appHandler = new ApplicationHandler(application);
+        this.appHandler.onStartup(this);
+    }
+
+    @Override
+    public ResourceConfig getConfiguration() {
+        return appHandler.getConfiguration();
+    }
+
+    @Override
+    public ApplicationHandler getApplicationHandler() {
+        return appHandler;
+    }
+
+    @Override
+    public void reload() {
+        reload(appHandler.getConfiguration());
+    }
+
+    @Override
+    public void reload(ResourceConfig configuration) {
+        appHandler.onShutdown(this);
+
+        appHandler = new ApplicationHandler(configuration);
+        appHandler.onReload(this);
+        appHandler.onStartup(this);
+    }
+
+    /**
+     * Get {@link java.util.concurrent.ExecutorService}.
+     *
+     * @return Executor service associated with this container.
+     */
+    ExecutorService getExecutorService() {
+        return appHandler.getInjectionManager().getInstance(ExecutorServiceProvider.class).getExecutorService();
+    }
+
+    /**
+     * Get {@link ScheduledExecutorService}.
+     *
+     * @return Scheduled executor service associated with this container.
+     */
+    ScheduledExecutorService getScheduledExecutorService() {
+        return appHandler.getInjectionManager().getInstance(ScheduledExecutorServiceProvider.class).getExecutorService();
+    }
+}
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
new file mode 100644
index 0000000..124b36e
--- /dev/null
+++ b/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/NettyHttpContainerProvider.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 2016, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.netty.httpserver;
+
+import java.net.URI;
+
+import javax.ws.rs.ProcessingException;
+import javax.ws.rs.core.Application;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.ssl.SslContext;
+import io.netty.util.concurrent.Future;
+import io.netty.util.concurrent.GenericFutureListener;
+import org.glassfish.jersey.Beta;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.spi.ContainerProvider;
+
+/**
+ * Netty implementation of {@link ContainerProvider}.
+ * <p>
+ * There is also a few "factory" methods for creating Netty server.
+ *
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ * @since 2.24
+ */
+@Beta
+public class NettyHttpContainerProvider implements ContainerProvider {
+
+    @Override
+    public <T> T createContainer(Class<T> type, Application application) throws ProcessingException {
+        if (NettyHttpContainer.class == type) {
+            return type.cast(new NettyHttpContainer(application));
+        }
+
+        return null;
+    }
+
+    /**
+     * Create and start Netty server.
+     *
+     * @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 {
+
+        // Configure the server.
+        final EventLoopGroup bossGroup = new NioEventLoopGroup(1);
+        final EventLoopGroup workerGroup = new NioEventLoopGroup();
+        final NettyHttpContainer container = new NettyHttpContainer(configuration);
+
+        try {
+            ServerBootstrap b = new ServerBootstrap();
+            b.option(ChannelOption.SO_BACKLOG, 1024);
+            b.group(bossGroup, workerGroup)
+             .channel(NioServerSocketChannel.class)
+             .childHandler(new JerseyServerInitializer(baseUri, sslContext, container));
+
+            int port = getPort(baseUri);
+
+            Channel ch = b.bind(port).sync().channel();
+
+            ch.closeFuture().addListener(new GenericFutureListener<Future<? super Void>>() {
+                @Override
+                public void operationComplete(Future<? super Void> future) throws Exception {
+                    container.getApplicationHandler().onShutdown(container);
+
+                    bossGroup.shutdownGracefully();
+                    workerGroup.shutdownGracefully();
+                }
+            });
+
+            if (block) {
+                ch.closeFuture().sync();
+                return ch;
+            } else {
+                return ch;
+            }
+        } catch (InterruptedException e) {
+            throw new ProcessingException(e);
+        }
+    }
+
+    /**
+     * 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.
+     * @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, final boolean block)
+            throws ProcessingException {
+
+        return createServer(baseUri, configuration, null, block);
+    }
+
+    /**
+     * Create and start Netty HTTP/2 server.
+     * <p>
+     * The server is capable of connection upgrade to HTTP/2. HTTP/1.x request will be server as they were used to.
+     * <p>
+     * Note that this implementation cannot be more experimental. Any contributions / feedback is welcomed.
+     *
+     * @param baseUri       base uri.
+     * @param configuration Jersey configuration.
+     * @param sslContext    Netty {@link SslContext}.
+     * @return Netty channel instance.
+     * @throws ProcessingException when there is an issue with creating new container.
+     */
+    public static Channel createHttp2Server(final URI baseUri, final ResourceConfig configuration, SslContext sslContext) throws
+            ProcessingException {
+
+        final EventLoopGroup bossGroup = new NioEventLoopGroup(1);
+        final EventLoopGroup workerGroup = new NioEventLoopGroup();
+        final NettyHttpContainer container = new NettyHttpContainer(configuration);
+
+        try {
+            ServerBootstrap b = new ServerBootstrap();
+            b.option(ChannelOption.SO_BACKLOG, 1024);
+            b.group(bossGroup, workerGroup)
+             .channel(NioServerSocketChannel.class)
+             .childHandler(new JerseyServerInitializer(baseUri, sslContext, container, true));
+
+            int port = getPort(baseUri);
+
+            Channel ch = b.bind(port).sync().channel();
+
+            ch.closeFuture().addListener(new GenericFutureListener<Future<? super Void>>() {
+                @Override
+                public void operationComplete(Future<? super Void> future) throws Exception {
+                    container.getApplicationHandler().onShutdown(container);
+
+                    bossGroup.shutdownGracefully();
+                    workerGroup.shutdownGracefully();
+                }
+            });
+
+            return ch;
+
+        } catch (InterruptedException e) {
+            throw new ProcessingException(e);
+        }
+    }
+
+    private static int getPort(URI uri) {
+        if (uri.getPort() == -1) {
+            if ("http".equalsIgnoreCase(uri.getScheme())) {
+                return 80;
+            } else if ("https".equalsIgnoreCase(uri.getScheme())) {
+                return 443;
+            }
+
+            throw new IllegalArgumentException("URI scheme must be 'http' or 'https'.");
+        }
+
+        return uri.getPort();
+    }
+}
diff --git a/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/NettyResponseWriter.java b/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/NettyResponseWriter.java
new file mode 100644
index 0000000..e50d870
--- /dev/null
+++ b/containers/netty-http/src/main/java/org/glassfish/jersey/netty/httpserver/NettyResponseWriter.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (c) 2016, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.netty.httpserver;
+
+import java.io.OutputStream;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.glassfish.jersey.netty.connector.internal.JerseyChunkedInput;
+import org.glassfish.jersey.server.ContainerException;
+import org.glassfish.jersey.server.ContainerResponse;
+import org.glassfish.jersey.server.spi.ContainerResponseWriter;
+
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.http.DefaultFullHttpResponse;
+import io.netty.handler.codec.http.DefaultHttpResponse;
+import io.netty.handler.codec.http.HttpChunkedInput;
+import io.netty.handler.codec.http.HttpHeaderNames;
+import io.netty.handler.codec.http.HttpHeaderValues;
+import io.netty.handler.codec.http.HttpMethod;
+import io.netty.handler.codec.http.HttpRequest;
+import io.netty.handler.codec.http.HttpResponseStatus;
+import io.netty.handler.codec.http.HttpUtil;
+import io.netty.handler.codec.http.LastHttpContent;
+
+/**
+ * Netty implementation of {@link ContainerResponseWriter}.
+ *
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ */
+class NettyResponseWriter implements ContainerResponseWriter {
+
+    private static final Logger LOGGER = Logger.getLogger(NettyResponseWriter.class.getName());
+
+    static final ChannelFutureListener FLUSH_FUTURE = new ChannelFutureListener() {
+        @Override
+        public void operationComplete(ChannelFuture future) throws Exception {
+            future.channel().flush();
+        }
+    };
+
+    private final ChannelHandlerContext ctx;
+    private final HttpRequest req;
+    private final NettyHttpContainer container;
+
+    private volatile ScheduledFuture<?> suspendTimeoutFuture;
+    private volatile Runnable suspendTimeoutHandler;
+
+    private boolean responseWritten = false;
+
+    NettyResponseWriter(ChannelHandlerContext ctx, HttpRequest req, NettyHttpContainer container) {
+        this.ctx = ctx;
+        this.req = req;
+        this.container = container;
+    }
+
+    @Override
+    public synchronized OutputStream writeResponseStatusAndHeaders(long contentLength, ContainerResponse responseContext)
+            throws ContainerException {
+
+        if (responseWritten) {
+            LOGGER.log(Level.FINE, "Response already written.");
+            return null;
+        }
+
+        responseWritten = true;
+
+        String reasonPhrase = responseContext.getStatusInfo().getReasonPhrase();
+        int statusCode = responseContext.getStatus();
+
+        HttpResponseStatus status = reasonPhrase == null
+                ? HttpResponseStatus.valueOf(statusCode)
+                : new HttpResponseStatus(statusCode, reasonPhrase);
+
+        DefaultHttpResponse response;
+        if (contentLength == 0) {
+            response = new DefaultFullHttpResponse(req.protocolVersion(), status);
+        } else {
+            response = new DefaultHttpResponse(req.protocolVersion(), status);
+        }
+
+        for (final Map.Entry<String, List<String>> e : responseContext.getStringHeaders().entrySet()) {
+            response.headers().add(e.getKey(), e.getValue());
+        }
+
+        if (contentLength == -1) {
+            HttpUtil.setTransferEncodingChunked(response, true);
+        } else {
+            response.headers().set(HttpHeaderNames.CONTENT_LENGTH, contentLength);
+        }
+
+        if (HttpUtil.isKeepAlive(req)) {
+            response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
+        }
+
+        ctx.writeAndFlush(response);
+
+        if (req.method() != HttpMethod.HEAD && (contentLength > 0 || contentLength == -1)) {
+
+            JerseyChunkedInput jerseyChunkedInput = new JerseyChunkedInput(ctx.channel());
+
+            if (HttpUtil.isTransferEncodingChunked(response)) {
+                ctx.write(new HttpChunkedInput(jerseyChunkedInput)).addListener(FLUSH_FUTURE);
+            } else {
+                ctx.write(new HttpChunkedInput(jerseyChunkedInput)).addListener(FLUSH_FUTURE);
+            }
+            return jerseyChunkedInput;
+
+        } else {
+            ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
+            return null;
+        }
+    }
+
+    @Override
+    public boolean suspend(long timeOut, TimeUnit timeUnit, final ContainerResponseWriter.TimeoutHandler
+            timeoutHandler) {
+
+        suspendTimeoutHandler = new Runnable() {
+            @Override
+            public void run() {
+                timeoutHandler.onTimeout(NettyResponseWriter.this);
+            }
+        };
+
+        if (timeOut <= 0) {
+            return true;
+        }
+
+        suspendTimeoutFuture =
+                container.getScheduledExecutorService().schedule(suspendTimeoutHandler, timeOut, timeUnit);
+
+        return true;
+    }
+
+    @Override
+    public void setSuspendTimeout(long timeOut, TimeUnit timeUnit) throws IllegalStateException {
+
+        // suspend(0, .., ..) was called, so suspendTimeoutFuture is null.
+        if (suspendTimeoutFuture != null) {
+            suspendTimeoutFuture.cancel(true);
+        }
+
+        if (timeOut <= 0) {
+            return;
+        }
+
+        suspendTimeoutFuture =
+                container.getScheduledExecutorService().schedule(suspendTimeoutHandler, timeOut, timeUnit);
+    }
+
+    @Override
+    public void commit() {
+        ctx.flush();
+    }
+
+    @Override
+    public void failure(Throwable error) {
+        ctx.writeAndFlush(new DefaultFullHttpResponse(req.protocolVersion(), HttpResponseStatus.INTERNAL_SERVER_ERROR))
+           .addListener(ChannelFutureListener.CLOSE);
+    }
+
+    @Override
+    public boolean enableResponseBuffering() {
+        return true;
+    }
+}
diff --git a/containers/netty-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ContainerProvider b/containers/netty-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ContainerProvider
new file mode 100644
index 0000000..96ee927
--- /dev/null
+++ b/containers/netty-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ContainerProvider
@@ -0,0 +1 @@
+org.glassfish.jersey.netty.httpserver.NettyHttpContainerProvider
diff --git a/containers/pom.xml b/containers/pom.xml
new file mode 100644
index 0000000..2246a14
--- /dev/null
+++ b/containers/pom.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.glassfish.jersey</groupId>
+        <artifactId>project</artifactId>
+        <version>2.28-SNAPSHOT</version>
+    </parent>
+
+    <groupId>org.glassfish.jersey.containers</groupId>
+    <artifactId>project</artifactId>
+    <packaging>pom</packaging>
+    <name>jersey-containers</name>
+
+    <description>Jersey container providers umbrella project module</description>
+
+    <modules>
+        <module>glassfish</module>
+        <module>grizzly2-http</module>
+        <module>grizzly2-servlet</module>
+        <module>jdk-http</module>
+        <module>jersey-servlet-core</module>
+        <module>jersey-servlet</module>
+        <module>jetty-http</module>
+        <module>jetty-servlet</module>
+        <module>netty-http</module>
+        <module>simple-http</module>
+    </modules>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.glassfish.jersey.core</groupId>
+            <artifactId>jersey-common</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.jersey.core</groupId>
+            <artifactId>jersey-server</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>javax.ws.rs</groupId>
+            <artifactId>javax.ws.rs-api</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.jersey.inject</groupId>
+            <artifactId>jersey-hk2</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/containers/simple-http/pom.xml b/containers/simple-http/pom.xml
new file mode 100644
index 0000000..e3bfb38
--- /dev/null
+++ b/containers/simple-http/pom.xml
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.glassfish.jersey.containers</groupId>
+        <artifactId>project</artifactId>
+        <version>2.28-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>jersey-container-simple-http</artifactId>
+    <packaging>jar</packaging>
+    <name>jersey-container-simple-http</name>
+
+    <description>Simple Http Container</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.glassfish.hk2.external</groupId>
+            <artifactId>javax.inject</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.simpleframework</groupId>
+            <artifactId>simple-http</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.simpleframework</groupId>
+            <artifactId>simple-transport</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.simpleframework</groupId>
+            <artifactId>simple-common</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>com.sun.istack</groupId>
+                <artifactId>maven-istack-commons-plugin</artifactId>
+                <inherited>true</inherited>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>build-helper-maven-plugin</artifactId>
+                <inherited>true</inherited>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <inherited>true</inherited>
+            </plugin>
+        </plugins>
+
+        <resources>
+            <resource>
+                <directory>${basedir}/src/main/java</directory>
+                <includes>
+                    <include>META-INF/**/*</include>
+                </includes>
+            </resource>
+            <resource>
+                <directory>${basedir}/src/main/resources</directory>
+                <filtering>true</filtering>
+            </resource>
+        </resources>
+    </build>
+
+</project>
diff --git a/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleContainer.java b/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleContainer.java
new file mode 100644
index 0000000..e59962f
--- /dev/null
+++ b/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleContainer.java
@@ -0,0 +1,468 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.simple;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.reflect.Type;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.Principal;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.SecurityContext;
+
+import javax.inject.Inject;
+import javax.inject.Provider;
+
+import org.glassfish.jersey.internal.MapPropertiesDelegate;
+import org.glassfish.jersey.internal.inject.AbstractBinder;
+import org.glassfish.jersey.internal.inject.ReferencingFactory;
+import org.glassfish.jersey.internal.util.ExtendedLogger;
+import org.glassfish.jersey.internal.util.collection.Ref;
+import org.glassfish.jersey.process.internal.RequestScoped;
+import org.glassfish.jersey.server.ApplicationHandler;
+import org.glassfish.jersey.server.ContainerException;
+import org.glassfish.jersey.server.ContainerRequest;
+import org.glassfish.jersey.server.ContainerResponse;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.internal.ContainerUtils;
+import org.glassfish.jersey.server.spi.Container;
+import org.glassfish.jersey.server.spi.ContainerResponseWriter;
+import org.glassfish.jersey.server.spi.ContainerResponseWriter.TimeoutHandler;
+
+import org.simpleframework.common.thread.DaemonFactory;
+import org.simpleframework.http.Address;
+import org.simpleframework.http.Request;
+import org.simpleframework.http.Response;
+import org.simpleframework.http.Status;
+
+/**
+ * Jersey {@code Container} implementation based on Simple framework
+ * {@link org.simpleframework.http.core.Container}.
+ *
+ * @author Arul Dhesiaseelan (aruld@acm.org)
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public final class SimpleContainer implements org.simpleframework.http.core.Container, Container {
+
+    private static final ExtendedLogger logger =
+            new ExtendedLogger(Logger.getLogger(SimpleContainer.class.getName()), Level.FINEST);
+
+    private final Type RequestTYPE = (new GenericType<Ref<Request>>() { }).getType();
+    private final Type ResponseTYPE = (new GenericType<Ref<Response>>() { }).getType();
+
+    /**
+     * Referencing factory for Simple request.
+     */
+    private static class SimpleRequestReferencingFactory extends ReferencingFactory<Request> {
+
+        @Inject
+        public SimpleRequestReferencingFactory(final Provider<Ref<Request>> referenceFactory) {
+            super(referenceFactory);
+        }
+    }
+
+    /**
+     * Referencing factory for Simple response.
+     */
+    private static class SimpleResponseReferencingFactory extends ReferencingFactory<Response> {
+
+        @Inject
+        public SimpleResponseReferencingFactory(final Provider<Ref<Response>> referenceFactory) {
+            super(referenceFactory);
+        }
+    }
+
+    /**
+     * An internal binder to enable Simple HTTP container specific types injection. This binder allows
+     * to inject underlying Grizzly HTTP request and response instances.
+     */
+    private static class SimpleBinder extends AbstractBinder {
+
+        @Override
+        protected void configure() {
+            bindFactory(SimpleRequestReferencingFactory.class).to(Request.class).proxy(true)
+                                                              .proxyForSameScope(false).in(RequestScoped.class);
+            bindFactory(ReferencingFactory.<Request>referenceFactory())
+                    .to(new GenericType<Ref<Request>>() {
+                    }).in(RequestScoped.class);
+
+            bindFactory(SimpleResponseReferencingFactory.class).to(Response.class).proxy(true)
+                                                               .proxyForSameScope(false).in(RequestScoped.class);
+            bindFactory(ReferencingFactory.<Response>referenceFactory())
+                    .to(new GenericType<Ref<Response>>() {
+                    }).in(RequestScoped.class);
+        }
+    }
+
+    private volatile ScheduledExecutorService scheduler;
+    private volatile ApplicationHandler appHandler;
+
+    private static final class ResponseWriter implements ContainerResponseWriter {
+
+        private final AtomicReference<TimeoutTimer> reference;
+        private final ScheduledExecutorService scheduler;
+        private final Response response;
+
+        ResponseWriter(final Response response, final ScheduledExecutorService scheduler) {
+            this.reference = new AtomicReference<TimeoutTimer>();
+            this.response = response;
+            this.scheduler = scheduler;
+        }
+
+        @Override
+        public OutputStream writeResponseStatusAndHeaders(final long contentLength,
+                                                          final ContainerResponse context) throws ContainerException {
+            final javax.ws.rs.core.Response.StatusType statusInfo = context.getStatusInfo();
+
+            final int code = statusInfo.getStatusCode();
+            final String reason = statusInfo.getReasonPhrase() == null
+                    ? Status.getDescription(code)
+                    : statusInfo.getReasonPhrase();
+            response.setCode(code);
+            response.setDescription(reason);
+
+            if (contentLength != -1) {
+                response.setContentLength(contentLength);
+            }
+            for (final Map.Entry<String, List<String>> e : context.getStringHeaders().entrySet()) {
+                for (final String value : e.getValue()) {
+                    response.addValue(e.getKey(), value);
+                }
+            }
+
+            try {
+                return response.getOutputStream();
+            } catch (final IOException ioe) {
+                throw new ContainerException("Error during writing out the response headers.", ioe);
+            }
+        }
+
+        @Override
+        public boolean suspend(final long timeOut, final TimeUnit timeUnit,
+                               final TimeoutHandler timeoutHandler) {
+            try {
+                TimeoutTimer timer = reference.get();
+
+                if (timer == null) {
+                    TimeoutDispatcher task = new TimeoutDispatcher(this, timeoutHandler);
+                    ScheduledFuture<?> future =
+                            scheduler.schedule(task, timeOut == 0 ? Integer.MAX_VALUE : timeOut,
+                                    timeOut == 0 ? TimeUnit.SECONDS : timeUnit);
+                    timer = new TimeoutTimer(scheduler, future, task);
+                    reference.set(timer);
+                    return true;
+                }
+                return false;
+            } catch (final IllegalStateException ex) {
+                return false;
+            } finally {
+                logger.debugLog("suspend(...) called");
+            }
+        }
+
+        @Override
+        public void setSuspendTimeout(final long timeOut, final TimeUnit timeUnit)
+                throws IllegalStateException {
+            try {
+                TimeoutTimer timer = reference.get();
+
+                if (timer == null) {
+                    throw new IllegalStateException("Response has not been suspended");
+                }
+                timer.reschedule(timeOut, timeUnit);
+            } finally {
+                logger.debugLog("setTimeout(...) called");
+            }
+        }
+
+        @Override
+        public void commit() {
+            try {
+                response.close();
+            } catch (final IOException e) {
+                logger.log(Level.SEVERE, "Unable to send 500 error response.", e);
+            } finally {
+                logger.debugLog("commit() called");
+            }
+        }
+
+        public boolean isSuspended() {
+            return reference.get() != null;
+        }
+
+        @Override
+        public void failure(final Throwable error) {
+            try {
+                if (!response.isCommitted()) {
+                    response.setCode(javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR.getStatusCode());
+                    response.setDescription(error.getMessage());
+                }
+            } finally {
+                logger.debugLog("failure(...) called");
+                commit();
+                rethrow(error);
+            }
+
+        }
+
+        @Override
+        public boolean enableResponseBuffering() {
+            return false;
+        }
+
+        /**
+         * Rethrow the original exception as required by JAX-RS, 3.3.4
+         *
+         * @param error throwable to be re-thrown
+         */
+        private void rethrow(final Throwable error) {
+            if (error instanceof RuntimeException) {
+                throw (RuntimeException) error;
+            } else {
+                throw new ContainerException(error);
+            }
+        }
+
+    }
+
+    private static final class TimeoutTimer {
+
+        private final AtomicReference<ScheduledFuture<?>> reference;
+        private final ScheduledExecutorService service;
+        private final TimeoutDispatcher task;
+
+        public TimeoutTimer(ScheduledExecutorService service, ScheduledFuture<?> future,
+                            TimeoutDispatcher task) {
+            this.reference = new AtomicReference<ScheduledFuture<?>>();
+            this.service = service;
+            this.task = task;
+        }
+
+        public void reschedule(long timeOut, TimeUnit timeUnit) {
+            ScheduledFuture<?> future = reference.getAndSet(null);
+
+            if (future != null) {
+                if (future.cancel(false)) {
+                    future = service.schedule(task, timeOut == 0 ? Integer.MAX_VALUE : timeOut,
+                            timeOut == 0 ? TimeUnit.SECONDS : timeUnit);
+                    reference.set(future);
+                }
+            } else {
+                future = service.schedule(task, timeOut == 0 ? Integer.MAX_VALUE : timeOut,
+                        timeOut == 0 ? TimeUnit.SECONDS : timeUnit);
+                reference.set(future);
+            }
+        }
+    }
+
+    private static final class TimeoutDispatcher implements Runnable {
+
+        private final ResponseWriter writer;
+        private final TimeoutHandler handler;
+
+        public TimeoutDispatcher(ResponseWriter writer, TimeoutHandler handler) {
+            this.writer = writer;
+            this.handler = handler;
+        }
+
+        public void run() {
+            try {
+                handler.onTimeout(writer);
+            } catch (Exception e) {
+                logger.log(Level.INFO, "Failed to call timeout handler", e);
+            }
+        }
+    }
+
+    @Override
+    public void handle(final Request request, final Response response) {
+        final ResponseWriter responseWriter = new ResponseWriter(response, scheduler);
+        final URI baseUri = getBaseUri(request);
+        final URI requestUri = getRequestUri(request, baseUri);
+
+        try {
+            final ContainerRequest requestContext = new ContainerRequest(baseUri, requestUri,
+                    request.getMethod(), getSecurityContext(request), new MapPropertiesDelegate());
+            requestContext.setEntityStream(request.getInputStream());
+            for (final String headerName : request.getNames()) {
+                requestContext.headers(headerName, request.getValue(headerName));
+            }
+            requestContext.setWriter(responseWriter);
+            requestContext.setRequestScopedInitializer(injectionManager -> {
+                injectionManager.<Ref<Request>>getInstance(RequestTYPE).set(request);
+                injectionManager.<Ref<Response>>getInstance(ResponseTYPE).set(response);
+            });
+
+            appHandler.handle(requestContext);
+        } catch (final Exception ex) {
+            throw new RuntimeException(ex);
+        } finally {
+            if (!responseWriter.isSuspended()) {
+                close(response);
+            }
+        }
+    }
+
+    private URI getRequestUri(final Request request, final URI baseUri) {
+        try {
+            final String serverAddress = getServerAddress(baseUri);
+            String uri = ContainerUtils.getHandlerPath(request.getTarget());
+
+            final String queryString = request.getQuery().toString();
+            if (queryString != null) {
+                uri = uri + "?" + ContainerUtils.encodeUnsafeCharacters(queryString);
+            }
+
+            return new URI(serverAddress + uri);
+        } catch (URISyntaxException ex) {
+            throw new IllegalArgumentException(ex);
+        }
+    }
+
+    private String getServerAddress(final URI baseUri) throws URISyntaxException {
+        return new URI(baseUri.getScheme(), null, baseUri.getHost(), baseUri.getPort(), null, null,
+                null).toString();
+    }
+
+    private URI getBaseUri(final Request request) {
+        try {
+            final String hostHeader = request.getValue("Host");
+
+            if (hostHeader != null) {
+                final String scheme = request.isSecure() ? "https" : "http";
+                return new URI(scheme + "://" + hostHeader + "/");
+            } else {
+                final Address address = request.getAddress();
+                return new URI(address.getScheme(), null, address.getDomain(), address.getPort(), "/", null,
+                        null);
+            }
+        } catch (final URISyntaxException ex) {
+            throw new IllegalArgumentException(ex);
+        }
+    }
+
+    private SecurityContext getSecurityContext(final Request request) {
+        return new SecurityContext() {
+
+            @Override
+            public boolean isUserInRole(final String role) {
+                return false;
+            }
+
+            @Override
+            public boolean isSecure() {
+                return request.isSecure();
+            }
+
+            @Override
+            public Principal getUserPrincipal() {
+                return null;
+            }
+
+            @Override
+            public String getAuthenticationScheme() {
+                return null;
+            }
+        };
+    }
+
+    private void close(final Response response) {
+        try {
+            response.close();
+        } catch (final Exception ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    @Override
+    public ResourceConfig getConfiguration() {
+        return appHandler.getConfiguration();
+    }
+
+    @Override
+    public void reload() {
+        reload(getConfiguration());
+    }
+
+    @Override
+    public void reload(final ResourceConfig configuration) {
+        appHandler.onShutdown(this);
+
+        appHandler = new ApplicationHandler(configuration.register(new SimpleBinder()));
+        scheduler = new ScheduledThreadPoolExecutor(2, new DaemonFactory(TimeoutDispatcher.class));
+        appHandler.onReload(this);
+        appHandler.onStartup(this);
+    }
+
+    @Override
+    public ApplicationHandler getApplicationHandler() {
+        return appHandler;
+    }
+
+    /**
+     * Inform this container that the server has been started.
+     * <p/>
+     * This method must be implicitly called after the server containing this container is started.
+     */
+    void onServerStart() {
+        appHandler.onStartup(this);
+    }
+
+    /**
+     * Inform this container that the server is being stopped.
+     * <p/>
+     * This method must be implicitly called before the server containing this container is stopped.
+     */
+    void onServerStop() {
+        appHandler.onShutdown(this);
+        scheduler.shutdown();
+    }
+
+    /**
+     * Create a new Simple framework HTTP container.
+     *
+     * @param application   JAX-RS / Jersey application to be deployed on Simple framework HTTP container.
+     * @param parentContext DI provider specific context with application's registered bindings.
+     */
+    SimpleContainer(final Application application, final Object parentContext) {
+        this.appHandler = new ApplicationHandler(application, new SimpleBinder(), parentContext);
+        this.scheduler = new ScheduledThreadPoolExecutor(2, new DaemonFactory(TimeoutDispatcher.class));
+    }
+
+    /**
+     * Create a new Simple framework HTTP container.
+     *
+     * @param application JAX-RS / Jersey application to be deployed on Simple framework HTTP
+     *                    container.
+     */
+    SimpleContainer(final Application application) {
+        this.appHandler = new ApplicationHandler(application, new SimpleBinder());
+        this.scheduler = new ScheduledThreadPoolExecutor(2, new DaemonFactory(TimeoutDispatcher.class));
+    }
+}
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
new file mode 100644
index 0000000..a9dc46d
--- /dev/null
+++ b/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleContainerFactory.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.simple;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.net.URI;
+
+import javax.ws.rs.ProcessingException;
+
+import javax.net.ssl.SSLContext;
+
+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.SocketProcessor;
+import org.simpleframework.transport.connect.Connection;
+import org.simpleframework.transport.connect.SocketConnection;
+
+/**
+ * Factory for creating and starting Simple server containers. This returns a handle to the started
+ * server as {@link Closeable} instances, which allows the server to be stopped by invoking the
+ * {@link Closeable#close} method.
+ * <p/>
+ * To start the server in HTTPS mode an {@link SSLContext} can be provided. This will be used to
+ * decrypt and encrypt information sent over the connected TCP socket channel.
+ *
+ * @author Arul Dhesiaseelan (aruld at acm.org)
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ * @author Paul Sandoz
+ */
+public final class SimpleContainerFactory {
+
+    private SimpleContainerFactory() {
+    }
+
+    /**
+     * Create a {@link Closeable} that registers an {@link Container} that in turn manages all root
+     * resource and provider classes declared by the resource configuration.
+     *
+     * @param address the URI to create the http server. The URI scheme must be equal to "http". The
+     *                URI user information and host are ignored If the URI port is not present then port 80
+     *                will be used. The URI path, query and fragment components are ignored.
+     * @param config  the resource configuration.
+     * @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 ResourceConfig config) {
+        return create(address, null, new SimpleContainer(config));
+    }
+
+    /**
+     * Create a {@link Closeable} that registers an {@link Container} that in turn manages all root
+     * resource and provider classes declared by the resource configuration.
+     *
+     * @param address the URI to create the http server. The URI scheme must be equal to "http". The
+     *                URI user information and host are ignored If the URI port is not present then port 80
+     *                will be used. The URI path, query and fragment components are ignored.
+     * @param config  the resource configuration.
+     * @param count   this is the number of threads to be used.
+     * @param select  this is the number of selector threads to use.
+     * @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 ResourceConfig config, final int count,
+                                      final int select) {
+        return create(address, null, new SimpleContainer(config), count, select);
+    }
+
+    /**
+     * Create a {@link Closeable} that registers an {@link Container} that in turn manages all root
+     * resource and provider classes declared by the resource configuration.
+     *
+     * @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.
+     * @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 ResourceConfig config) {
+        return create(address, context, new SimpleContainer(config));
+    }
+
+    /**
+     * Create a {@link Closeable} that registers an {@link Container} that in turn manages all root
+     * resource and provider classes declared by the resource configuration.
+     *
+     * @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 count   this is the number of threads to be used.
+     * @param select  this is the number of selector threads to use.
+     * @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 ResourceConfig config, final int count, final int select) {
+        return create(address, context, new SimpleContainer(config), count, select);
+    }
+
+    /**
+     * 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 container the container that handles all HTTP requests.
+     * @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 SimpleContainer container) {
+        return _create(address, context, container, new UnsafeValue<SocketProcessor, IOException>() {
+            @Override
+            public SocketProcessor get() throws IOException {
+                return new ContainerSocketProcessor(container);
+            }
+        });
+    }
+
+    /**
+     * Create a {@link Closeable} that registers an {@link Container} that in turn manages all root
+     * resource and provider classes declared by the resource configuration.
+     *
+     * @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 parentContext DI provider specific context with application's registered bindings.
+     * @param count         this is the number of threads to be used.
+     * @param select        this is the number of selector threads to use.
+     * @return the closeable connection, with the endpoint started.
+     * @throws ProcessingException      thrown when problems during server creation.
+     * @throws IllegalArgumentException if {@code address} is {@code null}.
+     * @since 2.12
+     */
+    public static SimpleServer create(final URI address, final SSLContext context,
+                                      final ResourceConfig config, final Object parentContext, final int count,
+                                      final int select) {
+        return create(address, context, new SimpleContainer(config, parentContext), count, select);
+    }
+
+    /**
+     * 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 container the container that handles all HTTP requests.
+     * @param count     this is the number of threads to be used.
+     * @param select    this is the number of selector threads to use.
+     * @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 SimpleContainer container, final int count, final int select)
+            throws ProcessingException {
+
+        return _create(address, context, container, new UnsafeValue<SocketProcessor, IOException>() {
+            @Override
+            public SocketProcessor get() throws IOException {
+                return new ContainerSocketProcessor(container, count, select);
+            }
+        });
+    }
+
+    private static SimpleServer _create(final URI address, final SSLContext context,
+                                        final SimpleContainer container,
+                                        final UnsafeValue<SocketProcessor, IOException> serverProvider)
+            throws ProcessingException {
+        if (address == null) {
+            throw new IllegalArgumentException(LocalizationMessages.URI_CANNOT_BE_NULL());
+        }
+        final String scheme = address.getScheme();
+        int defaultPort = org.glassfish.jersey.server.spi.Container.DEFAULT_HTTP_PORT;
+
+        if (context == null) {
+            if (!scheme.equalsIgnoreCase("http")) {
+                throw new IllegalArgumentException(LocalizationMessages.WRONG_SCHEME_WHEN_USING_HTTP());
+            }
+        } else {
+            if (!scheme.equalsIgnoreCase("https")) {
+                throw new IllegalArgumentException(LocalizationMessages.WRONG_SCHEME_WHEN_USING_HTTPS());
+            }
+            defaultPort = org.glassfish.jersey.server.spi.Container.DEFAULT_HTTPS_PORT;
+        }
+        int port = address.getPort();
+
+        if (port == -1) {
+            port = defaultPort;
+        }
+        final InetSocketAddress listen = new InetSocketAddress(port);
+        final Connection connection;
+        try {
+            final SimpleTraceAnalyzer analyzer = new SimpleTraceAnalyzer();
+            final SocketProcessor server = serverProvider.get();
+            connection = new SocketConnection(server, analyzer);
+
+
+            final SocketAddress socketAddr = connection.connect(listen, context);
+            container.onServerStart();
+
+            return new SimpleServer() {
+
+                @Override
+                public void close() throws IOException {
+                    container.onServerStop();
+                    analyzer.stop();
+                    connection.close();
+                }
+
+                @Override
+                public int getPort() {
+                    return ((InetSocketAddress) socketAddr).getPort();
+                }
+
+                @Override
+                public boolean isDebug() {
+                    return analyzer.isActive();
+                }
+
+                @Override
+                public void setDebug(boolean enable) {
+                    if (enable) {
+                        analyzer.start();
+                    } else {
+                        analyzer.stop();
+                    }
+                }
+            };
+        } 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/SimpleContainerProvider.java b/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleContainerProvider.java
new file mode 100644
index 0000000..68f8f0f
--- /dev/null
+++ b/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleContainerProvider.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.simple;
+
+import javax.ws.rs.ProcessingException;
+import javax.ws.rs.core.Application;
+
+import org.glassfish.jersey.server.spi.ContainerProvider;
+
+import org.simpleframework.http.core.Container;
+
+/**
+ * Container provider for containers based on Simple HTTP Server
+ * {@link org.simpleframework.http.core.Container}.
+ *
+ * @author Marc Hadley
+ * @author Arul Dhesiaseelan (aruld@acm.org)
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public final class SimpleContainerProvider implements ContainerProvider {
+
+    @Override
+    public <T> T createContainer(Class<T> type, Application application) throws ProcessingException {
+        if (Container.class == type || SimpleContainer.class == type) {
+            return type.cast(new SimpleContainer(application));
+        }
+        return 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
new file mode 100644
index 0000000..02aec75
--- /dev/null
+++ b/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleServer.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.simple;
+
+import java.io.Closeable;
+
+/**
+ * Simple server facade providing convenient methods to obtain info about the server (i.e. port).
+ *
+ * @author Michal Gajdos
+ * @since 2.9
+ */
+public interface SimpleServer extends Closeable {
+
+    /**
+     * 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.
+     *
+     * @return the port the server is listening on
+     */
+    public int getPort();
+
+    /**
+     * If this is true then very low level I/O operations are logged. Typically this is used to debug
+     * I/O issues such as HTTPS handshakes or performance issues by analysing the various latencies
+     * involved in the HTTP conversation.
+     * <p/>
+     * There is a minimal performance penalty if this is enabled and it is perfectly suited to being
+     * enabled in a production environment, at the cost of logging overhead.
+     *
+     * @return {@code true} if debug is enabled, false otherwise.
+     * @since 2.23
+     */
+    public boolean isDebug();
+
+    /**
+     * To enable very low level logging this can be enabled. This goes far beyond logging issues such
+     * as connection establishment of request dispatch, it can trace the TCP operations latencies
+     * involved.
+     *
+     * @param enable if {@code true} debug tracing will be enabled.
+     * @since 2.23
+     */
+    public void setDebug(boolean enable);
+}
diff --git a/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleTraceAnalyzer.java b/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleTraceAnalyzer.java
new file mode 100644
index 0000000..4873da1
--- /dev/null
+++ b/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleTraceAnalyzer.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.simple;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.nio.channels.SelectableChannel;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.glassfish.jersey.internal.util.ExtendedLogger;
+
+import org.simpleframework.common.thread.DaemonFactory;
+import org.simpleframework.transport.trace.Trace;
+import org.simpleframework.transport.trace.TraceAnalyzer;
+
+/**
+ * Tracing at a very low level can be performed with a {@link TraceAnalyzer}. This provides much
+ * more useful information than the conventional {@link LoggingFilter} in that it provides details
+ * at a very low level. This is very useful when monitoring performance interactions at the TCP
+ * level between clients and servers.
+ * <p/>
+ * Performance overhead for the server is minimal as events are pumped out in batches. The amount of
+ * logging information will increase quite significantly though.
+ *
+ * @author Niall Gallagher
+ */
+public class SimpleTraceAnalyzer implements TraceAnalyzer {
+
+    private static final ExtendedLogger logger =
+            new ExtendedLogger(Logger.getLogger(SimpleTraceAnalyzer.class.getName()), Level.FINEST);
+
+    private final TraceConsumer consumer;
+    private final ThreadFactory factory;
+    private final AtomicBoolean active;
+    private final AtomicLong count;
+
+    /**
+     * Creates an asynchronous trace event logger.
+     */
+    public SimpleTraceAnalyzer() {
+        this.factory = new DaemonFactory(TraceConsumer.class);
+        this.consumer = new TraceConsumer();
+        this.active = new AtomicBoolean();
+        this.count = new AtomicLong();
+    }
+
+    public boolean isActive() {
+        return active.get();
+    }
+
+    @Override
+    public Trace attach(SelectableChannel channel) {
+        long sequence = count.getAndIncrement();
+        return new TraceFeeder(channel, sequence);
+    }
+
+    /**
+     * Begin logging trace events to the underlying logger.
+     */
+    public void start() {
+        if (active.compareAndSet(false, true)) {
+            Thread thread = factory.newThread(consumer);
+            thread.start();
+        }
+    }
+
+    @Override
+    public void stop() {
+        active.set(false);
+    }
+
+    private class TraceConsumer implements Runnable {
+
+        private final Queue<TraceRecord> queue;
+
+        public TraceConsumer() {
+            this.queue = new ConcurrentLinkedQueue<TraceRecord>();
+        }
+
+        public void consume(TraceRecord record) {
+            queue.offer(record);
+        }
+
+        public void run() {
+            try {
+                while (active.get()) {
+                    Thread.sleep(1000);
+                    drain();
+                }
+            } catch (Exception e) {
+                logger.info("Trace analyzer error");
+            } finally {
+                try {
+                    drain();
+                } catch (Exception e) {
+                    logger.info("Trace analyzer could not drain queue");
+                }
+                active.set(false);
+            }
+
+        }
+
+        private void drain() {
+            while (!queue.isEmpty()) {
+                TraceRecord record = queue.poll();
+
+                if (record != null) {
+                    String message = record.toString();
+                    logger.info(message);
+                }
+            }
+        }
+    }
+
+    private class TraceFeeder implements Trace {
+
+        private final SelectableChannel channel;
+        private final long sequence;
+
+        public TraceFeeder(SelectableChannel channel, long sequence) {
+            this.sequence = sequence;
+            this.channel = channel;
+        }
+
+        @Override
+        public void trace(Object event) {
+            trace(event, null);
+        }
+
+        @Override
+        public void trace(Object event, Object value) {
+            if (active.get()) {
+                TraceRecord record = new TraceRecord(channel, event, value, sequence);
+                consumer.consume(record);
+            }
+        }
+
+    }
+
+    private class TraceRecord {
+
+        private final SelectableChannel channel;
+        private final String thread;
+        private final Object event;
+        private final Object value;
+        private final long sequence;
+
+        public TraceRecord(SelectableChannel channel, Object event, Object value, long sequence) {
+            this.thread = Thread.currentThread().getName();
+            this.sequence = sequence;
+            this.channel = channel;
+            this.event = event;
+            this.value = value;
+        }
+
+        public String toString() {
+            StringWriter builder = new StringWriter();
+            PrintWriter writer = new PrintWriter(builder);
+
+            writer.print(sequence);
+            writer.print(" ");
+            writer.print(channel);
+            writer.print(" (");
+            writer.print(thread);
+            writer.print("): ");
+            writer.print(event);
+
+            if (value != null) {
+                if (value instanceof Throwable) {
+                    writer.print(" -> ");
+                    ((Throwable) value).printStackTrace(writer);
+                } else {
+                    writer.print(" -> ");
+                    writer.print(value);
+                }
+            }
+            writer.close();
+            return builder.toString();
+        }
+    }
+}
diff --git a/containers/simple-http/src/main/java/org/glassfish/jersey/simple/package-info.java b/containers/simple-http/src/main/java/org/glassfish/jersey/simple/package-info.java
new file mode 100644
index 0000000..f7a4d38
--- /dev/null
+++ b/containers/simple-http/src/main/java/org/glassfish/jersey/simple/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+/**
+ * Jersey Simple 6.x container classes.
+ */
+package org.glassfish.jersey.simple;
diff --git a/containers/simple-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ContainerProvider b/containers/simple-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ContainerProvider
new file mode 100644
index 0000000..a0a4981
--- /dev/null
+++ b/containers/simple-http/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ContainerProvider
@@ -0,0 +1 @@
+org.glassfish.jersey.simple.SimpleContainerProvider
\ No newline at end of file
diff --git a/containers/simple-http/src/main/resources/org.glassfish.jersey.simple.internal/localization.properties b/containers/simple-http/src/main/resources/org.glassfish.jersey.simple.internal/localization.properties
new file mode 100644
index 0000000..138fa5b
--- /dev/null
+++ b/containers/simple-http/src/main/resources/org.glassfish.jersey.simple.internal/localization.properties
@@ -0,0 +1,20 @@
+#
+# Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Eclipse Public License v. 2.0, which is available at
+# http://www.eclipse.org/legal/epl-2.0.
+#
+# This Source Code may also be made available under the following Secondary
+# Licenses when the conditions for such availability set forth in the
+# Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+# version 2 with the GNU Classpath Exception, which is available at
+# https://www.gnu.org/software/classpath/license.html.
+#
+# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+#
+
+error.when.creating.server=IOException thrown when trying to create simple server.
+uri.cannot.be.null=The URI must not be null.
+wrong.scheme.when.using.http=The URI scheme should be 'http' when not using SSL.
+wrong.scheme.when.using.https=The URI scheme should be 'https' when using SSL.
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
new file mode 100644
index 0000000..eb653ca
--- /dev/null
+++ b/containers/simple-http/src/test/java/org/glassfish/jersey/simple/AbstractSimpleServerTester.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.simple;
+
+import java.net.URI;
+import java.security.AccessController;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.core.UriBuilder;
+
+import org.glassfish.jersey.internal.util.PropertiesHelper;
+import org.glassfish.jersey.logging.LoggingFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+
+import org.junit.After;
+
+/**
+ * Abstract Simple HTTP Server unit tester.
+ *
+ * @author Paul Sandoz
+ * @author Arul Dhesiaseelan (aruld at acm.org)
+ * @author Miroslav Fuksa
+ */
+public abstract class AbstractSimpleServerTester {
+
+    public static final String CONTEXT = "";
+    private final int DEFAULT_PORT = 9998;
+
+    private static final Logger LOGGER = Logger.getLogger(AbstractSimpleServerTester.class.getName());
+
+    /**
+     * Get the port to be used for test application deployments.
+     *
+     * @return The HTTP port of the URI
+     */
+    protected final int getPort() {
+        final String value = AccessController
+                .doPrivileged(PropertiesHelper.getSystemProperty("jersey.config.test.container.port"));
+        if (value != null) {
+
+            try {
+                final int i = Integer.parseInt(value);
+                if (i <= 0) {
+                    throw new NumberFormatException("Value not positive.");
+                }
+                return i;
+            } catch (NumberFormatException e) {
+                LOGGER.log(
+                        Level.CONFIG,
+                        "Value of 'jersey.config.test.container.port'"
+                        + " property is not a valid positive integer [" + value + "]."
+                        + " Reverting to default [" + DEFAULT_PORT + "].",
+                        e);
+            }
+        }
+
+        return DEFAULT_PORT;
+    }
+
+    private volatile SimpleServer server;
+
+    public UriBuilder getUri() {
+        return UriBuilder.fromUri("http://localhost").port(getPort()).path(CONTEXT);
+    }
+
+    public void startServer(Class... resources) {
+        ResourceConfig config = new ResourceConfig(resources);
+        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);
+    }
+
+    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);
+    }
+
+    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);
+    }
+
+    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);
+    }
+
+    public URI getBaseUri() {
+        return UriBuilder.fromUri("http://localhost/").port(getPort()).build();
+    }
+
+    public void setDebug(boolean enable) {
+        if (server != null) {
+            server.setDebug(enable);
+        }
+    }
+
+    public void stopServer() {
+        try {
+            server.close();
+            server = null;
+            LOGGER.log(Level.INFO, "Simple-http server stopped.");
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @After
+    public void tearDown() {
+        if (server != null) {
+            stopServer();
+        }
+    }
+}
diff --git a/containers/simple-http/src/test/java/org/glassfish/jersey/simple/AsyncTest.java b/containers/simple-http/src/test/java/org/glassfish/jersey/simple/AsyncTest.java
new file mode 100644
index 0000000..9587b04
--- /dev/null
+++ b/containers/simple-http/src/test/java/org/glassfish/jersey/simple/AsyncTest.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.simple;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.container.AsyncResponse;
+import javax.ws.rs.container.Suspended;
+import javax.ws.rs.container.TimeoutHandler;
+import javax.ws.rs.core.Response;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author Arul Dhesiaseelan (aruld at acm.org)
+ * @author Michal Gajdos
+ */
+public class AsyncTest extends AbstractSimpleServerTester {
+
+    @Path("/async")
+    @SuppressWarnings("VoidMethodAnnotatedWithGET")
+    public static class AsyncResource {
+
+        public static AtomicInteger INVOCATION_COUNT = new AtomicInteger(0);
+
+        @GET
+        public void asyncGet(@Suspended final AsyncResponse asyncResponse) {
+            new Thread(new Runnable() {
+
+                @Override
+                public void run() {
+                    final String result = veryExpensiveOperation();
+                    asyncResponse.resume(result);
+                }
+
+                private String veryExpensiveOperation() {
+                    // ... very expensive operation that typically finishes within 5 seconds, simulated using
+                    // sleep()
+                    try {
+                        Thread.sleep(5000);
+                    } catch (final InterruptedException e) {
+                        // ignore
+                    }
+                    return "DONE";
+                }
+            }).start();
+        }
+
+        @GET
+        @Path("timeout")
+        public void asyncGetWithTimeout(@Suspended final AsyncResponse asyncResponse) {
+            asyncResponse.setTimeoutHandler(new TimeoutHandler() {
+
+                @Override
+                public void handleTimeout(final AsyncResponse asyncResponse) {
+                    asyncResponse.resume(Response.status(Response.Status.SERVICE_UNAVAILABLE)
+                                                 .entity("Operation time out.").build());
+                }
+            });
+            asyncResponse.setTimeout(3, TimeUnit.SECONDS);
+
+            new Thread(new Runnable() {
+
+                @Override
+                public void run() {
+                    final String result = veryExpensiveOperation();
+                    asyncResponse.resume(result);
+                }
+
+                private String veryExpensiveOperation() {
+                    // ... very expensive operation that typically finishes within 10 seconds, simulated using
+                    // sleep()
+                    try {
+                        Thread.sleep(7000);
+                    } catch (final InterruptedException e) {
+                        // ignore
+                    }
+                    return "DONE";
+                }
+            }).start();
+        }
+
+        @GET
+        @Path("multiple-invocations")
+        public void asyncMultipleInvocations(@Suspended final AsyncResponse asyncResponse) {
+            INVOCATION_COUNT.incrementAndGet();
+
+            new Thread(new Runnable() {
+                @Override
+                public void run() {
+                    asyncResponse.resume("OK");
+                }
+            }).start();
+        }
+    }
+
+    private Client client;
+
+    @Before
+    public void setUp() throws Exception {
+        startServer(AsyncResource.class);
+        client = ClientBuilder.newClient();
+    }
+
+    @Override
+    @After
+    public void tearDown() {
+        super.tearDown();
+        client = null;
+    }
+
+    @Test
+    public void testAsyncGet() throws ExecutionException, InterruptedException {
+        final Future<Response> responseFuture =
+                client.target(getUri().path("/async")).request().async().get();
+        // Request is being processed asynchronously.
+        final Response response = responseFuture.get();
+        // get() waits for the response
+        assertEquals("DONE", response.readEntity(String.class));
+    }
+
+    @Test
+    public void testAsyncGetWithTimeout()
+            throws ExecutionException, InterruptedException, TimeoutException {
+        final Future<Response> responseFuture =
+                client.target(getUri().path("/async/timeout")).request().async().get();
+        // Request is being processed asynchronously.
+        final Response response = responseFuture.get();
+
+        // get() waits for the response
+        assertEquals(503, response.getStatus());
+        assertEquals("Operation time out.", response.readEntity(String.class));
+    }
+
+    /**
+     * JERSEY-2616 reproducer. Make sure resource method is only invoked once per one request.
+     */
+    @Test
+    public void testAsyncMultipleInvocations() throws Exception {
+        final Response response =
+                client.target(getUri().path("/async/multiple-invocations")).request().get();
+
+        assertThat(AsyncResource.INVOCATION_COUNT.get(), is(1));
+
+        assertThat(response.getStatus(), is(200));
+        assertThat(response.readEntity(String.class), is("OK"));
+    }
+}
diff --git a/containers/simple-http/src/test/java/org/glassfish/jersey/simple/ExceptionTest.java b/containers/simple-http/src/test/java/org/glassfish/jersey/simple/ExceptionTest.java
new file mode 100644
index 0000000..15e7b28
--- /dev/null
+++ b/containers/simple-http/src/test/java/org/glassfish/jersey/simple/ExceptionTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.simple;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Response;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author Paul Sandoz
+ */
+public class ExceptionTest extends AbstractSimpleServerTester {
+    @Path("{status}")
+    public static class ExceptionResource {
+        @GET
+        public String get(@PathParam("status") int status) {
+            throw new WebApplicationException(status);
+        }
+
+    }
+
+    @Test
+    public void test400StatusCode() {
+        startServer(ExceptionResource.class);
+        Client client = ClientBuilder.newClient();
+        WebTarget r = client.target(getUri().path("400").build());
+        assertEquals(400, r.request().get(Response.class).getStatus());
+    }
+
+    @Test
+    public void test500StatusCode() {
+        startServer(ExceptionResource.class);
+        Client client = ClientBuilder.newClient();
+        WebTarget r = client.target(getUri().path("500").build());
+
+        assertEquals(500, r.request().get(Response.class).getStatus());
+    }
+}
diff --git a/containers/simple-http/src/test/java/org/glassfish/jersey/simple/LifecycleListenerTest.java b/containers/simple-http/src/test/java/org/glassfish/jersey/simple/LifecycleListenerTest.java
new file mode 100644
index 0000000..439b7d2
--- /dev/null
+++ b/containers/simple-http/src/test/java/org/glassfish/jersey/simple/LifecycleListenerTest.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.simple;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.spi.AbstractContainerLifecycleListener;
+import org.glassfish.jersey.server.spi.Container;
+
+import org.junit.Test;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+
+/**
+ * Reload and ContainerLifecycleListener support test.
+ *
+ * @author Paul Sandoz
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public class LifecycleListenerTest extends AbstractSimpleServerTester {
+
+    @Path("/one")
+    public static class One {
+        @GET
+        public String get() {
+            return "one";
+        }
+    }
+
+    @Path("/two")
+    public static class Two {
+        @GET
+        public String get() {
+            return "two";
+        }
+    }
+
+    public static class Reloader extends AbstractContainerLifecycleListener {
+        Container container;
+
+        public void reload(ResourceConfig newConfig) {
+            container.reload(newConfig);
+        }
+
+        public void reload() {
+            container.reload();
+        }
+
+        @Override
+        public void onStartup(Container container) {
+            this.container = container;
+        }
+
+    }
+
+    @Test
+    public void testReload() {
+        final ResourceConfig rc = new ResourceConfig(One.class);
+
+        Reloader reloader = new Reloader();
+        rc.registerInstances(reloader);
+
+        startServer(rc);
+
+        WebTarget r = ClientBuilder.newClient().target(getUri().path("/").build());
+
+        assertEquals("one", r.path("one").request().get(String.class));
+        assertEquals(404, r.path("two").request().get(Response.class).getStatus());
+
+        // add Two resource
+        reloader.reload(new ResourceConfig(One.class, Two.class));
+
+        assertEquals("one", r.path("one").request().get(String.class));
+        assertEquals("two", r.path("two").request().get(String.class));
+    }
+
+    static class StartStopListener extends AbstractContainerLifecycleListener {
+        volatile boolean started;
+        volatile boolean stopped;
+
+        @Override
+        public void onStartup(Container container) {
+            started = true;
+        }
+
+        @Override
+        public void onShutdown(Container container) {
+            stopped = true;
+        }
+    }
+
+    @Test
+    public void testStartupShutdownHooks() {
+        final StartStopListener listener = new StartStopListener();
+
+        startServer(new ResourceConfig(One.class).register(listener));
+
+        WebTarget r = ClientBuilder.newClient().target(getUri().path("/").build());
+
+        assertThat(r.path("one").request().get(String.class), equalTo("one"));
+        assertThat(r.path("two").request().get(Response.class).getStatus(), equalTo(404));
+
+        stopServer();
+
+        assertTrue("ContainerLifecycleListener.onStartup has not been called.", listener.started);
+        assertTrue("ContainerLifecycleListener.onShutdown has not been called.", listener.stopped);
+    }
+}
diff --git a/containers/simple-http/src/test/java/org/glassfish/jersey/simple/OptionsTest.java b/containers/simple-http/src/test/java/org/glassfish/jersey/simple/OptionsTest.java
new file mode 100644
index 0000000..3b12b1a
--- /dev/null
+++ b/containers/simple-http/src/test/java/org/glassfish/jersey/simple/OptionsTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.simple;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.core.Response;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class OptionsTest extends AbstractSimpleServerTester {
+
+    @Path("helloworld")
+    public static class HelloWorldResource {
+        public static final String CLICHED_MESSAGE = "Hello World!";
+
+        @GET
+        @Produces("text/plain")
+        public String getHello() {
+            return CLICHED_MESSAGE;
+        }
+    }
+
+    @Path("/users")
+    public class UserResource {
+
+        @Path("/current")
+        @GET
+        @Produces("text/plain")
+        public String getCurrentUser() {
+            return "current user";
+        }
+    }
+
+    private Client client;
+
+    @Before
+    public void setUp() throws Exception {
+        startServer(HelloWorldResource.class, UserResource.class);
+        client = ClientBuilder.newClient();
+    }
+
+    @Override
+    @After
+    public void tearDown() {
+        super.tearDown();
+        client = null;
+    }
+
+
+    @Test
+    public void testFooBarOptions() {
+        Response response =
+                client.target(getUri()).path("helloworld").request().header("Accept", "foo/bar").options();
+        assertEquals(200, response.getStatus());
+        final String allowHeader = response.getHeaderString("Allow");
+        _checkAllowContent(allowHeader);
+        assertEquals(0, response.getLength());
+        assertEquals("foo/bar", response.getMediaType().toString());
+    }
+
+    private void _checkAllowContent(final String content) {
+        assertTrue(content.contains("GET"));
+        assertTrue(content.contains("HEAD"));
+        assertTrue(content.contains("OPTIONS"));
+    }
+
+    @Test
+    public void testNoDefaultMethod() {
+        Response response = client.target(getUri()).path("/users").request().options();
+        assertThat(response.getStatus(), is(404));
+    }
+
+}
diff --git a/containers/simple-http/src/test/java/org/glassfish/jersey/simple/ParallelTest.java b/containers/simple-http/src/test/java/org/glassfish/jersey/simple/ParallelTest.java
new file mode 100644
index 0000000..e0b6611
--- /dev/null
+++ b/containers/simple-http/src/test/java/org/glassfish/jersey/simple/ParallelTest.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.simple;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.WebTarget;
+
+import org.glassfish.jersey.server.ResourceConfig;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Tests the parallel execution of multiple requests.
+ *
+ * @author Stepan Kopriva
+ * @author Arul Dhesiaseelan (aruld at acm.org)
+ */
+public class ParallelTest extends AbstractSimpleServerTester {
+
+    // Server-side dispatcher and selector pool configuration
+    private static final int selectorThreads = Runtime.getRuntime().availableProcessors();
+    private static final int dispatcherThreads = Math.max(8, selectorThreads * 2);
+
+    private static final int numberOfThreads = 100;
+
+    private static final String PATH = "test";
+    private static AtomicInteger receivedCounter = new AtomicInteger(0);
+    private static AtomicInteger resourceCounter = new AtomicInteger(0);
+    private static CountDownLatch latch = new CountDownLatch(numberOfThreads);
+
+    @Path(PATH)
+    public static class MyResource {
+
+        @GET
+        public String get() {
+            this.sleep();
+            resourceCounter.addAndGet(1);
+            return "GET";
+        }
+
+        private void sleep() {
+            try {
+                Thread.sleep(10);
+            } catch (InterruptedException ex) {
+                Logger.getLogger(ParallelTest.class.getName()).log(Level.SEVERE, null, ex);
+            }
+        }
+    }
+
+    private class ResourceThread extends Thread {
+
+        private WebTarget target;
+        private String path;
+
+        public ResourceThread(WebTarget target, String path) {
+            this.target = target;
+            this.path = path;
+        }
+
+        @Override
+        public void run() {
+            assertEquals("GET", target.path(path).request().get(String.class));
+            receivedCounter.addAndGet(1);
+            latch.countDown();
+        }
+    }
+
+    @Test
+    public void testParallel() {
+        ResourceConfig config = new ResourceConfig(MyResource.class);
+        startServer(config, dispatcherThreads, selectorThreads);
+        WebTarget target = ClientBuilder.newClient().target(getUri().path("/").build());
+
+        for (int i = 1; i <= numberOfThreads; i++) {
+            ResourceThread rt = new ResourceThread(target, PATH);
+            rt.start();
+        }
+
+        try {
+            latch.await(8000, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException ex) {
+            Logger.getLogger(ParallelTest.class.getName()).log(Level.SEVERE, null, ex);
+        }
+
+        int result = receivedCounter.get();
+        assertEquals(numberOfThreads, result);
+    }
+}
diff --git a/containers/simple-http/src/test/java/org/glassfish/jersey/simple/TraceTest.java b/containers/simple-http/src/test/java/org/glassfish/jersey/simple/TraceTest.java
new file mode 100644
index 0000000..1e2d737
--- /dev/null
+++ b/containers/simple-http/src/test/java/org/glassfish/jersey/simple/TraceTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.simple;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.core.Response;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class TraceTest extends AbstractSimpleServerTester {
+
+    @Path("helloworld")
+    public static class HelloWorldResource {
+        public static final String CLICHED_MESSAGE = "Hello World!";
+
+        @GET
+        @Produces("text/plain")
+        public String getHello() {
+            return CLICHED_MESSAGE;
+        }
+    }
+
+    @Path("/users")
+    public class UserResource {
+
+        @Path("/current")
+        @GET
+        @Produces("text/plain")
+        public String getCurrentUser() {
+            return "current user";
+        }
+    }
+
+    private Client client;
+
+    @Before
+    public void setUp() throws Exception {
+        startServerNoLoggingFilter(HelloWorldResource.class, UserResource.class); // disable crude
+        // LoggingFilter
+        setDebug(true);
+        client = ClientBuilder.newClient();
+    }
+
+    @Override
+    @After
+    public void tearDown() {
+        super.tearDown();
+        client = null;
+    }
+
+
+    @Test
+    public void testFooBarOptions() {
+        for (int i = 0; i < 100; i++) {
+            Response response = client.target(getUri()).path("helloworld").request()
+                                      .header("Accept", "foo/bar").options();
+            assertEquals(200, response.getStatus());
+            final String allowHeader = response.getHeaderString("Allow");
+            _checkAllowContent(allowHeader);
+            assertEquals(0, response.getLength());
+            assertEquals("foo/bar", response.getMediaType().toString());
+
+            try {
+                Thread.sleep(50);
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    private void _checkAllowContent(final String content) {
+        assertTrue(content.contains("GET"));
+        assertTrue(content.contains("HEAD"));
+        assertTrue(content.contains("OPTIONS"));
+    }
+
+    @Test
+    public void testNoDefaultMethod() {
+        Response response = client.target(getUri()).path("/users").request().options();
+        assertThat(response.getStatus(), is(404));
+    }
+}