Initial Contribution

Signed-off-by: Jan Supol <jan.supol@oracle.com>
diff --git a/ext/bean-validation/pom.xml b/ext/bean-validation/pom.xml
new file mode 100644
index 0000000..aa1b1f6
--- /dev/null
+++ b/ext/bean-validation/pom.xml
@@ -0,0 +1,110 @@
+<?xml version="1.0"?>
+<!--
+
+    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.ext</groupId>
+        <artifactId>project</artifactId>
+        <version>2.28-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>jersey-bean-validation</artifactId>
+    <name>jersey-ext-bean-validation</name>
+
+    <description>
+        Jersey extension module providing support for Bean Validation (JSR-349) API.
+    </description>
+
+    <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. -->
+                        <Export-Package>org.glassfish.jersey.server.validation.*;version=${project.version}</Export-Package>
+                        <Import-Package>javax.validation.*;resolution:=optional;version="${range;[==,3);${javax.validation.api.version}}", *</Import-Package>
+                    </instructions>
+                    <unpackBundle>true</unpackBundle>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.glassfish.hk2.external</groupId>
+            <artifactId>javax.inject</artifactId>
+        </dependency>
+        <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.validation</groupId>
+            <artifactId>validation-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.hibernate</groupId>
+            <artifactId>hibernate-validator</artifactId>
+        </dependency>
+
+        <!-- java-el related dependencies are in scope "provided" in hibernate-validator -->
+        <dependency>
+            <groupId>javax.el</groupId>
+            <artifactId>javax.el-api</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.glassfish.web</groupId>
+            <artifactId>javax.el</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+            <artifactId>jersey-test-framework-provider-bundle</artifactId>
+            <version>${project.version}</version>
+            <type>pom</type>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/ValidationConfig.java b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/ValidationConfig.java
new file mode 100644
index 0000000..61f8885
--- /dev/null
+++ b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/ValidationConfig.java
@@ -0,0 +1,115 @@
+/*
+ * 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.server.validation;
+
+import javax.validation.ConstraintValidatorFactory;
+import javax.validation.MessageInterpolator;
+import javax.validation.ParameterNameProvider;
+import javax.validation.TraversableResolver;
+
+/**
+ * Configuration class for Bean Validation provider.
+ *
+ * @author Michal Gajdos
+ */
+public final class ValidationConfig {
+
+    private MessageInterpolator messageInterpolator;
+    private TraversableResolver traversableResolver;
+    private ConstraintValidatorFactory constraintValidatorFactory;
+    private ParameterNameProvider parameterNameProvider;
+
+    /**
+     * Return {@code MessageInterpolator} implementation used for configuration.
+     *
+     * @return instance of {@code MessageInterpolator} or {@code null} if not defined.
+     */
+    public MessageInterpolator getMessageInterpolator() {
+        return messageInterpolator;
+    }
+
+    /**
+     * Return {@code TraversableResolver} implementation used for configuration.
+     *
+     * @return instance of {@code TraversableResolver} or {@code null} if not defined.
+     */
+    public TraversableResolver getTraversableResolver() {
+        return traversableResolver;
+    }
+
+    /**
+     * Return {@code ConstraintValidatorFactory} implementation used for configuration.
+     *
+     * @return instance of {@code ConstraintValidatorFactory} or {@code null} if not defined.
+     */
+    public ConstraintValidatorFactory getConstraintValidatorFactory() {
+        return constraintValidatorFactory;
+    }
+
+    /**
+     * Return {@code ParameterNameProvider} implementation used for configuration.
+     *
+     * @return instance of {@code ParameterNameProvider} or {@code null} if not defined.
+     */
+    public ParameterNameProvider getParameterNameProvider() {
+        return parameterNameProvider;
+    }
+
+    /**
+     * Defines the message interpolator.
+     * If {@code null} is passed, the default message interpolator is used.
+     *
+     * @param messageInterpolator message interpolator implementation.
+     */
+    public ValidationConfig messageInterpolator(final MessageInterpolator messageInterpolator) {
+        this.messageInterpolator = messageInterpolator;
+        return this;
+    }
+
+    /**
+     * Defines the traversable resolver.
+     * If {@code null} is passed, the default traversable resolver is used.
+     *
+     * @param traversableResolver traversable resolver implementation.
+     */
+    public ValidationConfig traversableResolver(final TraversableResolver traversableResolver) {
+        this.traversableResolver = traversableResolver;
+        return this;
+    }
+
+    /**
+     * Defines the constraint validator factory.
+     * If {@code null} is passed, the default constraint validator factory is used.
+     *
+     * @param constraintValidatorFactory constraint factory implementation.
+     */
+    public ValidationConfig constraintValidatorFactory(final ConstraintValidatorFactory constraintValidatorFactory) {
+        this.constraintValidatorFactory = constraintValidatorFactory;
+        return this;
+    }
+
+    /**
+     * Defines the parameter name provider.
+     * If {@code null} is passed, the default parameter name provider is used.
+     *
+     * @param parameterNameProvider parameter name provider implementation.
+     */
+    public ValidationConfig parameterNameProvider(final ParameterNameProvider parameterNameProvider) {
+        this.parameterNameProvider = parameterNameProvider;
+        return this;
+    }
+}
diff --git a/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/ValidationError.java b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/ValidationError.java
new file mode 100644
index 0000000..cf2f1c4
--- /dev/null
+++ b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/ValidationError.java
@@ -0,0 +1,130 @@
+/*
+ * 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.server.validation;
+
+import javax.xml.bind.annotation.XmlRootElement;
+
+/**
+ * Default validation error entity to be included in {@code Response}.
+ *
+ * @author Michal Gajdos
+ */
+@XmlRootElement
+@SuppressWarnings("UnusedDeclaration")
+public final class ValidationError {
+
+    private String message;
+
+    private String messageTemplate;
+
+    private String path;
+
+    private String invalidValue;
+
+    /**
+     * Create a {@code ValidationError} instance. Constructor for JAXB providers.
+     */
+    public ValidationError() {
+    }
+
+    /**
+     * Create a {@code ValidationError} instance.
+     *
+     * @param message interpolated error message.
+     * @param messageTemplate non-interpolated error message.
+     * @param path property path.
+     * @param invalidValue value that failed to pass constraints.
+     */
+    public ValidationError(final String message, final String messageTemplate, final String path, final String invalidValue) {
+        this.message = message;
+        this.messageTemplate = messageTemplate;
+        this.path = path;
+        this.invalidValue = invalidValue;
+    }
+
+    /**
+     * Return the interpolated error message for this validation error.
+     *
+     * @return the interpolated error message for this validation error.
+     */
+    public String getMessage() {
+        return message;
+    }
+
+    /**
+     * Return the interpolated error message for this validation error.
+     *
+     * @param message the interpolated error message for this validation error.
+     */
+    public void setMessage(final String message) {
+        this.message = message;
+    }
+
+    /**
+     * Return the string representation of the property path to the value.
+     *
+     * @return the string representation of the property path to the value.
+     */
+    public String getPath() {
+        return path;
+    }
+
+    /**
+     * Set the string representation of the property path to the value.
+     *
+     * @param path the string representation of the property path to the value.
+     */
+    public void setPath(final String path) {
+        this.path = path;
+    }
+
+    /**
+     * Returns the string representation of the value failing to pass the constraint.
+     *
+     * @return the value failing to pass the constraint.
+     */
+    public String getInvalidValue() {
+        return invalidValue;
+    }
+
+    /**
+     * Set the value failing to pass the constraint.
+     *
+     * @param invalidValue the value failing to pass the constraint.
+     */
+    public void setInvalidValue(final String invalidValue) {
+        this.invalidValue = invalidValue;
+    }
+
+    /**
+     * Return the non-interpolated error message for this validation error.
+     *
+     * @return the non-interpolated error message for this validation error.
+     */
+    public String getMessageTemplate() {
+        return messageTemplate;
+    }
+
+    /**
+     * Set the non-interpolated error message for this validation error.
+     *
+     * @param messageTemplate the non-interpolated error message for this validation error.
+     */
+    public void setMessageTemplate(final String messageTemplate) {
+        this.messageTemplate = messageTemplate;
+    }
+}
diff --git a/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/ValidationFeature.java b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/ValidationFeature.java
new file mode 100644
index 0000000..a012756
--- /dev/null
+++ b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/ValidationFeature.java
@@ -0,0 +1,57 @@
+/*
+ * 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.server.validation;
+
+import javax.ws.rs.ConstrainedTo;
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.Feature;
+import javax.ws.rs.core.FeatureContext;
+
+import org.glassfish.jersey.internal.util.PropertiesHelper;
+import org.glassfish.jersey.server.ServerProperties;
+import org.glassfish.jersey.server.validation.internal.ValidationBinder;
+
+/**
+ * {@code ValidationFeature} used to add Bean Validation (JSR-349) support to the server.
+ *
+ * @author Michal Gajdos
+ */
+@ConstrainedTo(RuntimeType.SERVER)
+public final class ValidationFeature implements Feature {
+
+    @Override
+    public boolean configure(final FeatureContext context) {
+        final Configuration config = context.getConfiguration();
+
+        // Validation disabled?
+        if (PropertiesHelper.isProperty(config.getProperty(ServerProperties.BV_FEATURE_DISABLE))) {
+            return false;
+        }
+
+        context.register(new ValidationBinder());
+
+        // Set ServerProperties.RESPONSE_SET_STATUS_OVER_SEND_ERROR to make sure no sendError is called on servlet container
+        // when ServerProperties.BV_SEND_ERROR_IN_RESPONSE is enabled.
+        if (PropertiesHelper.isProperty(config.getProperty(ServerProperties.BV_SEND_ERROR_IN_RESPONSE))
+                && config.getProperty(ServerProperties.RESPONSE_SET_STATUS_OVER_SEND_ERROR) == null) {
+            context.property(ServerProperties.RESPONSE_SET_STATUS_OVER_SEND_ERROR, true);
+        }
+
+        return true;
+    }
+}
diff --git a/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/DefaultConfiguredValidator.java b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/DefaultConfiguredValidator.java
new file mode 100644
index 0000000..74e1845
--- /dev/null
+++ b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/DefaultConfiguredValidator.java
@@ -0,0 +1,191 @@
+/*
+ * 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.server.validation.internal;
+
+import java.lang.reflect.Method;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import javax.ws.rs.core.Response;
+
+import javax.validation.Configuration;
+import javax.validation.ConstraintViolation;
+import javax.validation.ConstraintViolationException;
+import javax.validation.Validator;
+import javax.validation.executable.ExecutableType;
+import javax.validation.executable.ExecutableValidator;
+import javax.validation.metadata.BeanDescriptor;
+import javax.validation.metadata.MethodDescriptor;
+
+import org.glassfish.jersey.server.internal.inject.ConfiguredValidator;
+import org.glassfish.jersey.server.model.Invocable;
+import org.glassfish.jersey.server.spi.ValidationInterceptor;
+import org.glassfish.jersey.server.spi.ValidationInterceptorContext;
+
+/**
+ * Default {@link ConfiguredValidator} implementation - delegates calls to the underlying {@link Validator}.
+ *
+ * @author Michal Gajdos
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+class DefaultConfiguredValidator implements ConfiguredValidator, ValidationInterceptor {
+
+    private final Validator delegate;
+    private final Configuration configuration;
+    private final ValidateOnExecutionHandler validateOnExecutionHandler;
+    private final List<ValidationInterceptor> interceptors;
+
+    /**
+     * Create a configured validator instance.
+     *
+     * @param delegate                   validator to delegate calls to.
+     * @param configuration              configuration to obtain {@link ExecutableType executable types} configured in descriptor
+     *                                   from.
+     * @param validateOnExecutionHandler handler for processing {@link javax.validation.executable.ValidateOnExecution}
+     *                                   annotations.
+     * @param interceptors               custom validation interceptors.
+     */
+    DefaultConfiguredValidator(final Validator delegate, final Configuration configuration,
+                               final ValidateOnExecutionHandler validateOnExecutionHandler,
+                               final Iterable<ValidationInterceptor> interceptors) {
+        this.delegate = delegate;
+        this.configuration = configuration;
+        this.validateOnExecutionHandler = validateOnExecutionHandler;
+        this.interceptors = createInterceptorList(interceptors);
+    }
+
+    private List<ValidationInterceptor> createInterceptorList(Iterable<ValidationInterceptor> interceptors) {
+        List<ValidationInterceptor> result = new LinkedList<>();
+        for (ValidationInterceptor i : interceptors) {
+            result.add(i);
+        }
+        result.add(this);
+        return result;
+    }
+
+    @Override
+    public <T> Set<ConstraintViolation<T>> validate(final T object, final Class<?>... groups) {
+        return delegate.validate(object, groups);
+    }
+
+    @Override
+    public <T> Set<ConstraintViolation<T>> validateProperty(final T object, final String propertyName, final Class<?>... groups) {
+        return delegate.validateProperty(object, propertyName, groups);
+    }
+
+    @Override
+    public <T> Set<ConstraintViolation<T>> validateValue(final Class<T> beanType, final String propertyName,
+                                                         final Object value, final Class<?>... groups) {
+        return delegate.validateValue(beanType, propertyName, value, groups);
+    }
+
+    @Override
+    public BeanDescriptor getConstraintsForClass(final Class<?> clazz) {
+        return delegate.getConstraintsForClass(clazz);
+    }
+
+    @Override
+    public <T> T unwrap(final Class<T> type) {
+        return delegate.unwrap(type);
+    }
+
+    @Override
+    public ExecutableValidator forExecutables() {
+        return delegate.forExecutables();
+    }
+
+    @Override
+    public void validateResourceAndInputParams(final Object resource, final Invocable resourceMethod, final Object[] args) {
+
+        ValidationInterceptorExecutor validationExecutor = new ValidationInterceptorExecutor(
+                resource,
+                resourceMethod,
+                args,
+                interceptors.iterator());
+
+        validationExecutor.proceed();
+    }
+
+    // Invoked as the last validation interceptor method in the chain.
+    @Override
+    public void onValidate(final ValidationInterceptorContext ctx) {
+
+        final Object resource = ctx.getResource();
+        final Invocable resourceMethod = ctx.getInvocable();
+        final Object[] args = ctx.getArgs();
+
+        final Set<ConstraintViolation<Object>> constraintViolations = new HashSet<>();
+        final BeanDescriptor beanDescriptor = getConstraintsForClass(resource.getClass());
+
+        // Resource validation.
+        if (beanDescriptor.isBeanConstrained()) {
+            constraintViolations.addAll(validate(resource));
+        }
+
+        if (resourceMethod != null
+                && configuration.getBootstrapConfiguration().isExecutableValidationEnabled()) {
+            final Method handlingMethod = resourceMethod.getHandlingMethod();
+
+            // Resource method validation - input parameters.
+            final MethodDescriptor methodDescriptor = beanDescriptor.getConstraintsForMethod(handlingMethod.getName(),
+                    handlingMethod.getParameterTypes());
+
+            if (methodDescriptor != null
+                    && methodDescriptor.hasConstrainedParameters()
+                    && validateOnExecutionHandler.validateMethod(resource.getClass(),
+                                                                 resourceMethod.getDefinitionMethod(),
+                                                                 resourceMethod.getHandlingMethod())) {
+                constraintViolations.addAll(forExecutables().validateParameters(resource, handlingMethod, args));
+            }
+        }
+
+        if (!constraintViolations.isEmpty()) {
+            throw new ConstraintViolationException(constraintViolations);
+        }
+    }
+
+    @Override
+    public void validateResult(final Object resource, final Invocable resourceMethod, final Object result) {
+        if (configuration.getBootstrapConfiguration().isExecutableValidationEnabled()) {
+            final Set<ConstraintViolation<Object>> constraintViolations = new HashSet<>();
+            final Method handlingMethod = resourceMethod.getHandlingMethod();
+
+            final BeanDescriptor beanDescriptor = getConstraintsForClass(resource.getClass());
+            final MethodDescriptor methodDescriptor = beanDescriptor.getConstraintsForMethod(handlingMethod.getName(),
+                    handlingMethod.getParameterTypes());
+
+            final Method definitionMethod = resourceMethod.getDefinitionMethod();
+
+            if (methodDescriptor != null
+                    && methodDescriptor.hasConstrainedReturnValue()
+                    && validateOnExecutionHandler.validateMethod(resource.getClass(), definitionMethod, handlingMethod)) {
+                constraintViolations.addAll(forExecutables().validateReturnValue(resource, handlingMethod, result));
+
+                if (result instanceof Response) {
+                    constraintViolations.addAll(forExecutables().validateReturnValue(resource, handlingMethod,
+                            ((Response) result).getEntity()));
+                }
+            }
+
+            if (!constraintViolations.isEmpty()) {
+                throw new ConstraintViolationException(constraintViolations);
+            }
+        }
+    }
+}
diff --git a/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/InjectingConstraintValidatorFactory.java b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/InjectingConstraintValidatorFactory.java
new file mode 100644
index 0000000..69f2f8c
--- /dev/null
+++ b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/InjectingConstraintValidatorFactory.java
@@ -0,0 +1,44 @@
+/*
+ * 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.server.validation.internal;
+
+import javax.ws.rs.container.ResourceContext;
+import javax.ws.rs.core.Context;
+
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorFactory;
+
+/**
+ * {@link ConstraintValidatorFactory} with support of injecting Jersey providers/resources.
+ *
+ * @author Michal Gajdos
+ */
+public class InjectingConstraintValidatorFactory implements ConstraintValidatorFactory {
+
+    @Context
+    private ResourceContext resourceContext;
+
+    @Override
+    public <T extends ConstraintValidator<?, ?>> T getInstance(final Class<T> key) {
+        return resourceContext.getResource(key);
+    }
+
+    @Override
+    public void releaseInstance(final ConstraintValidator<?, ?> instance) {
+        // NOOP
+    }
+}
diff --git a/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidateOnExecutionHandler.java b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidateOnExecutionHandler.java
new file mode 100644
index 0000000..2d14937
--- /dev/null
+++ b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidateOnExecutionHandler.java
@@ -0,0 +1,267 @@
+/*
+ * 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.server.validation.internal;
+
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Method;
+import java.security.AccessController;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.stream.Collectors;
+
+import javax.validation.Configuration;
+import javax.validation.ValidationException;
+import javax.validation.executable.ExecutableType;
+import javax.validation.executable.ValidateOnExecution;
+
+import org.glassfish.jersey.internal.guava.Multimap;
+import org.glassfish.jersey.internal.guava.Multimaps;
+import org.glassfish.jersey.internal.util.ReflectionHelper;
+
+/**
+ * Handler providing methods to determine whether an executable should be validated during the validation process based on the
+ * presence of {@link ValidateOnExecution} annotation.
+ *
+ * @author Michal Gajdos
+ */
+class ValidateOnExecutionHandler {
+
+    private final ConcurrentMap<Method, Boolean> validateMethodCache = new ConcurrentHashMap<>();
+    private final ConcurrentMap<Method, Boolean> validateGetterCache = new ConcurrentHashMap<>();
+
+    private final Configuration config;
+    private final boolean checkOverrides;
+
+    /**
+     * Create {@link ValidateOnExecutionHandler}.
+     *
+     * @param config validation configuration to obtain bootstrap config.
+     * @param checkOverrides flag whether overriding/implementing methods should be checked if the {@link ValidateOnExecution}
+     * annotation is present.
+     */
+    ValidateOnExecutionHandler(final Configuration config, final boolean checkOverrides) {
+        this.config = config;
+        this.checkOverrides = checkOverrides;
+    }
+
+    /**
+     * Determine whether the given {@link Method getter} on the given {@link Class clazz} should be validated during the
+     * resource class validation. See {@code #validateMethod} to understand the difference between this and {@code
+     * #validateMethod}.
+     *
+     * @param clazz class on which the getter will be invoked.
+     * @param method method to be examined.
+     * @return {@code true} if the getter should be validated, {@code false} otherwise.
+     */
+    boolean validateGetter(final Class<?> clazz, final Method method) {
+        if (!validateGetterCache.containsKey(method)) {
+            processMethod(clazz, method, method, true);
+        }
+        return validateGetterCache.get(method);
+    }
+
+    /**
+     * Determine whether the given resource {@link Method method} to-be-executed on the given {@link Class clazz} should be
+     * validated. The difference between this and {@code #validateGetter} method is that this method returns {@code true} if the
+     * {@code method} is getter and validating getter method is not explicitly disabled by {@link ValidateOnExecution} annotation
+     * in the class hierarchy.
+     *
+     * @param clazz class on which the method will be invoked.
+     * @param method method to be examined.
+     * @param validationMethod method used for cache.
+     * @return {@code true} if the method should be validated, {@code false} otherwise.
+     */
+    boolean validateMethod(final Class<?> clazz, final Method method, final Method validationMethod) {
+        if (!validateMethodCache.containsKey(validationMethod)) {
+            processMethod(clazz, method, validationMethod, false);
+        }
+        return validateMethodCache.get(validationMethod);
+    }
+
+    /**
+     * Process the given {@code method} and {@code validationMethod} on given {@code clazz} and determine whether this method
+     * should be validated or not.
+     *
+     * @param clazz class on which the method will be invoked.
+     * @param method method to be examined.
+     * @param validationMethod method used for cache.
+     * @param forceValidation forces validation of a getter if no {@link ValidateOnExecution} annotation is present.
+     */
+    private void processMethod(final Class<?> clazz, final Method method, final Method validationMethod,
+                               final boolean forceValidation) {
+        final Deque<Class<?>> hierarchy = getValidationClassHierarchy(clazz);
+        Boolean validateMethod = processAnnotation(method, hierarchy, checkOverrides);
+
+        if (validateMethod != null) {
+            validateMethodCache.putIfAbsent(validationMethod, validateMethod);
+            validateGetterCache.putIfAbsent(validationMethod, validateMethod);
+        }
+
+        // Return value from validation.xml.
+        if (!validateMethodCache.containsKey(validationMethod)) {
+            final Set<ExecutableType> defaultValidatedExecutableTypes = config.getBootstrapConfiguration()
+                    .getDefaultValidatedExecutableTypes();
+            validateMethod = validateMethod(method, false, defaultValidatedExecutableTypes);
+
+            validateGetterCache.putIfAbsent(validationMethod, validateMethod || forceValidation);
+
+            // When validateMethod is called and no ValidateOnExecution annotation is present we want to validate getter resource
+            // methods by default (see SPEC).
+            validateMethodCache.putIfAbsent(validationMethod, ReflectionHelper.isGetter(validationMethod) || validateMethod);
+        }
+    }
+
+    /**
+     * Process {@link ValidateOnExecution} annotation for given method on a class hierarchy.
+     *
+     * @param method method to be examined.
+     * @param hierarchy class hierarchy to be examined.
+     * @param checkOverrides flag whether a overriding/implementing methods should also be checked.
+     * @return {@code true} if the method should be validated, {@code false} if not, {@code null} if the flag cannot be
+     * determined (no annotation present).
+     */
+    private Boolean processAnnotation(final Method method, final Deque<Class<?>> hierarchy, final boolean checkOverrides) {
+        // Overridden methods.
+        while (!hierarchy.isEmpty()) {
+            final Class<?> overriddenClass = hierarchy.removeFirst();
+            final Method overriddenMethod =
+                    AccessController.doPrivileged(ReflectionHelper.findMethodOnClassPA(overriddenClass, method));
+
+            if (overriddenMethod != null) {
+                // Method.
+                Set<ExecutableType> executableTypes = getExecutableTypes(overriddenMethod);
+                if (!executableTypes.isEmpty()) {
+
+                    // If an overriding/implementing method is annotated with @ValidateOnExecution, throw an exception.
+                    if (checkOverrides
+                            && processAnnotation(method, hierarchy, false) != null) {
+                        final String methodName = method.getDeclaringClass().getName() + "#" + method.getName();
+                        throw new ValidationException(LocalizationMessages.OVERRIDE_CHECK_ERROR(methodName));
+                    }
+
+                    return validateMethod(overriddenMethod, true, executableTypes);
+                }
+
+                // Class.
+                executableTypes = getExecutableTypes(overriddenClass);
+                if (!executableTypes.isEmpty()
+                        // It should contain not only ExecutableType#IMPLICIT but something else as well.
+                        // ExecutableType#IMPLICIT on class itself does nothing.
+                        && !(executableTypes.size() == 1 && executableTypes.contains(ExecutableType.IMPLICIT))) {
+
+                    return validateMethod(overriddenMethod, false, executableTypes);
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Determine whether the given {@link Method method} should be validated depending on the given {@code executableTypes}.
+     *
+     * @param method method to be examined.
+     * @param allowImplicit allows check for {@link ExecutableType#IMPLICIT} type.
+     * @param executableTypes executable types assigned to the method.
+     * @return {@code true} if the method should be validated, {@code false otherwise}.
+     */
+    private boolean validateMethod(final Method method, final boolean allowImplicit, final Set<ExecutableType> executableTypes) {
+        if (executableTypes.contains(ExecutableType.ALL)
+                || (allowImplicit && executableTypes.contains(ExecutableType.IMPLICIT))) {
+            return true;
+        }
+        return ReflectionHelper.isGetter(method) ? executableTypes.contains(ExecutableType.GETTER_METHODS)
+                : executableTypes.contains(ExecutableType.NON_GETTER_METHODS);
+    }
+
+    /**
+     * Return a set of executable types contained in {@link ValidateOnExecution} annotation belonging to the {@code element}.
+     *
+     * @param element element to be examined for {@link ValidateOnExecution}.
+     * @return set of executable types or an empty set if the element is not annotated with {@link ValidateOnExecution}.
+     */
+    private Set<ExecutableType> getExecutableTypes(final AnnotatedElement element) {
+        final ValidateOnExecution validateExecutable = element.getAnnotation(ValidateOnExecution.class);
+        return validateExecutable != null
+                ? Arrays.stream(validateExecutable.type()).collect(Collectors.toSet())
+                : Collections.emptySet();
+    }
+
+    /**
+     * Get a class hierarchy for the given {@code clazz} suitable to be looked for {@link ValidateOnExecution} annotation
+     * in order according to the priority defined by Bean Validation spec (superclasses, interfaces).
+     *
+     * @param clazz class to obtain hierarchy for.
+     * @return class hierarchy.
+     */
+    private Deque<Class<?>> getValidationClassHierarchy(final Class<?> clazz) {
+        final List<Class<?>> hierarchy = new ArrayList<>();
+
+        // Get all superclasses.
+        for (Class<?> currentClass = clazz; currentClass != Object.class; currentClass = currentClass.getSuperclass()) {
+            hierarchy.add(clazz);
+        }
+
+        hierarchy.addAll(getAllValidationInterfaces(clazz));
+        Collections.reverse(hierarchy);
+
+        return new ArrayDeque<>(hierarchy);
+    }
+
+    private List<Class<?>> getAllValidationInterfaces(final Class<?> clazz) {
+        final Multimap<Integer, Class<?>> map = Multimaps.newListMultimap(
+                new TreeMap<Integer, Collection<Class<?>>>(), ArrayList::new);
+
+        retrieveAllValidationInterfaces(clazz, map);
+
+        final List<Class<?>> interfaces = new ArrayList<>(map.values());
+        Collections.reverse(interfaces);
+
+        return interfaces;
+    }
+
+    private int retrieveAllValidationInterfaces(Class<?> clazz, final Multimap<Integer, Class<?>> map) {
+        if (clazz == null) {
+            return 0;
+        }
+
+        int minDepth = 0;
+
+        while (clazz != null) {
+            for (final Class<?> iface : clazz.getInterfaces()) {
+                int depth = retrieveAllValidationInterfaces(iface, map);
+
+                if (!map.containsValue(iface)) {
+                    map.put(depth, iface);
+                }
+                minDepth = minDepth > depth ? depth : minDepth;
+            }
+
+            clazz = clazz.getSuperclass();
+        }
+
+        return minDepth + 1;
+    }
+}
diff --git a/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidateOnExecutionTraversableResolver.java b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidateOnExecutionTraversableResolver.java
new file mode 100644
index 0000000..50a1321
--- /dev/null
+++ b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidateOnExecutionTraversableResolver.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.server.validation.internal;
+
+import java.lang.annotation.ElementType;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.security.AccessController;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import javax.validation.Path;
+import javax.validation.TraversableResolver;
+
+import org.glassfish.jersey.internal.util.ReflectionHelper;
+
+/**
+ * {@link TraversableResolver Traversable resolver} used for handling {@link javax.validation.executable.ValidateOnExecution}
+ * annotations present on property getters when validating resource class.
+ *
+ * @author Michal Gajdos
+ */
+class ValidateOnExecutionTraversableResolver implements TraversableResolver {
+
+    private final TraversableResolver delegate;
+
+    private final ConcurrentMap<String, Method> propertyToMethod = new ConcurrentHashMap<>();
+
+    private final ValidateOnExecutionHandler validateOnExecutionHandler;
+
+    private final boolean validateExecutable;
+
+    /**
+     * Create a new {@link ValidateOnExecutionTraversableResolver}.
+     *
+     * @param delegate delegate requests to this underlying traversable resolver if this one cannot resolve it.
+     * @param validateOnExecutionHandler handler to determine whether a getter should be validated or not.
+     * @param validateExecutable bootstrap flag to enable/disable global validation of executables.
+     */
+    public ValidateOnExecutionTraversableResolver(final TraversableResolver delegate,
+                                                  final ValidateOnExecutionHandler validateOnExecutionHandler,
+                                                  final boolean validateExecutable) {
+        this.delegate = delegate;
+        this.validateExecutable = validateExecutable;
+        this.validateOnExecutionHandler = validateOnExecutionHandler;
+    }
+
+    @Override
+    public boolean isReachable(final Object traversableObject,
+                               final Path.Node traversableProperty,
+                               final Class<?> rootBeanType,
+                               final Path pathToTraversableObject,
+                               final ElementType elementType) {
+        // Make sure only getters on entities are validated (not getters on resource classes).
+        final Class<?> traversableObjectClass = traversableObject.getClass();
+        final boolean isEntity = !rootBeanType.equals(traversableObjectClass);
+
+        if (isEntity && validateExecutable && ElementType.METHOD.equals(elementType)) {
+            final String propertyName = traversableProperty.getName();
+            final String propertyKey = traversableObjectClass.getName() + "#" + propertyName;
+
+            if (!propertyToMethod.containsKey(propertyKey)) {
+                final Method getter = getGetterMethod(traversableObjectClass, propertyName);
+
+                if (getter != null) {
+                    propertyToMethod.putIfAbsent(propertyKey, getter);
+                }
+            }
+
+            final Method getter = propertyToMethod.get(propertyKey);
+            return getter != null && validateOnExecutionHandler.validateGetter(traversableObjectClass, getter);
+        }
+
+        return delegate.isReachable(traversableObject, traversableProperty, rootBeanType, pathToTraversableObject, elementType);
+    }
+
+    @Override
+    public boolean isCascadable(final Object traversableObject,
+                                final Path.Node traversableProperty,
+                                final Class<?> rootBeanType,
+                                final Path pathToTraversableObject,
+                                final ElementType elementType) {
+        return delegate.isCascadable(traversableObject, traversableProperty, rootBeanType, pathToTraversableObject, elementType);
+    }
+
+    /**
+     * Return getter method defined on {@code clazz} of property with given {@code propertyName}.
+     *
+     * @param clazz class to find a getter method on.
+     * @param propertyName name of the property to find a getter for.
+     * @return getter method or {@code null} if the method cannot be found.
+     */
+    private Method getGetterMethod(final Class<?> clazz, final String propertyName) {
+        // Property type.
+        Class<?> propertyType = null;
+        for (final Field field : AccessController.doPrivileged(ReflectionHelper.getAllFieldsPA(clazz))) {
+            if (field.getName().equals(propertyName)) {
+                propertyType = field.getType();
+            }
+        }
+
+        final char[] chars = propertyName.toCharArray();
+        chars[0] = Character.toUpperCase(chars[0]);
+        final String getterPropertyName = new String(chars);
+
+        final String isGetter = "is" + getterPropertyName;
+        final String getGetter = "get" + getterPropertyName;
+
+        for (final Method method : AccessController.doPrivileged(ReflectionHelper.getMethodsPA(clazz))) {
+            final String methodName = method.getName();
+
+            if ((methodName.equals(isGetter) || methodName.equals(getGetter))
+                    && ReflectionHelper.isGetter(method)
+                    && (propertyType == null || propertyType.isAssignableFrom(method.getReturnType()))) {
+                return AccessController.doPrivileged(ReflectionHelper.findMethodOnClassPA(clazz, method));
+            }
+        }
+
+        return null;
+    }
+}
diff --git a/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidationAutoDiscoverable.java b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidationAutoDiscoverable.java
new file mode 100644
index 0000000..ab373fc
--- /dev/null
+++ b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidationAutoDiscoverable.java
@@ -0,0 +1,43 @@
+/*
+ * 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.server.validation.internal;
+
+import javax.annotation.Priority;
+import javax.ws.rs.ConstrainedTo;
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.core.FeatureContext;
+
+import org.glassfish.jersey.internal.spi.AutoDiscoverable;
+import org.glassfish.jersey.internal.spi.ForcedAutoDiscoverable;
+import org.glassfish.jersey.server.validation.ValidationFeature;
+
+/**
+ * {@link AutoDiscoverable} registering {@link ValidationFeature} if this feature is not already registered.
+ *
+ * @author Michal Gajdos
+ */
+@ConstrainedTo(RuntimeType.SERVER)
+@Priority(AutoDiscoverable.DEFAULT_PRIORITY)
+public final class ValidationAutoDiscoverable implements ForcedAutoDiscoverable {
+
+    @Override
+    public void configure(final FeatureContext context) {
+        if (!context.getConfiguration().isRegistered(ValidationFeature.class)) {
+            context.register(ValidationFeature.class);
+        }
+    }
+}
diff --git a/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidationBinder.java b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidationBinder.java
new file mode 100644
index 0000000..050f920
--- /dev/null
+++ b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidationBinder.java
@@ -0,0 +1,294 @@
+/*
+ * 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.server.validation.internal;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.WeakHashMap;
+import java.util.function.Supplier;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.container.ResourceContext;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.ext.ContextResolver;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.Providers;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import javax.validation.Configuration;
+import javax.validation.TraversableResolver;
+import javax.validation.Validation;
+import javax.validation.ValidationException;
+import javax.validation.ValidationProviderResolver;
+import javax.validation.Validator;
+import javax.validation.ValidatorContext;
+import javax.validation.ValidatorFactory;
+import javax.validation.spi.ValidationProvider;
+
+import org.glassfish.jersey.internal.ServiceFinder;
+import org.glassfish.jersey.internal.inject.AbstractBinder;
+import org.glassfish.jersey.internal.inject.InjectionManager;
+import org.glassfish.jersey.internal.util.PropertiesHelper;
+import org.glassfish.jersey.internal.util.ReflectionHelper;
+import org.glassfish.jersey.model.internal.RankedComparator;
+import org.glassfish.jersey.model.internal.RankedProvider;
+import org.glassfish.jersey.server.ServerProperties;
+import org.glassfish.jersey.server.internal.inject.ConfiguredValidator;
+import org.glassfish.jersey.server.spi.ValidationInterceptor;
+import org.glassfish.jersey.server.validation.ValidationConfig;
+
+/**
+ * Bean Validation provider injection binder.
+ *
+ * @author Michal Gajdos
+ */
+public final class ValidationBinder extends AbstractBinder {
+
+    private static final Logger LOGGER = Logger.getLogger(ValidationBinder.class.getName());
+
+    @Override
+    protected void configure() {
+        bindFactory(DefaultConfigurationProvider.class, Singleton.class).to(Configuration.class).in(Singleton.class);
+
+        bindFactory(DefaultValidatorFactoryProvider.class, Singleton.class).to(ValidatorFactory.class).in(Singleton.class);
+        bindFactory(DefaultValidatorProvider.class, Singleton.class).to(Validator.class).in(Singleton.class);
+
+        bindFactory(ConfiguredValidatorProvider.class, Singleton.class).to(ConfiguredValidator.class);
+
+        // Custom Exception Mapper and Writer - registering in binder to make possible for users register their own providers.
+        bind(ValidationExceptionMapper.class).to(ExceptionMapper.class).in(Singleton.class);
+        bind(ValidationErrorMessageBodyWriter.class).to(MessageBodyWriter.class).in(Singleton.class);
+    }
+
+    /**
+     * Factory providing default {@link javax.validation.Configuration} instance.
+     */
+    private static class DefaultConfigurationProvider implements Supplier<Configuration> {
+
+        private final boolean inOsgi;
+
+        public DefaultConfigurationProvider() {
+            this.inOsgi = ReflectionHelper.getOsgiRegistryInstance() != null;
+        }
+
+        @Override
+        public Configuration get() {
+            try {
+                if (!inOsgi) {
+                    return Validation.byDefaultProvider().configure();
+                } else {
+                    return Validation
+                            .byDefaultProvider()
+                            .providerResolver(new ValidationProviderResolver() {
+                                @Override
+                                public List<ValidationProvider<?>> getValidationProviders() {
+                                    final List<ValidationProvider<?>> validationProviders = new ArrayList<>();
+
+                                    for (final ValidationProvider validationProvider : ServiceFinder
+                                            .find(ValidationProvider.class)) {
+                                        validationProviders.add(validationProvider);
+                                    }
+
+                                    return validationProviders;
+                                }
+                            })
+                            .configure();
+                }
+            } catch (final ValidationException e) {
+                // log and re-trow
+                LOGGER.log(Level.FINE, LocalizationMessages.VALIDATION_EXCEPTION_PROVIDER(), e);
+                throw e;
+            }
+        }
+    }
+
+    /**
+     * Factory providing default (un-configured) {@link ValidatorFactory} instance.
+     */
+    private static class DefaultValidatorFactoryProvider implements Supplier<ValidatorFactory> {
+
+        @Inject
+        private Configuration config;
+
+        @Override
+        public ValidatorFactory get() {
+            return config.buildValidatorFactory();
+        }
+    }
+
+    /**
+     * Factory providing default (un-configured) {@link Validator} instance.
+     */
+    private static class DefaultValidatorProvider implements Supplier<Validator> {
+
+        @Inject
+        private ValidatorFactory factory;
+
+        @Override
+        public Validator get() {
+            return factory.getValidator();
+        }
+    }
+
+    /**
+     * Factory providing configured {@link Validator} instance.
+     */
+    private static class ConfiguredValidatorProvider implements Supplier<ConfiguredValidator> {
+
+        @Inject
+        private InjectionManager injectionManager;
+
+        @Inject
+        private Configuration validationConfig;
+        @Inject
+        private ValidatorFactory factory;
+
+        @Context
+        private javax.ws.rs.core.Configuration jaxRsConfig;
+        @Context
+        private Providers providers;
+        @Context
+        private ResourceContext resourceContext;
+
+        private ConfiguredValidator defaultValidator;
+
+        private final WeakHashMap<ContextResolver<ValidationConfig>, ConfiguredValidator> validatorCache =
+                new WeakHashMap<>();
+
+        @Override
+        public ConfiguredValidator get() {
+
+            // Custom Configuration.
+            final ContextResolver<ValidationConfig> contextResolver =
+                    providers.getContextResolver(ValidationConfig.class, MediaType.WILDCARD_TYPE);
+
+            if (contextResolver == null) {
+                return getDefaultValidator();
+            } else {
+                if (!validatorCache.containsKey(contextResolver)) {
+                    final ValidateOnExecutionHandler validateOnExecutionHandler =
+                            new ValidateOnExecutionHandler(validationConfig, !isValidateOnExecutableOverrideCheckDisabled());
+
+                    final ValidatorContext context = getDefaultValidatorContext(validateOnExecutionHandler);
+                    final ValidationConfig config = contextResolver.getContext(ValidationConfig.class);
+
+                    if (config != null) {
+                        // MessageInterpolator
+                        if (config.getMessageInterpolator() != null) {
+                            context.messageInterpolator(config.getMessageInterpolator());
+                        }
+
+                        // TraversableResolver
+                        if (config.getTraversableResolver() != null) {
+                            context.traversableResolver(
+                                    getTraversableResolver(config.getTraversableResolver(), validateOnExecutionHandler));
+                        }
+
+                        // ConstraintValidatorFactory
+                        if (config.getConstraintValidatorFactory() != null) {
+                            context.constraintValidatorFactory(config.getConstraintValidatorFactory());
+                        }
+
+                        // ParameterNameProvider
+                        if (config.getParameterNameProvider() != null) {
+                            context.parameterNameProvider(config.getParameterNameProvider());
+                        }
+                    }
+
+                    validatorCache.put(contextResolver,
+                            new DefaultConfiguredValidator(context.getValidator(), this.validationConfig,
+                                    validateOnExecutionHandler, getValidationInterceptors()));
+                }
+
+                return validatorCache.get(contextResolver);
+            }
+        }
+
+        private Iterable<ValidationInterceptor> getValidationInterceptors() {
+            final Iterable<RankedProvider<ValidationInterceptor>> validationInterceptorIterable =
+                    org.glassfish.jersey.internal.inject.Providers
+                            .getAllRankedProviders(injectionManager, ValidationInterceptor.class);
+            return org.glassfish.jersey.internal.inject.Providers.sortRankedProviders(
+                    new RankedComparator<ValidationInterceptor>(), validationInterceptorIterable);
+        }
+
+        /**
+         * Return default validator.
+         *
+         * @return default validator.
+         */
+        private ConfiguredValidator getDefaultValidator() {
+            if (defaultValidator == null) {
+                final ValidateOnExecutionHandler validateOnExecutionHandler =
+                        new ValidateOnExecutionHandler(validationConfig, !isValidateOnExecutableOverrideCheckDisabled());
+                final Validator validator = getDefaultValidatorContext(validateOnExecutionHandler).getValidator();
+
+                defaultValidator = new DefaultConfiguredValidator(validator, validationConfig,
+                        validateOnExecutionHandler, getValidationInterceptors());
+            }
+            return defaultValidator;
+        }
+
+        /**
+         * Return default {@link ValidatorContext validator context} able to inject JAX-RS resources/providers.
+         *
+         * @param handler handler to create traversable resolver for.
+         * @return default validator context.
+         */
+        private ValidatorContext getDefaultValidatorContext(final ValidateOnExecutionHandler handler) {
+            final ValidatorContext context = factory.usingContext();
+
+            // Default Configuration.
+            context.constraintValidatorFactory(resourceContext.getResource(InjectingConstraintValidatorFactory.class));
+
+            // Traversable Resolver.
+            context.traversableResolver(getTraversableResolver(factory.getTraversableResolver(), handler));
+
+            return context;
+        }
+
+        /**
+         * Create traversable resolver able to process {@link javax.validation.executable.ValidateOnExecution} annotation on
+         * beans.
+         *
+         * @param delegate resolver to be wrapped into the custom traversable resolver.
+         * @param handler  handler to create traversable resolver for.
+         * @return custom traversable resolver.
+         */
+        private ValidateOnExecutionTraversableResolver getTraversableResolver(TraversableResolver delegate,
+                                                                              final ValidateOnExecutionHandler handler) {
+            if (delegate == null) {
+                delegate = validationConfig.getDefaultTraversableResolver();
+            }
+
+            final boolean validationEnabled = validationConfig.getBootstrapConfiguration().isExecutableValidationEnabled();
+            final ValidateOnExecutionTraversableResolver traversableResolver = new
+                    ValidateOnExecutionTraversableResolver(delegate, handler, validationEnabled);
+
+            return resourceContext.initResource(traversableResolver);
+        }
+
+        private boolean isValidateOnExecutableOverrideCheckDisabled() {
+            return PropertiesHelper.isProperty(
+                    jaxRsConfig.getProperty(ServerProperties.BV_DISABLE_VALIDATE_ON_EXECUTABLE_OVERRIDE_CHECK));
+        }
+    }
+}
diff --git a/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidationErrorMessageBodyWriter.java b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidationErrorMessageBodyWriter.java
new file mode 100644
index 0000000..6383246
--- /dev/null
+++ b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidationErrorMessageBodyWriter.java
@@ -0,0 +1,140 @@
+/*
+ * 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.server.validation.internal;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Collection;
+import java.util.Collections;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyWriter;
+
+import org.glassfish.jersey.message.MessageUtils;
+import org.glassfish.jersey.server.validation.ValidationError;
+
+/**
+ * {@link MessageBodyWriter} providing support for (collections of) {@link ValidationError}
+ * that is able to output instances to {@code text/plain}/{@code text/html}.
+ *
+ * @author Michal Gajdos
+ */
+final class ValidationErrorMessageBodyWriter implements MessageBodyWriter<Object> {
+
+    @Override
+    public boolean isWriteable(final Class<?> type,
+                               final Type genericType,
+                               final Annotation[] annotations,
+                               final MediaType mediaType) {
+        return isSupportedMediaType(mediaType) && isSupportedType(type, genericType);
+    }
+
+    private boolean isSupportedType(final Class<?> type, final Type genericType) {
+        if (ValidationError.class.isAssignableFrom(type)) {
+            return true;
+        } else if (Collection.class.isAssignableFrom(type)) {
+            if (genericType instanceof ParameterizedType) {
+                return ValidationError.class
+                        .isAssignableFrom((Class) ((ParameterizedType) genericType).getActualTypeArguments()[0]);
+            }
+        }
+        return false;
+    }
+
+    private boolean isSupportedMediaType(final MediaType mediaType) {
+        return MediaType.TEXT_HTML_TYPE.equals(mediaType) || MediaType.TEXT_PLAIN_TYPE.equals(mediaType);
+    }
+
+    @Override
+    public long getSize(final Object validationErrors,
+                        final Class<?> type,
+                        final Type genericType,
+                        final Annotation[] annotations,
+                        final MediaType mediaType) {
+        return -1;
+    }
+
+    @Override
+    public void writeTo(final Object entity,
+                        final Class<?> type,
+                        final Type genericType,
+                        final Annotation[] annotations,
+                        final MediaType mediaType,
+                        final MultivaluedMap<String, Object> httpHeaders,
+                        final OutputStream entityStream) throws IOException, WebApplicationException {
+        final Collection<ValidationError> errors;
+
+        if (entity instanceof ValidationError) {
+            errors = Collections.singleton((ValidationError) entity);
+        } else {
+            //noinspection unchecked
+            errors = (Collection<ValidationError>) entity;
+        }
+
+        final boolean isPlain = MediaType.TEXT_PLAIN_TYPE.getSubtype().equals(mediaType.getSubtype());
+
+        final StringBuilder builder = new StringBuilder();
+
+        // Root <div>
+        if (!isPlain) {
+            builder.append("<div class=\"validation-errors\">");
+        }
+
+        for (final ValidationError error : errors) {
+            if (!isPlain) {
+                builder.append("<div class=\"validation-error\">");
+            }
+
+            // Message.
+            builder.append(isPlain ? error.getMessage() : "<span class=\"message\">" + error.getMessage() + "</span>");
+            builder.append(' ');
+
+            builder.append('(');
+
+            // Path.
+            builder.append(isPlain ? "path = " : ("<span class=\"path\"><strong>path</strong> = "));
+            builder.append(isPlain ? error.getPath() : (error.getPath() + "</span>"));
+            builder.append(',');
+            builder.append(' ');
+
+            // Invalid value.
+            builder.append(isPlain ? "invalidValue = " : ("<span class=\"invalid-value\"><strong>invalidValue</strong> = "));
+            builder.append(isPlain ? error.getInvalidValue() : (error.getInvalidValue() + "</span>"));
+
+            builder.append(')');
+
+            if (!isPlain) {
+                builder.append("</div>");
+            } else {
+                builder.append('\n');
+            }
+        }
+
+        // Root <div>
+        if (!isPlain) {
+            builder.append("</div>");
+        }
+
+        entityStream.write(builder.toString().getBytes(MessageUtils.getCharset(mediaType)));
+        entityStream.flush();
+    }
+}
diff --git a/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidationExceptionMapper.java b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidationExceptionMapper.java
new file mode 100644
index 0000000..b569607
--- /dev/null
+++ b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidationExceptionMapper.java
@@ -0,0 +1,100 @@
+/*
+ * 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.server.validation.internal;
+
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.GenericEntity;
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Request;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Variant;
+import javax.ws.rs.ext.ExceptionMapper;
+
+import javax.inject.Provider;
+import javax.validation.ConstraintViolationException;
+import javax.validation.ValidationException;
+
+import org.glassfish.jersey.server.ServerProperties;
+import org.glassfish.jersey.server.validation.ValidationError;
+
+/**
+ * {@link ExceptionMapper} for {@link ValidationException}.
+ * <p/>
+ * If {@value ServerProperties#BV_SEND_ERROR_IN_RESPONSE} property is enabled then a list of {@link ValidationError}
+ * instances is sent in {@link Response} as well (in addition to HTTP 400/500 status code). Supported media types are:
+ * {@code application/json}/{@code application/xml} (in appropriate provider is registered on server) or
+ * {@code text/html}/{@code text/plain} (via custom {@link ValidationErrorMessageBodyWriter}).
+ *
+ * @author Michal Gajdos
+ */
+public final class ValidationExceptionMapper implements ExceptionMapper<ValidationException> {
+
+    private static final Logger LOGGER = Logger.getLogger(ValidationExceptionMapper.class.getName());
+
+    @Context
+    private Configuration config;
+    @Context
+    private Provider<Request> request;
+
+    @Override
+    public Response toResponse(final ValidationException exception) {
+        if (exception instanceof ConstraintViolationException) {
+            LOGGER.log(Level.FINER, LocalizationMessages.CONSTRAINT_VIOLATIONS_ENCOUNTERED(), exception);
+
+            final ConstraintViolationException cve = (ConstraintViolationException) exception;
+            final Response.ResponseBuilder response = Response.status(ValidationHelper.getResponseStatus(cve));
+
+            // Entity.
+            final Object property = config.getProperty(ServerProperties.BV_SEND_ERROR_IN_RESPONSE);
+            if (property != null && Boolean.valueOf(property.toString())) {
+                final List<Variant> variants = Variant.mediaTypes(
+                        MediaType.TEXT_PLAIN_TYPE,
+                        MediaType.TEXT_HTML_TYPE,
+                        MediaType.APPLICATION_XML_TYPE,
+                        MediaType.APPLICATION_JSON_TYPE).build();
+                final Variant variant = request.get().selectVariant(variants);
+                if (variant != null) {
+                    response.type(variant.getMediaType());
+                } else {
+
+                    // default media type which will be used only when none media type from {@value variants} is in accept
+                    // header of original request.
+                    // could be settable by configuration property.
+                    response.type(MediaType.TEXT_PLAIN_TYPE);
+                }
+                response.entity(
+                        new GenericEntity<>(
+                                ValidationHelper.constraintViolationToValidationErrors(cve),
+                                new GenericType<List<ValidationError>>() {}.getType()
+                        )
+                );
+            }
+
+            return response.build();
+        } else {
+            LOGGER.log(Level.WARNING, LocalizationMessages.VALIDATION_EXCEPTION_RAISED(), exception);
+
+            return Response.serverError().entity(exception.getMessage()).build();
+        }
+    }
+}
diff --git a/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidationHelper.java b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidationHelper.java
new file mode 100644
index 0000000..5e92de9
--- /dev/null
+++ b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidationHelper.java
@@ -0,0 +1,132 @@
+/*
+ * 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.server.validation.internal;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import javax.validation.ConstraintViolation;
+import javax.validation.ConstraintViolationException;
+import javax.validation.ElementKind;
+import javax.validation.Path;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.server.validation.ValidationError;
+
+/**
+ * Utility methods for Bean Validation processing.
+ *
+ * @author Michal Gajdos
+ * @since 2.3
+ */
+public final class ValidationHelper {
+
+    /**
+     * Extract {@link ConstraintViolation constraint violations} from given exception and transform them into a list of
+     * {@link ValidationError validation errors}.
+     *
+     * @param violation exception containing constraint violations.
+     * @return list of validation errors (not {@code null}).
+     */
+    public static List<ValidationError> constraintViolationToValidationErrors(final ConstraintViolationException violation) {
+        return violation.getConstraintViolations().stream().map(violation1 -> new ValidationError(
+                violation1.getMessage(),
+                violation1.getMessageTemplate(),
+                getViolationPath(violation1),
+                getViolationInvalidValue(violation1.getInvalidValue())
+        )).collect(Collectors.toList());
+    }
+
+    /**
+     * Provide a string value of (invalid) value that caused the exception.
+     *
+     * @param invalidValue invalid value causing BV exception.
+     * @return string value of given object or {@code null}.
+     */
+    private static String getViolationInvalidValue(final Object invalidValue) {
+        if (invalidValue == null) {
+            return null;
+        }
+
+        if (invalidValue.getClass().isArray()) {
+            if (invalidValue instanceof Object[]) {
+                return Arrays.toString((Object[]) invalidValue);
+            } else if (invalidValue instanceof boolean[]) {
+                return Arrays.toString((boolean[]) invalidValue);
+            } else if (invalidValue instanceof byte[]) {
+                return Arrays.toString((byte[]) invalidValue);
+            } else if (invalidValue instanceof char[]) {
+                return Arrays.toString((char[]) invalidValue);
+            } else if (invalidValue instanceof double[]) {
+                return Arrays.toString((double[]) invalidValue);
+            } else if (invalidValue instanceof float[]) {
+                return Arrays.toString((float[]) invalidValue);
+            } else if (invalidValue instanceof int[]) {
+                return Arrays.toString((int[]) invalidValue);
+            } else if (invalidValue instanceof long[]) {
+                return Arrays.toString((long[]) invalidValue);
+            } else if (invalidValue instanceof short[]) {
+                return Arrays.toString((short[]) invalidValue);
+            }
+        }
+
+        return invalidValue.toString();
+    }
+
+    /**
+     * Get a path to a field causing constraint violations.
+     *
+     * @param violation constraint violation.
+     * @return path to a property that caused constraint violations.
+     */
+    private static String getViolationPath(final ConstraintViolation violation) {
+        final String rootBeanName = violation.getRootBean().getClass().getSimpleName();
+        final String propertyPath = violation.getPropertyPath().toString();
+
+        return rootBeanName + (!"".equals(propertyPath) ? '.' + propertyPath : "");
+    }
+
+    /**
+     * Determine the response status (400 or 500) from the given BV exception.
+     *
+     * @param violation BV exception.
+     * @return response status (400 or 500).
+     */
+    public static Response.Status getResponseStatus(final ConstraintViolationException violation) {
+        final Iterator<ConstraintViolation<?>> iterator = violation.getConstraintViolations().iterator();
+
+        if (iterator.hasNext()) {
+            for (final Path.Node node : iterator.next().getPropertyPath()) {
+                final ElementKind kind = node.getKind();
+
+                if (ElementKind.RETURN_VALUE.equals(kind)) {
+                    return Response.Status.INTERNAL_SERVER_ERROR;
+                }
+            }
+        }
+
+        return Response.Status.BAD_REQUEST;
+    }
+
+    /**
+     * Prevent instantiation.
+     */
+    private ValidationHelper() {
+    }
+}
diff --git a/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidationInterceptorExecutor.java b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidationInterceptorExecutor.java
new file mode 100644
index 0000000..6f0f728
--- /dev/null
+++ b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/ValidationInterceptorExecutor.java
@@ -0,0 +1,92 @@
+/*
+ * 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.server.validation.internal;
+
+import java.util.Iterator;
+
+import javax.validation.ValidationException;
+
+import org.glassfish.jersey.server.model.Invocable;
+import org.glassfish.jersey.server.spi.ValidationInterceptor;
+import org.glassfish.jersey.server.spi.ValidationInterceptorContext;
+
+/**
+ * Validation executor for resource method validation processing. It is intended for a one-off usage
+ * when the executor instance serves also as a {@link org.glassfish.jersey.server.spi.ValidationInterceptorContext}.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+final class ValidationInterceptorExecutor implements ValidationInterceptorContext {
+
+    private Object resource;
+    private Object[] args;
+    private final Invocable invocable;
+
+    private final Iterator<ValidationInterceptor> iterator;
+
+    /**
+     * Create a one-off validation executor for given resource, invocable and parameter and given
+     * interceptors.
+     *
+     * @param resource  actual resource instance to get validated
+     * @param invocable resource method
+     * @param args      actual resource method parameters
+     * @param iterator  validator interceptors to be involved
+     */
+    public ValidationInterceptorExecutor(
+            final Object resource,
+            final Invocable invocable,
+            final Object[] args,
+            final Iterator<ValidationInterceptor> iterator) {
+
+        this.resource = resource;
+        this.invocable = invocable;
+        this.args = args;
+        this.iterator = iterator;
+    }
+
+    @Override
+    public Object getResource() {
+        return resource;
+    }
+
+    @Override
+    public void setResource(final Object resource) {
+        this.resource = resource;
+    }
+
+    @Override
+    public Invocable getInvocable() {
+        return invocable;
+    }
+
+    @Override
+    public Object[] getArgs() {
+        return args;
+    }
+
+    @Override
+    public void setArgs(final Object[] args) {
+        this.args = args;
+    }
+
+    @Override
+    public void proceed() throws ValidationException {
+        final ValidationInterceptor validationInterceptor = iterator.next();
+        validationInterceptor.onValidate(this);
+    }
+}
diff --git a/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/package-info.java b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/internal/package-info.java
new file mode 100644
index 0000000..ab78478
--- /dev/null
+++ b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/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 server-side bean validation internal classes.
+ */
+package org.glassfish.jersey.server.validation.internal;
diff --git a/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/package-info.java b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/package-info.java
new file mode 100644
index 0000000..ff31f90
--- /dev/null
+++ b/ext/bean-validation/src/main/java/org/glassfish/jersey/server/validation/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 server-side bean validation classes.
+ */
+package org.glassfish.jersey.server.validation;
diff --git a/ext/bean-validation/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.ForcedAutoDiscoverable b/ext/bean-validation/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.ForcedAutoDiscoverable
new file mode 100644
index 0000000..1afc0d8
--- /dev/null
+++ b/ext/bean-validation/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.ForcedAutoDiscoverable
@@ -0,0 +1 @@
+org.glassfish.jersey.server.validation.internal.ValidationAutoDiscoverable
\ No newline at end of file
diff --git a/ext/bean-validation/src/main/resources/org/glassfish/jersey/server/validation/internal/localization.properties b/ext/bean-validation/src/main/resources/org/glassfish/jersey/server/validation/internal/localization.properties
new file mode 100644
index 0000000..97018fa
--- /dev/null
+++ b/ext/bean-validation/src/main/resources/org/glassfish/jersey/server/validation/internal/localization.properties
@@ -0,0 +1,21 @@
+#
+# 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
+#
+
+constraint.violations.encountered=Following ConstraintViolations has been encountered.
+# {0} - Class#methodName, i.e. java.lang.String#intern
+override.check.error=Multiple ValidateOnExecution annotation definitions are hosted in the hierarchy of {0} method.
+validation.exception.raised=Unexpected Bean Validation problem.
+validation.exception.provider=Failed to configure the default validation provider.
diff --git a/ext/cdi/jersey-cdi1x-ban-custom-hk2-binding/pom.xml b/ext/cdi/jersey-cdi1x-ban-custom-hk2-binding/pom.xml
new file mode 100644
index 0000000..8b7598c
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x-ban-custom-hk2-binding/pom.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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
+
+-->
+
+<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.ext.cdi</groupId>
+        <artifactId>project</artifactId>
+        <version>2.28-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>jersey-cdi1x-ban-custom-hk2-binding</artifactId>
+    <packaging>jar</packaging>
+    <name>jersey-ext-cdi1x-ban-custom-hk2-binding</name>
+
+    <description>Jersey CDI integration - this module disables custom HK2 bindings</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.glassfish.jersey.ext.cdi</groupId>
+            <artifactId>jersey-cdi1x</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.jersey.core</groupId>
+            <artifactId>jersey-common</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.hamcrest</groupId>
+            <artifactId>hamcrest-library</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+           <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>
+                        <Export-Package>org.glassfish.jersey.ext.cdi1x.hk2ban</Export-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/ext/cdi/jersey-cdi1x-ban-custom-hk2-binding/src/main/java/org/glassfish/jersey/ext/cdi1x/hk2ban/EmptyHk2CustomInjectionTypeProvider.java b/ext/cdi/jersey-cdi1x-ban-custom-hk2-binding/src/main/java/org/glassfish/jersey/ext/cdi1x/hk2ban/EmptyHk2CustomInjectionTypeProvider.java
new file mode 100644
index 0000000..2e408f0
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x-ban-custom-hk2-binding/src/main/java/org/glassfish/jersey/ext/cdi1x/hk2ban/EmptyHk2CustomInjectionTypeProvider.java
@@ -0,0 +1,37 @@
+/*
+ * 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.ext.cdi1x.hk2ban;
+
+import java.lang.reflect.Type;
+import java.util.Collections;
+import java.util.Set;
+
+import org.glassfish.jersey.ext.cdi1x.spi.Hk2CustomBoundTypesProvider;
+
+/**
+ * Utility class that effectively disables any attempts of Jersey/CDI integration layer
+ * to delegate injection from CDI to HK2.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public final class EmptyHk2CustomInjectionTypeProvider implements Hk2CustomBoundTypesProvider {
+
+    @Override
+    public Set<Type> getHk2Types() {
+        return Collections.emptySet();
+    }
+}
diff --git a/ext/cdi/jersey-cdi1x-ban-custom-hk2-binding/src/main/java/org/glassfish/jersey/ext/cdi1x/hk2ban/package-info.java b/ext/cdi/jersey-cdi1x-ban-custom-hk2-binding/src/main/java/org/glassfish/jersey/ext/cdi1x/hk2ban/package-info.java
new file mode 100644
index 0000000..ecc230e
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x-ban-custom-hk2-binding/src/main/java/org/glassfish/jersey/ext/cdi1x/hk2ban/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * 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
+ */
+
+/**
+ * Jersey package supporting Jersey HK2 custom injection binding
+ * to be disabled in CDI environment.
+ */
+package org.glassfish.jersey.ext.cdi1x.hk2ban;
diff --git a/ext/cdi/jersey-cdi1x-ban-custom-hk2-binding/src/main/resources/META-INF/services/org.glassfish.jersey.ext.cdi1x.spi.Hk2CustomBoundTypesProvider b/ext/cdi/jersey-cdi1x-ban-custom-hk2-binding/src/main/resources/META-INF/services/org.glassfish.jersey.ext.cdi1x.spi.Hk2CustomBoundTypesProvider
new file mode 100644
index 0000000..5fe4f00
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x-ban-custom-hk2-binding/src/main/resources/META-INF/services/org.glassfish.jersey.ext.cdi1x.spi.Hk2CustomBoundTypesProvider
@@ -0,0 +1 @@
+org.glassfish.jersey.ext.cdi1x.hk2ban.EmptyHk2CustomInjectionTypeProvider
diff --git a/ext/cdi/jersey-cdi1x-ban-custom-hk2-binding/src/test/java/org/glassfish/jersey/ext/cdi1x/hk2ban/EmptyHk2CustomInjectionTypeProviderTest.java b/ext/cdi/jersey-cdi1x-ban-custom-hk2-binding/src/test/java/org/glassfish/jersey/ext/cdi1x/hk2ban/EmptyHk2CustomInjectionTypeProviderTest.java
new file mode 100644
index 0000000..6bce650
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x-ban-custom-hk2-binding/src/test/java/org/glassfish/jersey/ext/cdi1x/hk2ban/EmptyHk2CustomInjectionTypeProviderTest.java
@@ -0,0 +1,53 @@
+/*
+ * 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.ext.cdi1x.hk2ban;
+
+import java.lang.reflect.Type;
+
+import org.glassfish.jersey.ext.cdi1x.spi.Hk2CustomBoundTypesProvider;
+import org.glassfish.jersey.internal.ServiceFinder;
+
+import org.junit.Test;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.emptyCollectionOf;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+
+/**
+ * Test for {@link EmptyHk2CustomInjectionTypeProvider}.
+ * Make sure that the empty provider could be loaded and provides an empty type set.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public class EmptyHk2CustomInjectionTypeProviderTest {
+
+    /**
+     * Test sub-resource detection.
+     */
+    @Test
+    public void testEmptyProviderLookup() {
+
+        final Hk2CustomBoundTypesProvider[] providers = ServiceFinder.find(Hk2CustomBoundTypesProvider.class).toArray();
+        assertThat(providers, is(notNullValue()));
+        assertThat(providers.length, is(1));
+
+        final Hk2CustomBoundTypesProvider theOnlyProvider = providers[0];
+        assertThat(theOnlyProvider, is(instanceOf(EmptyHk2CustomInjectionTypeProvider.class)));
+        assertThat(theOnlyProvider.getHk2Types(), is(emptyCollectionOf(Type.class)));
+    }
+}
diff --git a/ext/cdi/jersey-cdi1x-servlet/pom.xml b/ext/cdi/jersey-cdi1x-servlet/pom.xml
new file mode 100644
index 0000000..e09cfe5
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x-servlet/pom.xml
@@ -0,0 +1,111 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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
+
+-->
+
+<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.ext.cdi</groupId>
+        <artifactId>project</artifactId>
+        <version>2.28-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>jersey-cdi1x-servlet</artifactId>
+    <packaging>jar</packaging>
+    <name>jersey-ext-cdi1x-servlet</name>
+
+    <description>Jersey CDI 1.x Servlet Support</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>javax.servlet-api</artifactId>
+            <version>${servlet3.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>javax.enterprise</groupId>
+            <artifactId>cdi-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.glassfish.jersey.ext.cdi</groupId>
+            <artifactId>jersey-cdi1x</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.jersey.core</groupId>
+            <artifactId>jersey-server</artifactId>
+            <version>${project.version}</version>
+        </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>
+                        <Export-Package>org.glassfish.jersey.ext.cdi1x.servlet.internal</Export-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/ext/cdi/jersey-cdi1x-servlet/src/main/java/org/glassfish/jersey/ext/cdi1x/servlet/internal/CdiExternalRequestScope.java b/ext/cdi/jersey-cdi1x-servlet/src/main/java/org/glassfish/jersey/ext/cdi1x/servlet/internal/CdiExternalRequestScope.java
new file mode 100644
index 0000000..94217f2
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x-servlet/src/main/java/org/glassfish/jersey/ext/cdi1x/servlet/internal/CdiExternalRequestScope.java
@@ -0,0 +1,57 @@
+/*
+ * 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.ext.cdi1x.servlet.internal;
+
+import javax.enterprise.context.ApplicationScoped;
+
+import org.glassfish.jersey.ext.cdi1x.internal.JerseyVetoed;
+import org.glassfish.jersey.internal.inject.InjectionManager;
+import org.glassfish.jersey.server.spi.ExternalRequestContext;
+import org.glassfish.jersey.server.spi.ExternalRequestScope;
+
+/**
+ * Weld specific request scope to align CDI request context with Jersey.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@ApplicationScoped
+@JerseyVetoed
+public class CdiExternalRequestScope implements ExternalRequestScope<Object> {
+
+    public static final ThreadLocal<InjectionManager> actualInjectionManager = new ThreadLocal<>();
+
+    @Override
+    public ExternalRequestContext<Object> open(InjectionManager injectionManager) {
+        actualInjectionManager.set(injectionManager);
+        return new ExternalRequestContext<>(null);
+    }
+
+    @Override
+    public void resume(final ExternalRequestContext<Object> ctx, InjectionManager injectionManager) {
+        actualInjectionManager.set(injectionManager);
+    }
+
+    @Override
+    public void suspend(final ExternalRequestContext<Object> ctx, InjectionManager injectionManager) {
+        actualInjectionManager.remove();
+    }
+
+    @Override
+    public void close() {
+        actualInjectionManager.remove();
+    }
+}
diff --git a/ext/cdi/jersey-cdi1x-servlet/src/main/java/org/glassfish/jersey/ext/cdi1x/servlet/internal/CdiExternalRequestScopeExtension.java b/ext/cdi/jersey-cdi1x-servlet/src/main/java/org/glassfish/jersey/ext/cdi1x/servlet/internal/CdiExternalRequestScopeExtension.java
new file mode 100644
index 0000000..53fe1a0
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x-servlet/src/main/java/org/glassfish/jersey/ext/cdi1x/servlet/internal/CdiExternalRequestScopeExtension.java
@@ -0,0 +1,148 @@
+/*
+ * 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.ext.cdi1x.servlet.internal;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.enterprise.context.Dependent;
+import javax.enterprise.context.spi.CreationalContext;
+import javax.enterprise.event.Observes;
+import javax.enterprise.inject.Any;
+import javax.enterprise.inject.Default;
+import javax.enterprise.inject.spi.AfterBeanDiscovery;
+import javax.enterprise.inject.spi.AnnotatedType;
+import javax.enterprise.inject.spi.Bean;
+import javax.enterprise.inject.spi.BeanManager;
+import javax.enterprise.inject.spi.BeforeBeanDiscovery;
+import javax.enterprise.inject.spi.Extension;
+import javax.enterprise.inject.spi.InjectionPoint;
+import javax.enterprise.inject.spi.InjectionTarget;
+import javax.enterprise.util.AnnotationLiteral;
+
+/**
+ * CDI extension to register {@link CdiExternalRequestScope}.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public class CdiExternalRequestScopeExtension implements Extension {
+
+    public static final AnnotationLiteral<Default> DEFAULT_ANNOTATION_LITERAL = new AnnotationLiteral<Default>() {};
+    public static final AnnotationLiteral<Any> ANY_ANNOTATION_LITERAL = new AnnotationLiteral<Any>() {};
+
+    private AnnotatedType<CdiExternalRequestScope> requestScopeType;
+
+    /**
+     * Register our external request scope.
+     *
+     * @param beforeBeanDiscoveryEvent CDI bootstrap event.
+     * @param beanManager current bean manager.
+     */
+    private void beforeBeanDiscovery(@Observes BeforeBeanDiscovery beforeBeanDiscoveryEvent, final BeanManager beanManager) {
+        requestScopeType = beanManager.createAnnotatedType(CdiExternalRequestScope.class);
+        beforeBeanDiscoveryEvent.addAnnotatedType(requestScopeType);
+    }
+
+    /**
+     * Register a CDI bean for the external scope.
+     *
+     * @param afterBeanDiscovery CDI bootstrap event.
+     * @param beanManager current bean manager
+     */
+    private void afterBeanDiscovery(@Observes AfterBeanDiscovery afterBeanDiscovery, BeanManager beanManager) {
+
+        // we need the injection target so that CDI could instantiate the original interceptor for us
+        final InjectionTarget<CdiExternalRequestScope> interceptorTarget = beanManager.createInjectionTarget(requestScopeType);
+
+
+        afterBeanDiscovery.addBean(new Bean<CdiExternalRequestScope>() {
+
+            @Override
+            public Class<?> getBeanClass() {
+                return CdiExternalRequestScope.class;
+            }
+
+            @Override
+            public Set<InjectionPoint> getInjectionPoints() {
+                return interceptorTarget.getInjectionPoints();
+            }
+
+            @Override
+            public String getName() {
+                return "CdiExternalRequestScope";
+            }
+
+            @Override
+            public Set<Annotation> getQualifiers() {
+                return new HashSet<Annotation>() {{
+                    add(DEFAULT_ANNOTATION_LITERAL);
+                    add(ANY_ANNOTATION_LITERAL);
+                }};
+            }
+
+            @Override
+            public Class<? extends Annotation> getScope() {
+                return Dependent.class;
+            }
+
+            @Override
+            public Set<Class<? extends Annotation>> getStereotypes() {
+                return Collections.emptySet();
+            }
+
+            @Override
+            public Set<Type> getTypes() {
+                return new HashSet<Type>() {{
+                    add(CdiExternalRequestScope.class);
+                    add(Object.class);
+                }};
+            }
+
+            @Override
+            public boolean isAlternative() {
+                return false;
+            }
+
+            @Override
+            public boolean isNullable() {
+                return false;
+            }
+
+            @Override
+            public CdiExternalRequestScope create(CreationalContext<CdiExternalRequestScope> ctx) {
+
+                final CdiExternalRequestScope result = interceptorTarget.produce(ctx);
+                interceptorTarget.inject(result, ctx);
+                interceptorTarget.postConstruct(result);
+                return result;
+            }
+
+
+            @Override
+            public void destroy(CdiExternalRequestScope instance,
+                                CreationalContext<CdiExternalRequestScope> ctx) {
+
+                interceptorTarget.preDestroy(instance);
+                interceptorTarget.dispose(instance);
+                ctx.release();
+            }
+        });
+    }
+}
diff --git a/ext/cdi/jersey-cdi1x-servlet/src/main/java/org/glassfish/jersey/ext/cdi1x/servlet/internal/ServletInjectionManagerStore.java b/ext/cdi/jersey-cdi1x-servlet/src/main/java/org/glassfish/jersey/ext/cdi1x/servlet/internal/ServletInjectionManagerStore.java
new file mode 100644
index 0000000..2c6f154
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x-servlet/src/main/java/org/glassfish/jersey/ext/cdi1x/servlet/internal/ServletInjectionManagerStore.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.ext.cdi1x.servlet.internal;
+
+import org.glassfish.jersey.ext.cdi1x.internal.GenericInjectionManagerStore;
+import org.glassfish.jersey.ext.cdi1x.internal.spi.InjectionManagerStore;
+import org.glassfish.jersey.internal.inject.InjectionManager;
+
+/**
+ * {@link InjectionManagerStore injection manager} for servlet based containers. The provider
+ * enables WAR and EAR to be deployed on a servlet container and be properly injected.
+ *
+ * @author Michal Gajdos
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ * @since 2.17
+ */
+public class ServletInjectionManagerStore extends GenericInjectionManagerStore {
+
+    @Override
+    public InjectionManager lookupInjectionManager() {
+        return CdiExternalRequestScope.actualInjectionManager.get();
+    }
+}
diff --git a/ext/cdi/jersey-cdi1x-servlet/src/main/java/org/glassfish/jersey/ext/cdi1x/servlet/internal/package-info.java b/ext/cdi/jersey-cdi1x-servlet/src/main/java/org/glassfish/jersey/ext/cdi1x/servlet/internal/package-info.java
new file mode 100644
index 0000000..cad877a
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x-servlet/src/main/java/org/glassfish/jersey/ext/cdi1x/servlet/internal/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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
+ */
+
+/**
+ * Jersey internal package supporting Jersey CDI integration in Servlet containers.
+ *
+ * @since 2.17
+ */
+package org.glassfish.jersey.ext.cdi1x.servlet.internal;
diff --git a/ext/cdi/jersey-cdi1x-servlet/src/main/resources/META-INF/beans.xml b/ext/cdi/jersey-cdi1x-servlet/src/main/resources/META-INF/beans.xml
new file mode 100644
index 0000000..aaf0547
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x-servlet/src/main/resources/META-INF/beans.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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
+
+-->
+
+<beans/>
diff --git a/ext/cdi/jersey-cdi1x-servlet/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension b/ext/cdi/jersey-cdi1x-servlet/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
new file mode 100644
index 0000000..952037c
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x-servlet/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
@@ -0,0 +1 @@
+org.glassfish.jersey.ext.cdi1x.servlet.internal.CdiExternalRequestScopeExtension
diff --git a/ext/cdi/jersey-cdi1x-servlet/src/main/resources/META-INF/services/org.glassfish.jersey.ext.cdi1x.internal.spi.InjectionManagerStore b/ext/cdi/jersey-cdi1x-servlet/src/main/resources/META-INF/services/org.glassfish.jersey.ext.cdi1x.internal.spi.InjectionManagerStore
new file mode 100644
index 0000000..fa979a0
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x-servlet/src/main/resources/META-INF/services/org.glassfish.jersey.ext.cdi1x.internal.spi.InjectionManagerStore
@@ -0,0 +1 @@
+org.glassfish.jersey.ext.cdi1x.servlet.internal.ServletInjectionManagerStore
diff --git a/ext/cdi/jersey-cdi1x-servlet/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ExternalRequestScope b/ext/cdi/jersey-cdi1x-servlet/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ExternalRequestScope
new file mode 100644
index 0000000..f46abfc
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x-servlet/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ExternalRequestScope
@@ -0,0 +1 @@
+org.glassfish.jersey.ext.cdi1x.servlet.internal.CdiExternalRequestScope
\ No newline at end of file
diff --git a/ext/cdi/jersey-cdi1x-transaction/pom.xml b/ext/cdi/jersey-cdi1x-transaction/pom.xml
new file mode 100644
index 0000000..cea42c1
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x-transaction/pom.xml
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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
+
+-->
+
+<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.ext.cdi</groupId>
+        <artifactId>project</artifactId>
+        <version>2.28-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>jersey-cdi1x-transaction</artifactId>
+    <packaging>jar</packaging>
+    <name>jersey-ext-cdi1x-transaction</name>
+
+    <description>Jersey CDI 1.x Transactional Support</description>
+
+    <dependencies>
+
+        <dependency>
+            <groupId>javax</groupId>
+            <artifactId>javaee-api</artifactId>
+            <version>7.0</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.glassfish.jersey.ext.cdi</groupId>
+            <artifactId>jersey-cdi1x</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.glassfish.jersey.core</groupId>
+            <artifactId>jersey-server</artifactId>
+            <version>${project.version}</version>
+            <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>
+                        <Export-Package>org.glassfish.jersey.ext.cdi1x.transaction.internal</Export-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/ext/cdi/jersey-cdi1x-transaction/src/main/java/org/glassfish/jersey/ext/cdi1x/transaction/internal/TransactionalExceptionInterceptorProvider.java b/ext/cdi/jersey-cdi1x-transaction/src/main/java/org/glassfish/jersey/ext/cdi1x/transaction/internal/TransactionalExceptionInterceptorProvider.java
new file mode 100644
index 0000000..54ac7eb
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x-transaction/src/main/java/org/glassfish/jersey/ext/cdi1x/transaction/internal/TransactionalExceptionInterceptorProvider.java
@@ -0,0 +1,109 @@
+/*
+ * 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.ext.cdi1x.transaction.internal;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import java.util.List;
+import java.util.Set;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.ext.ExceptionMapper;
+
+import javax.annotation.Priority;
+import javax.enterprise.event.Observes;
+import javax.enterprise.inject.spi.AfterTypeDiscovery;
+import javax.enterprise.inject.spi.BeanManager;
+import javax.enterprise.inject.spi.BeforeBeanDiscovery;
+import javax.enterprise.inject.spi.Extension;
+import javax.inject.Qualifier;
+import javax.interceptor.Interceptor;
+import javax.transaction.TransactionalException;
+
+import org.glassfish.jersey.ext.cdi1x.internal.CdiUtil;
+import org.glassfish.jersey.ext.cdi1x.internal.GenericCdiBeanSupplier;
+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.server.spi.ComponentProvider;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Jersey CDI extension that provides means to retain {@link WebApplicationException}
+ * thrown from JAX-RS components implemented as CDI transactional beans.
+ * This is to avoid the {@link WebApplicationException} from being masked with
+ * {@link TransactionalException}. Jersey will try to restore the original
+ * JAX-RS exception using {@link TransactionalExceptionMapper}.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@Priority(value = Interceptor.Priority.PLATFORM_BEFORE + 199)
+public class TransactionalExceptionInterceptorProvider implements ComponentProvider, Extension {
+
+    private InjectionManager injectionManager;
+    private BeanManager beanManager;
+
+    @Qualifier
+    @Retention(RUNTIME)
+    @Target({METHOD, FIELD, PARAMETER, TYPE})
+    public static @interface WaeQualifier {
+    }
+
+    @Override
+    public void initialize(final InjectionManager injectionManager) {
+        this.injectionManager = injectionManager;
+        this.beanManager = CdiUtil.getBeanManager();
+    }
+
+    @Override
+    public boolean bind(final Class<?> component, final Set<Class<?>> providerContracts) {
+        return false;
+    }
+
+    @Override
+    public void done() {
+        if (beanManager != null) {
+            bindWaeRestoringExceptionMapper();
+        }
+    }
+
+    private void bindWaeRestoringExceptionMapper() {
+        GenericCdiBeanSupplier beanSupplier =
+                new GenericCdiBeanSupplier(TransactionalExceptionMapper.class, injectionManager, beanManager, true);
+        Binding binding = Bindings.supplier(beanSupplier).to(ExceptionMapper.class);
+        injectionManager.register(binding);
+    }
+
+    @SuppressWarnings("unused")
+    private void afterTypeDiscovery(@Observes final AfterTypeDiscovery afterTypeDiscovery) {
+        final List<Class<?>> interceptors = afterTypeDiscovery.getInterceptors();
+        interceptors.add(WebAppExceptionInterceptor.class);
+    }
+
+    @SuppressWarnings("unused")
+    private void beforeBeanDiscovery(@Observes final BeforeBeanDiscovery beforeBeanDiscovery, final javax.enterprise.inject.spi
+            .BeanManager beanManager) {
+        beforeBeanDiscovery.addAnnotatedType(beanManager.createAnnotatedType(WebAppExceptionHolder.class));
+        beforeBeanDiscovery.addAnnotatedType(beanManager.createAnnotatedType(WebAppExceptionInterceptor.class));
+        beforeBeanDiscovery.addAnnotatedType(beanManager.createAnnotatedType(TransactionalExceptionMapper.class));
+    }
+}
diff --git a/ext/cdi/jersey-cdi1x-transaction/src/main/java/org/glassfish/jersey/ext/cdi1x/transaction/internal/TransactionalExceptionMapper.java b/ext/cdi/jersey-cdi1x-transaction/src/main/java/org/glassfish/jersey/ext/cdi1x/transaction/internal/TransactionalExceptionMapper.java
new file mode 100644
index 0000000..3d9b20e
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x-transaction/src/main/java/org/glassfish/jersey/ext/cdi1x/transaction/internal/TransactionalExceptionMapper.java
@@ -0,0 +1,75 @@
+/*
+ * 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.ext.cdi1x.transaction.internal;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.inject.spi.BeanManager;
+import javax.inject.Inject;
+import javax.inject.Provider;
+import javax.transaction.TransactionalException;
+
+import org.glassfish.jersey.ext.cdi1x.internal.JerseyVetoed;
+import org.glassfish.jersey.spi.ExceptionMappers;
+import org.glassfish.jersey.spi.ExtendedExceptionMapper;
+
+/**
+ * Helper class to handle exceptions thrown by JTA layer. If this mapper was not
+ * registered, no {@link WebApplicationException} thrown from a transactional
+ * CDI bean would get properly mapped to corresponding response.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@ApplicationScoped
+@JerseyVetoed
+public class TransactionalExceptionMapper implements ExtendedExceptionMapper<TransactionalException> {
+
+    @Inject
+    @TransactionalExceptionInterceptorProvider.WaeQualifier
+    private WebAppExceptionHolder waeHolder;
+
+    @Inject
+    private BeanManager beanManager;
+
+    @Inject
+    private Provider<ExceptionMappers> mappers;
+
+    @Override
+    public Response toResponse(TransactionalException exception) {
+        final ExceptionMapper mapper = mappers.get().findMapping(exception);
+
+        if (mapper != null && !TransactionalExceptionMapper.class.isAssignableFrom(mapper.getClass())) {
+            return mapper.toResponse(exception);
+        } else {
+            if (waeHolder != null) {
+                final WebApplicationException wae = waeHolder.getException();
+                if (wae != null) {
+                    return wae.getResponse();
+                }
+            }
+            throw exception;
+        }
+    }
+
+    @Override
+    public boolean isMappable(TransactionalException exception) {
+        return true;
+    }
+}
diff --git a/ext/cdi/jersey-cdi1x-transaction/src/main/java/org/glassfish/jersey/ext/cdi1x/transaction/internal/WebAppExceptionHolder.java b/ext/cdi/jersey-cdi1x-transaction/src/main/java/org/glassfish/jersey/ext/cdi1x/transaction/internal/WebAppExceptionHolder.java
new file mode 100644
index 0000000..9f052df
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x-transaction/src/main/java/org/glassfish/jersey/ext/cdi1x/transaction/internal/WebAppExceptionHolder.java
@@ -0,0 +1,51 @@
+/*
+ * 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.ext.cdi1x.transaction.internal;
+
+import java.io.Serializable;
+
+import javax.ws.rs.WebApplicationException;
+
+import javax.enterprise.context.RequestScoped;
+import javax.transaction.Transactional;
+
+import org.glassfish.jersey.ext.cdi1x.internal.JerseyVetoed;
+
+/**
+ * CDI bean to store any {@link WebApplicationException}
+ * thrown in a {@link Transactional} CDI bean for later use
+ * in {@link TransactionalExceptionMapper}.
+ *
+ * @author Jakub.Podlesak (jakub.podlesak at oracle.com)
+ */
+@RequestScoped
+@JerseyVetoed
+@TransactionalExceptionInterceptorProvider.WaeQualifier
+public class WebAppExceptionHolder implements Serializable {
+
+    private static final long serialVersionUID = 31415926535879L;
+
+    private WebApplicationException exception;
+
+    /* package */ void setException(WebApplicationException exception) {
+        this.exception = exception;
+    }
+
+    /* package */ WebApplicationException getException() {
+        return exception;
+    }
+}
diff --git a/ext/cdi/jersey-cdi1x-transaction/src/main/java/org/glassfish/jersey/ext/cdi1x/transaction/internal/WebAppExceptionInterceptor.java b/ext/cdi/jersey-cdi1x-transaction/src/main/java/org/glassfish/jersey/ext/cdi1x/transaction/internal/WebAppExceptionInterceptor.java
new file mode 100644
index 0000000..4c68615
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x-transaction/src/main/java/org/glassfish/jersey/ext/cdi1x/transaction/internal/WebAppExceptionInterceptor.java
@@ -0,0 +1,61 @@
+/*
+ * 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.ext.cdi1x.transaction.internal;
+
+import java.io.Serializable;
+
+import javax.ws.rs.WebApplicationException;
+
+import javax.annotation.Priority;
+import javax.inject.Inject;
+import javax.interceptor.AroundInvoke;
+import javax.interceptor.Interceptor;
+import javax.interceptor.InvocationContext;
+import javax.transaction.Transactional;
+
+import org.glassfish.jersey.ext.cdi1x.internal.JerseyVetoed;
+
+/**
+ * Transactional interceptor to help retain {@link WebApplicationException}
+ * thrown by transactional beans.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@Priority(value = Interceptor.Priority.PLATFORM_BEFORE + 199)
+@Interceptor
+@Transactional
+@JerseyVetoed
+public final class WebAppExceptionInterceptor implements Serializable {
+
+    private static final long serialVersionUID = -1L;
+
+    @Inject
+    @TransactionalExceptionInterceptorProvider.WaeQualifier
+    private WebAppExceptionHolder store;
+
+    @AroundInvoke
+    public Object intercept(final InvocationContext ic) throws Exception {
+        try {
+            return ic.proceed();
+        } catch (final WebApplicationException wae) {
+            if (store != null) {
+                store.setException(wae);
+            }
+            throw wae;
+        }
+    }
+}
diff --git a/ext/cdi/jersey-cdi1x-transaction/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension b/ext/cdi/jersey-cdi1x-transaction/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
new file mode 100644
index 0000000..2d3d1b8
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x-transaction/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
@@ -0,0 +1 @@
+org.glassfish.jersey.ext.cdi1x.transaction.internal.TransactionalExceptionInterceptorProvider
diff --git a/ext/cdi/jersey-cdi1x-transaction/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ComponentProvider b/ext/cdi/jersey-cdi1x-transaction/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ComponentProvider
new file mode 100644
index 0000000..e6b5859
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x-transaction/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ComponentProvider
@@ -0,0 +1,2 @@
+org.glassfish.jersey.ext.cdi1x.transaction.internal.TransactionalExceptionInterceptorProvider
+
diff --git a/ext/cdi/jersey-cdi1x-validation/pom.xml b/ext/cdi/jersey-cdi1x-validation/pom.xml
new file mode 100644
index 0000000..26d209a
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x-validation/pom.xml
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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
+
+-->
+
+<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.ext.cdi</groupId>
+        <artifactId>project</artifactId>
+        <version>2.28-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>jersey-cdi1x-validation</artifactId>
+    <packaging>jar</packaging>
+    <name>jersey-ext-cdi1x-validation</name>
+
+    <description>Jersey CDI 1.x Bean Validation Support</description>
+
+    <dependencies>
+
+        <dependency>
+            <groupId>org.glassfish.jersey.core</groupId>
+            <artifactId>jersey-server</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.hibernate</groupId>
+            <artifactId>hibernate-validator-cdi</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>javax.enterprise</groupId>
+            <artifactId>cdi-api</artifactId>
+            <scope>provided</scope>
+            <version>1.2</version>
+        </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>
+                        <Export-Package>org.glassfish.jersey.ext.cdi1x.validation.internal;version=${project.version}</Export-Package>
+                    </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/ext/cdi/jersey-cdi1x-validation/src/main/java/org/glassfish/jersey/ext/cdi1x/validation/internal/CdiInterceptorWrapper.java b/ext/cdi/jersey-cdi1x-validation/src/main/java/org/glassfish/jersey/ext/cdi1x/validation/internal/CdiInterceptorWrapper.java
new file mode 100644
index 0000000..16a098d
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x-validation/src/main/java/org/glassfish/jersey/ext/cdi1x/validation/internal/CdiInterceptorWrapper.java
@@ -0,0 +1,74 @@
+/*
+ * 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.ext.cdi1x.validation.internal;
+
+import java.util.Set;
+
+import javax.annotation.Priority;
+import javax.enterprise.inject.spi.Bean;
+import javax.enterprise.inject.spi.BeanManager;
+import javax.inject.Inject;
+import javax.interceptor.AroundConstruct;
+import javax.interceptor.AroundInvoke;
+import javax.interceptor.Interceptor;
+import javax.interceptor.InvocationContext;
+
+import org.hibernate.validator.internal.cdi.interceptor.MethodValidated;
+import org.hibernate.validator.internal.cdi.interceptor.ValidationInterceptor;
+
+/**
+ * JAX-RS wrapper for Hibernate CDI bean validation interceptor.
+ * Since Jersey already executes validation on JAX-RS resources,
+ * Jersey registers this wrapper into CDI container so that JAX-RS
+ * components do not get validated twice.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@MethodValidated
+@Interceptor
+@Priority(Interceptor.Priority.PLATFORM_AFTER + 800)
+public class CdiInterceptorWrapper {
+
+    private final ValidationInterceptor interceptor;
+
+    @Inject
+    public CdiInterceptorWrapper(BeanManager beanManager) {
+        // get the original interceptor from the bean manager directly
+        // to avoid CDI bootstrap issues caused by wrong extension ordering
+        final Set<Bean<?>> interceptorBeans = beanManager.getBeans(ValidationInterceptor.class);
+        final Bean<?> interceptorBean = beanManager.resolve(interceptorBeans);
+        this.interceptor = (ValidationInterceptor) beanManager.getReference(
+                interceptorBean, ValidationInterceptor.class, beanManager.createCreationalContext(interceptorBean));
+    }
+
+    @Inject
+    private CdiInterceptorWrapperExtension extension;
+
+    @AroundInvoke
+    public Object validateMethodInvocation(InvocationContext ctx) throws Exception {
+        final boolean isJaxRsMethod = extension.jaxRsResourceCache.apply(ctx.getMethod().getDeclaringClass());
+        return isJaxRsMethod ? ctx.proceed() : interceptor.validateMethodInvocation(ctx);
+    }
+
+    @AroundConstruct
+    public void validateConstructorInvocation(InvocationContext ctx) throws Exception {
+        final boolean isJaxRsConstructor = extension.jaxRsResourceCache.apply(ctx.getConstructor().getDeclaringClass());
+        if (!isJaxRsConstructor) {
+            interceptor.validateConstructorInvocation(ctx);
+        }
+    }
+}
diff --git a/ext/cdi/jersey-cdi1x-validation/src/main/java/org/glassfish/jersey/ext/cdi1x/validation/internal/CdiInterceptorWrapperExtension.java b/ext/cdi/jersey-cdi1x-validation/src/main/java/org/glassfish/jersey/ext/cdi1x/validation/internal/CdiInterceptorWrapperExtension.java
new file mode 100644
index 0000000..b461110
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x-validation/src/main/java/org/glassfish/jersey/ext/cdi1x/validation/internal/CdiInterceptorWrapperExtension.java
@@ -0,0 +1,170 @@
+/*
+ * 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.ext.cdi1x.validation.internal;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.annotation.Priority;
+import javax.enterprise.context.Dependent;
+import javax.enterprise.context.spi.CreationalContext;
+import javax.enterprise.event.Observes;
+import javax.enterprise.inject.Any;
+import javax.enterprise.inject.Default;
+import javax.enterprise.inject.spi.AfterBeanDiscovery;
+import javax.enterprise.inject.spi.AfterTypeDiscovery;
+import javax.enterprise.inject.spi.AnnotatedType;
+import javax.enterprise.inject.spi.Bean;
+import javax.enterprise.inject.spi.BeanManager;
+import javax.enterprise.inject.spi.BeforeBeanDiscovery;
+import javax.enterprise.inject.spi.Extension;
+import javax.enterprise.inject.spi.InjectionPoint;
+import javax.enterprise.inject.spi.InjectionTarget;
+import javax.enterprise.util.AnnotationLiteral;
+import javax.interceptor.Interceptor;
+
+import org.glassfish.jersey.internal.util.collection.Cache;
+import org.glassfish.jersey.server.model.Resource;
+
+import org.hibernate.validator.internal.cdi.interceptor.ValidationInterceptor;
+
+/**
+ * CDI extension to register {@link CdiInterceptorWrapper}.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@Priority(value = Interceptor.Priority.PLATFORM_BEFORE + 199)
+public class CdiInterceptorWrapperExtension implements Extension {
+
+    public static final AnnotationLiteral<Default> DEFAULT_ANNOTATION_LITERAL = new AnnotationLiteral<Default>() {};
+    public static final AnnotationLiteral<Any> ANY_ANNOTATION_LITERAL = new AnnotationLiteral<Any>() {};
+
+    final Cache<Class<?>, Boolean> jaxRsResourceCache = new Cache<>(clazz -> Resource.from(clazz) != null);
+
+    private AnnotatedType<ValidationInterceptor> interceptorAnnotatedType;
+
+    /**
+     * Register our validation interceptor wrapper.
+     *
+     * @param beforeBeanDiscoveryEvent CDI bootstrap event.
+     * @param beanManager current bean manager.
+     */
+    private void beforeBeanDiscovery(@Observes BeforeBeanDiscovery beforeBeanDiscoveryEvent, final BeanManager beanManager) {
+        beforeBeanDiscoveryEvent.addAnnotatedType(beanManager.createAnnotatedType(CdiInterceptorWrapper.class));
+        interceptorAnnotatedType = beanManager.createAnnotatedType(ValidationInterceptor.class);
+        beforeBeanDiscoveryEvent.addAnnotatedType(interceptorAnnotatedType);
+    }
+
+    /**
+     * Remove the original interceptor, as we are going to proxy the calls with {@link CdiInterceptorWrapper}.
+     *
+     * @param afterTypeDiscovery CDI bootstrap event.
+     */
+    private void afterTypeDiscovery(@Observes final AfterTypeDiscovery afterTypeDiscovery) {
+        afterTypeDiscovery.getInterceptors().remove(ValidationInterceptor.class);
+    }
+
+    /**
+     * Register a CDI bean for the original interceptor so that we can have it injected in our wrapper.
+     *
+     * @param afterBeanDiscovery CDI bootstrap event.
+     * @param beanManager current bean manager
+     */
+    private void afterBeanDiscovery(@Observes AfterBeanDiscovery afterBeanDiscovery, BeanManager beanManager) {
+
+        // we need the injection target so that CDI could instantiate the original interceptor for us
+        final AnnotatedType<ValidationInterceptor> interceptorType = interceptorAnnotatedType;
+        final InjectionTarget<ValidationInterceptor> interceptorTarget = beanManager.createInjectionTarget(interceptorType);
+
+
+        afterBeanDiscovery.addBean(new Bean<ValidationInterceptor>() {
+
+            @Override
+            public Class<?> getBeanClass() {
+                return ValidationInterceptor.class;
+            }
+
+            @Override
+            public Set<InjectionPoint> getInjectionPoints() {
+                return interceptorTarget.getInjectionPoints();
+            }
+
+            @Override
+            public String getName() {
+                return "HibernateValidationInterceptorImpl";
+            }
+
+            @Override
+            public Set<Annotation> getQualifiers() {
+                return new HashSet<Annotation>() {{
+                    add(DEFAULT_ANNOTATION_LITERAL);
+                    add(ANY_ANNOTATION_LITERAL);
+                }};
+            }
+
+            @Override
+            public Class<? extends Annotation> getScope() {
+                return Dependent.class;
+            }
+
+            @Override
+            public Set<Class<? extends Annotation>> getStereotypes() {
+                return Collections.emptySet();
+            }
+
+            @Override
+            public Set<Type> getTypes() {
+                return new HashSet<Type>() {{
+                    add(ValidationInterceptor.class);
+                    add(Object.class);
+                }};
+            }
+
+            @Override
+            public boolean isAlternative() {
+                return false;
+            }
+
+            @Override
+            public boolean isNullable() {
+                return false;
+            }
+
+            @Override
+            public ValidationInterceptor create(CreationalContext<ValidationInterceptor> ctx) {
+
+                final ValidationInterceptor result = interceptorTarget.produce(ctx);
+                interceptorTarget.inject(result, ctx);
+                interceptorTarget.postConstruct(result);
+                return result;
+            }
+
+
+            @Override
+            public void destroy(ValidationInterceptor instance,
+                                CreationalContext<ValidationInterceptor> ctx) {
+
+                interceptorTarget.preDestroy(instance);
+                interceptorTarget.dispose(instance);
+                ctx.release();
+            }
+        });
+    }
+}
diff --git a/ext/cdi/jersey-cdi1x-validation/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension b/ext/cdi/jersey-cdi1x-validation/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
new file mode 100644
index 0000000..7cdf9fe
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x-validation/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
@@ -0,0 +1 @@
+org.glassfish.jersey.ext.cdi1x.validation.internal.CdiInterceptorWrapperExtension
diff --git a/ext/cdi/jersey-cdi1x/pom.xml b/ext/cdi/jersey-cdi1x/pom.xml
new file mode 100644
index 0000000..11ef360
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/pom.xml
@@ -0,0 +1,102 @@
+<?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/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.glassfish.jersey.ext.cdi</groupId>
+        <artifactId>project</artifactId>
+        <version>2.28-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>jersey-cdi1x</artifactId>
+    <packaging>jar</packaging>
+    <name>jersey-ext-cdi1x</name>
+
+    <description>Jersey CDI 1.1 integration</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.glassfish.jersey.inject</groupId>
+            <artifactId>jersey-hk2</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>javax.enterprise</groupId>
+            <artifactId>cdi-api</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.jmockit</groupId>
+            <artifactId>jmockit</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>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Export-Package>
+                            org.glassfish.jersey.ext.cdi1x.spi,org.glassfish.jersey.ext.cdi1x.internal,
+                            org.glassfish.jersey.ext.cdi1x.internal.spi
+                        </Export-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/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/AbstractCdiBeanSupplier.java b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/AbstractCdiBeanSupplier.java
new file mode 100644
index 0000000..d62c6a5
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/AbstractCdiBeanSupplier.java
@@ -0,0 +1,132 @@
+/*
+ * 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.ext.cdi1x.internal;
+
+import java.lang.annotation.Annotation;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import javax.enterprise.context.spi.CreationalContext;
+import javax.enterprise.inject.spi.AnnotatedType;
+import javax.enterprise.inject.spi.Bean;
+import javax.enterprise.inject.spi.BeanManager;
+import javax.enterprise.inject.spi.InjectionTarget;
+import javax.enterprise.inject.spi.InjectionTargetFactory;
+
+import org.glassfish.jersey.internal.inject.DisposableSupplier;
+import org.glassfish.jersey.internal.inject.InjectionManager;
+
+/**
+ * Abstract supplier to provide CDI components obtained from CDI bean manager.
+ * The factory handles CDI managed components as well as non-contextual managed beans.
+ * To specify scope of provided CDI beans, an extension of this supplier
+ * should implement properly annotated {@link java.util.function.Supplier#get()} method that
+ * could just delegate to the existing {@link #_provide()} method.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public abstract class AbstractCdiBeanSupplier<T> implements DisposableSupplier<T> {
+
+    final Class<T> clazz;
+    final InstanceManager<T> referenceProvider;
+    final Annotation[] qualifiers;
+    /**
+     * Create new factory instance for given type and bean manager.
+     *
+     * @param rawType          type of the components to provide.
+     * @param injectionManager actual injection manager instance.
+     * @param beanManager      current bean manager to get references from.
+     * @param cdiManaged       set to {@code true} if the component should be managed by CDI.
+     */
+    public AbstractCdiBeanSupplier(final Class<T> rawType,
+                                     final InjectionManager injectionManager,
+                                     final BeanManager beanManager,
+                                     final boolean cdiManaged) {
+
+        this.clazz = rawType;
+        this.qualifiers = CdiUtil.getQualifiers(clazz.getAnnotations());
+        this.referenceProvider = cdiManaged ? new InstanceManager<T>() {
+
+            final Iterator<Bean<?>> beans = beanManager.getBeans(clazz, qualifiers).iterator();
+            final Bean bean = beans.hasNext() ? beans.next() : null;
+
+            @Override
+            public T getInstance(final Class<T> clazz) {
+                return (bean != null) ? CdiUtil.getBeanReference(clazz, bean, beanManager) : null;
+            }
+
+            @Override
+            public void preDestroy(final T instance) {
+                // do nothing
+            }
+        } : new InstanceManager<T>() {
+
+            final AnnotatedType<T> annotatedType = beanManager.createAnnotatedType(clazz);
+            final InjectionTargetFactory<T> injectionTargetFactory = beanManager.getInjectionTargetFactory(annotatedType);
+            final InjectionTarget<T> injectionTarget = injectionTargetFactory.createInjectionTarget(null);
+
+            @Override
+            public T getInstance(final Class<T> clazz) {
+                final CreationalContext<T> creationalContext = beanManager.createCreationalContext(null);
+                final T instance = injectionTarget.produce(creationalContext);
+                injectionTarget.inject(instance, creationalContext);
+                if (injectionManager != null) {
+                    injectionManager.inject(instance, CdiComponentProvider.CDI_CLASS_ANALYZER);
+                }
+                injectionTarget.postConstruct(instance);
+                return instance;
+            }
+
+            @Override
+            public void preDestroy(final T instance) {
+                injectionTarget.preDestroy(instance);
+            }
+        };
+    }
+
+    @SuppressWarnings(value = "unchecked")
+    /* package */ T _provide() {
+        final T instance = referenceProvider.getInstance(clazz);
+        if (instance != null) {
+            return instance;
+        }
+        throw new NoSuchElementException(LocalizationMessages.CDI_LOOKUP_FAILED(clazz));
+    }
+
+    @Override
+    public void dispose(final T instance) {
+        referenceProvider.preDestroy(instance);
+    }
+
+    private interface InstanceManager<T> {
+
+        /**
+         * Get me correctly instantiated and injected instance.
+         *
+         * @param clazz type of the component to instantiate.
+         * @return injected component instance.
+         */
+        T getInstance(Class<T> clazz);
+
+        /**
+         * Do whatever needs to be done before given instance is destroyed.
+         *
+         * @param instance to be destroyed.
+         */
+        void preDestroy(T instance);
+    }
+}
diff --git a/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/CdiComponentProvider.java b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/CdiComponentProvider.java
new file mode 100644
index 0000000..80b2126
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/CdiComponentProvider.java
@@ -0,0 +1,949 @@
+/*
+ * 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.ext.cdi1x.internal;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.Context;
+
+import javax.annotation.ManagedBean;
+import javax.annotation.Priority;
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.context.Dependent;
+import javax.enterprise.context.RequestScoped;
+import javax.enterprise.context.spi.CreationalContext;
+import javax.enterprise.event.Observes;
+import javax.enterprise.inject.Default;
+import javax.enterprise.inject.spi.AfterBeanDiscovery;
+import javax.enterprise.inject.spi.AfterTypeDiscovery;
+import javax.enterprise.inject.spi.Annotated;
+import javax.enterprise.inject.spi.AnnotatedCallable;
+import javax.enterprise.inject.spi.AnnotatedConstructor;
+import javax.enterprise.inject.spi.AnnotatedParameter;
+import javax.enterprise.inject.spi.AnnotatedType;
+import javax.enterprise.inject.spi.Bean;
+import javax.enterprise.inject.spi.BeanManager;
+import javax.enterprise.inject.spi.BeforeBeanDiscovery;
+import javax.enterprise.inject.spi.Extension;
+import javax.enterprise.inject.spi.InjectionPoint;
+import javax.enterprise.inject.spi.InjectionTarget;
+import javax.enterprise.inject.spi.ProcessAnnotatedType;
+import javax.enterprise.inject.spi.ProcessInjectionTarget;
+import javax.enterprise.util.AnnotationLiteral;
+import javax.inject.Qualifier;
+
+import org.glassfish.jersey.ext.cdi1x.internal.spi.InjectionManagerInjectedTarget;
+import org.glassfish.jersey.ext.cdi1x.internal.spi.InjectionManagerStore;
+import org.glassfish.jersey.ext.cdi1x.internal.spi.InjectionTargetListener;
+import org.glassfish.jersey.ext.cdi1x.spi.Hk2CustomBoundTypesProvider;
+import org.glassfish.jersey.internal.inject.AbstractBinder;
+import org.glassfish.jersey.internal.inject.Binder;
+import org.glassfish.jersey.internal.inject.Bindings;
+import org.glassfish.jersey.internal.inject.ForeignRequestScopeBridge;
+import org.glassfish.jersey.internal.inject.InjectionManager;
+import org.glassfish.jersey.internal.inject.InstanceBinding;
+import org.glassfish.jersey.internal.inject.Providers;
+import org.glassfish.jersey.internal.inject.SupplierInstanceBinding;
+import org.glassfish.jersey.internal.util.collection.Cache;
+import org.glassfish.jersey.server.ContainerRequest;
+import org.glassfish.jersey.server.model.Parameter;
+import org.glassfish.jersey.server.model.Resource;
+import org.glassfish.jersey.server.spi.ComponentProvider;
+import org.glassfish.jersey.server.spi.internal.ValueParamProvider;
+
+import org.glassfish.hk2.api.ClassAnalyzer;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Jersey CDI integration implementation.
+ * Implements {@link ComponentProvider Jersey component provider} to serve CDI beans
+ * obtained from the actual CDI bean manager.
+ * To properly inject JAX-RS/Jersey managed beans into CDI, it also
+ * serves as a {@link Extension CDI Extension}, that intercepts CDI injection targets.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@Priority(200)
+public class CdiComponentProvider implements ComponentProvider, Extension {
+
+    private static final Logger LOGGER = Logger.getLogger(CdiComponentProvider.class.getName());
+
+    /**
+     * annotation types that distinguish the classes to be added to {@link #jaxrsInjectableTypes}
+     */
+    private static final Set<Class<? extends Annotation>> JAX_RS_INJECT_ANNOTATIONS =
+            new HashSet<Class<? extends Annotation>>() {{
+                addAll(JaxRsParamProducer.JAX_RS_STRING_PARAM_ANNOTATIONS);
+                add(Context.class);
+            }};
+
+    /**
+     * Name to be used when binding CDI injectee skipping class analyzer to HK2 service injection manager.
+     */
+    public static final String CDI_CLASS_ANALYZER = "CdiInjecteeSkippingClassAnalyzer";
+
+    /**
+     * set of non JAX-RS components containing JAX-RS injection points
+     */
+    private final Set<Type> jaxrsInjectableTypes = new HashSet<>();
+    private final Set<Type> hk2ProvidedTypes = Collections.synchronizedSet(new HashSet<Type>());
+    private final Set<Type> jerseyVetoedTypes = Collections.synchronizedSet(new HashSet<Type>());
+
+    /**
+     * set of request scoped components
+     */
+    private final Set<Class<?>> requestScopedComponents = new HashSet<>();
+
+
+    private final Cache<Class<?>, Boolean> jaxRsComponentCache = new Cache<>(new Function<Class<?>, Boolean>() {
+        @Override
+        public Boolean apply(final Class<?> clazz) {
+            return Application.class.isAssignableFrom(clazz)
+                    || Providers.isJaxRsProvider(clazz)
+                    || jaxRsResourceCache.apply(clazz);
+        }
+    });
+
+    private final Cache<Class<?>, Boolean> jaxRsResourceCache = new Cache<>(clazz -> Resource.from(clazz) != null);
+
+    private final Hk2CustomBoundTypesProvider customHk2TypesProvider;
+    private final InjectionManagerStore injectionManagerStore;
+
+    private volatile InjectionManager injectionManager;
+    private volatile javax.enterprise.inject.spi.BeanManager beanManager;
+
+    private volatile Map<Class<?>, Set<Method>> methodsToSkip = new HashMap<>();
+    private volatile Map<Class<?>, Set<Field>> fieldsToSkip = new HashMap<>();
+
+    public CdiComponentProvider() {
+        customHk2TypesProvider = CdiUtil.lookupService(Hk2CustomBoundTypesProvider.class);
+        injectionManagerStore = CdiUtil.createHk2InjectionManagerStore();
+    }
+
+    @Override
+    public void initialize(final InjectionManager injectionManager) {
+        this.injectionManager = injectionManager;
+        this.beanManager = CdiUtil.getBeanManager();
+
+        if (beanManager != null) {
+            // Try to get CdiComponentProvider created by CDI.
+            final CdiComponentProvider extension = beanManager.getExtension(CdiComponentProvider.class);
+
+            if (extension != null) {
+                extension.addInjectionManager(this.injectionManager);
+
+                this.fieldsToSkip = extension.getFieldsToSkip();
+                this.methodsToSkip = extension.getMethodsToSkip();
+
+                bindHk2ClassAnalyzer();
+
+                LOGGER.config(LocalizationMessages.CDI_PROVIDER_INITIALIZED());
+            }
+        }
+    }
+
+    /**
+     * CDI producer for CDI bean constructor String parameters, that should be injected by JAX-RS.
+     */
+    @ApplicationScoped
+    public static class JaxRsParamProducer {
+
+        @Qualifier
+        @Retention(RUNTIME)
+        @Target({METHOD, FIELD, PARAMETER, TYPE})
+        public static @interface JaxRsParamQualifier {
+        }
+
+        private static final JaxRsParamQualifier JaxRsParamQUALIFIER = new JaxRsParamQualifier() {
+
+            @Override
+            public Class<? extends Annotation> annotationType() {
+                return JaxRsParamQualifier.class;
+            }
+        };
+
+        static final Set<Class<? extends Annotation>> JAX_RS_STRING_PARAM_ANNOTATIONS =
+                new HashSet<Class<? extends Annotation>>() {{
+                    add(javax.ws.rs.PathParam.class);
+                    add(javax.ws.rs.QueryParam.class);
+                    add(javax.ws.rs.CookieParam.class);
+                    add(javax.ws.rs.HeaderParam.class);
+                    add(javax.ws.rs.MatrixParam.class);
+                    add(javax.ws.rs.FormParam.class);
+                }};
+
+        /**
+         * Internal cache to store CDI {@link InjectionPoint} to Jersey {@link Parameter} mapping.
+         */
+        final Cache<InjectionPoint, Parameter> parameterCache = new Cache<>(injectionPoint -> {
+            final Annotated annotated = injectionPoint.getAnnotated();
+            final Class<?> clazz = injectionPoint.getMember().getDeclaringClass();
+
+            if (annotated instanceof AnnotatedParameter) {
+
+                final AnnotatedParameter annotatedParameter = (AnnotatedParameter) annotated;
+                final AnnotatedCallable callable = annotatedParameter.getDeclaringCallable();
+
+                if (callable instanceof AnnotatedConstructor) {
+
+                    final AnnotatedConstructor ac = (AnnotatedConstructor) callable;
+                    final int position = annotatedParameter.getPosition();
+                    final List<Parameter> parameters = Parameter.create(clazz, clazz, ac.getJavaMember(), false);
+
+                    return parameters.get(position);
+                }
+            }
+
+            return null;
+        });
+
+        /**
+         * Provide a value for given injection point. If the injection point does not refer
+         * to a CDI bean constructor parameter, or the value could not be found, the method will return null.
+         *
+         * @param injectionPoint actual injection point.
+         * @param beanManager    current application bean manager.
+         * @return concrete JAX-RS parameter value for given injection point.
+         */
+        @javax.enterprise.inject.Produces
+        @JaxRsParamQualifier
+        public String getParameterValue(final InjectionPoint injectionPoint, final BeanManager beanManager) {
+            final Parameter parameter = parameterCache.apply(injectionPoint);
+
+            if (parameter != null) {
+                InjectionManager injectionManager =
+                        beanManager.getExtension(CdiComponentProvider.class).getEffectiveInjectionManager();
+
+                Set<ValueParamProvider> providers = Providers.getProviders(injectionManager, ValueParamProvider.class);
+                ContainerRequest containerRequest = injectionManager.getInstance(ContainerRequest.class);
+                for (ValueParamProvider vfp : providers) {
+                    Function<ContainerRequest, ?> paramValueSupplier = vfp.getValueProvider(parameter);
+                    if (paramValueSupplier != null) {
+                        return (String) paramValueSupplier.apply(containerRequest);
+                    }
+                }
+            }
+
+            return null;
+        }
+    }
+
+    @Override
+    public boolean bind(final Class<?> clazz, final Set<Class<?>> providerContracts) {
+        if (LOGGER.isLoggable(Level.FINE)) {
+            LOGGER.fine(LocalizationMessages.CDI_CLASS_BEING_CHECKED(clazz));
+        }
+
+        if (beanManager == null) {
+            return false;
+        }
+
+        if (isJerseyOrDependencyType(clazz)) {
+            return false;
+        }
+
+        final boolean isCdiManaged = isCdiComponent(clazz);
+        final boolean isManagedBean = isManagedBean(clazz);
+        final boolean isJaxRsComponent = isJaxRsComponentType(clazz);
+
+        if (!isCdiManaged && !isManagedBean && !isJaxRsComponent) {
+            return false;
+        }
+
+        final boolean isJaxRsResource = jaxRsResourceCache.apply(clazz);
+
+        final Class<? extends Annotation> beanScopeAnnotation = CdiUtil.getBeanScope(clazz, beanManager);
+        final boolean isRequestScoped = beanScopeAnnotation == RequestScoped.class
+                                        || (beanScopeAnnotation == Dependent.class && isJaxRsResource);
+
+        Supplier<AbstractCdiBeanSupplier> beanFactory = isRequestScoped
+                ? new RequestScopedCdiBeanSupplier(clazz, injectionManager, beanManager, isCdiManaged)
+                : new GenericCdiBeanSupplier(clazz, injectionManager, beanManager, isCdiManaged);
+
+        SupplierInstanceBinding<AbstractCdiBeanSupplier> builder = Bindings.supplier(beanFactory).to(clazz);
+        for (final Class contract : providerContracts) {
+            builder.to(contract);
+        }
+        injectionManager.register(builder);
+
+        if (isRequestScoped) {
+            requestScopedComponents.add(clazz);
+        }
+
+        if (LOGGER.isLoggable(Level.CONFIG)) {
+            LOGGER.config(LocalizationMessages.CDI_CLASS_BOUND_WITH_CDI(clazz));
+        }
+
+        return true;
+    }
+
+    @Override
+    public void done() {
+        if (requestScopedComponents.size() > 0) {
+            InstanceBinding<ForeignRequestScopeBridge> descriptor = Bindings
+                    .service((ForeignRequestScopeBridge) () -> requestScopedComponents)
+                    .to(ForeignRequestScopeBridge.class);
+
+            injectionManager.register(descriptor);
+
+            if (LOGGER.isLoggable(Level.CONFIG)) {
+                LOGGER.config(LocalizationMessages.CDI_REQUEST_SCOPED_COMPONENTS_RECOGNIZED(
+                        listElements(new StringBuilder().append("\n"), requestScopedComponents).toString()));
+            }
+        }
+    }
+
+    private boolean isCdiComponent(final Class<?> component) {
+        final Annotation[] qualifiers = CdiUtil.getQualifiers(component.getAnnotations());
+        return !beanManager.getBeans(component, qualifiers).isEmpty();
+    }
+
+    private boolean isManagedBean(final Class<?> component) {
+        return component.isAnnotationPresent(ManagedBean.class);
+    }
+
+    private static AnnotatedConstructor<?> enrichedConstructor(final AnnotatedConstructor<?> ctor) {
+        return new AnnotatedConstructor(){
+
+            @Override
+            public Constructor getJavaMember() {
+                return ctor.getJavaMember();
+            }
+
+            @Override
+            public List<AnnotatedParameter> getParameters() {
+                final List<AnnotatedParameter> parameters = new ArrayList<>(ctor.getParameters().size());
+
+                for (final AnnotatedParameter<?> ap : ctor.getParameters()) {
+                    parameters.add(new AnnotatedParameter() {
+
+                        @Override
+                        public int getPosition() {
+                            return ap.getPosition();
+                        }
+
+                        @Override
+                        public AnnotatedCallable getDeclaringCallable() {
+                            return ap.getDeclaringCallable();
+                        }
+
+                        @Override
+                        public Type getBaseType() {
+                            return ap.getBaseType();
+                        }
+
+                        @Override
+                        public Set<Type> getTypeClosure() {
+                            return ap.getTypeClosure();
+                        }
+
+                        @Override
+                        public <T extends Annotation> T getAnnotation(final Class<T> annotationType) {
+                            if (annotationType == JaxRsParamProducer.JaxRsParamQualifier.class) {
+                                return hasAnnotation(ap, JaxRsParamProducer.JAX_RS_STRING_PARAM_ANNOTATIONS)
+                                        ? (T) JaxRsParamProducer.JaxRsParamQUALIFIER : null;
+                            } else {
+                                return ap.getAnnotation(annotationType);
+                            }
+                        }
+
+                        @Override
+                        public Set<Annotation> getAnnotations() {
+                            final Set<Annotation> result = new HashSet<>();
+                            for (final Annotation a : ap.getAnnotations()) {
+                                result.add(a);
+                                final Class<? extends Annotation> annotationType = a.annotationType();
+                                if (JaxRsParamProducer.JAX_RS_STRING_PARAM_ANNOTATIONS.contains(annotationType)) {
+                                    result.add(JaxRsParamProducer.JaxRsParamQUALIFIER);
+                                }
+                            }
+                            return result;
+                        }
+
+                        @Override
+                        public boolean isAnnotationPresent(final Class<? extends Annotation> annotationType) {
+                            return (annotationType == JaxRsParamProducer.JaxRsParamQualifier.class
+                                            && hasAnnotation(ap, JaxRsParamProducer.JAX_RS_STRING_PARAM_ANNOTATIONS))
+                                    || ap.isAnnotationPresent(annotationType);
+                        }
+                    });
+                }
+                return parameters;
+            }
+
+            @Override
+            public boolean isStatic() {
+                return ctor.isStatic();
+            }
+
+            @Override
+            public AnnotatedType getDeclaringType() {
+                return ctor.getDeclaringType();
+            }
+
+            @Override
+            public Type getBaseType() {
+                return ctor.getBaseType();
+            }
+
+            @Override
+            public Set<Type> getTypeClosure() {
+                return ctor.getTypeClosure();
+            }
+
+            @Override
+            public <T extends Annotation> T getAnnotation(final Class<T> annotationType) {
+                return ctor.getAnnotation(annotationType);
+            }
+
+            @Override
+            public Set<Annotation> getAnnotations() {
+                return ctor.getAnnotations();
+            }
+
+            @Override
+            public boolean isAnnotationPresent(final Class<? extends Annotation> annotationType) {
+                return ctor.isAnnotationPresent(annotationType);
+            }
+        };
+    }
+
+    @SuppressWarnings("unused")
+    private void processAnnotatedType(@Observes
+    // We can not apply the following constraint
+    // if we want to fully support {@link org.glassfish.jersey.ext.cdi1x.spi.Hk2CustomBoundTypesProvider}.
+    // Covered by tests/integration/cdi-with-jersey-injection-custom-cfg-webapp test application:
+//                                      @WithAnnotations({
+//                                              Context.class,
+//                                              ApplicationPath.class,
+//                                              HeaderParam.class,
+//                                              QueryParam.class,
+//                                              FormParam.class,
+//                                              MatrixParam.class,
+//                                              BeanParam.class,
+//                                              PathParam.class})
+                                      final ProcessAnnotatedType processAnnotatedType) {
+        final AnnotatedType<?> annotatedType = processAnnotatedType.getAnnotatedType();
+
+        // if one of the JAX-RS annotations is present in the currently seen class, add it to the "whitelist"
+        if (containsJaxRsConstructorInjection(annotatedType)
+                || containsJaxRsFieldInjection(annotatedType)
+                || containsJaxRsMethodInjection(annotatedType)) {
+            jaxrsInjectableTypes.add(annotatedType.getBaseType());
+        }
+
+        if (customHk2TypesProvider != null) {
+            final Type baseType = annotatedType.getBaseType();
+            if (customHk2TypesProvider.getHk2Types().contains(baseType)) {
+                processAnnotatedType.veto();
+                jerseyVetoedTypes.add(baseType);
+            }
+        }
+
+        if (containsJaxRsParameterizedCtor(annotatedType)) {
+            processAnnotatedType.setAnnotatedType(new AnnotatedType() {
+
+                @Override
+                public Class getJavaClass() {
+                    return annotatedType.getJavaClass();
+                }
+
+                @Override
+                public Set<AnnotatedConstructor> getConstructors() {
+                    final Set<AnnotatedConstructor> result = new HashSet<>();
+                    for (final AnnotatedConstructor c : annotatedType.getConstructors()) {
+                        result.add(enrichedConstructor(c));
+                    }
+                    return result;
+                }
+
+                @Override
+                public Set getMethods() {
+                    return annotatedType.getMethods();
+                }
+
+                @Override
+                public Set getFields() {
+                    return annotatedType.getFields();
+                }
+
+                @Override
+                public Type getBaseType() {
+                    return annotatedType.getBaseType();
+                }
+
+                @Override
+                public Set<Type> getTypeClosure() {
+                    return annotatedType.getTypeClosure();
+                }
+
+                @Override
+                public <T extends Annotation> T getAnnotation(final Class<T> annotationType) {
+                    return annotatedType.getAnnotation(annotationType);
+                }
+
+                @Override
+                public Set<Annotation> getAnnotations() {
+                    return annotatedType.getAnnotations();
+                }
+
+                @Override
+                public boolean isAnnotationPresent(final Class<? extends Annotation> annotationType) {
+                    return annotatedType.isAnnotationPresent(annotationType);
+                }
+            });
+        }
+    }
+
+    private boolean containsJaxRsParameterizedCtor(final AnnotatedType annotatedType) {
+        return containAnnotatedParameters(annotatedType.getConstructors(), JaxRsParamProducer.JAX_RS_STRING_PARAM_ANNOTATIONS);
+    }
+
+    private boolean containsJaxRsConstructorInjection(final AnnotatedType annotatedType) {
+        return containAnnotatedParameters(annotatedType.getConstructors(), JAX_RS_INJECT_ANNOTATIONS);
+    }
+
+    private boolean containsJaxRsMethodInjection(final AnnotatedType annotatedType) {
+        return containAnnotatedParameters(annotatedType.getMethods(), JAX_RS_INJECT_ANNOTATIONS);
+    }
+
+    private boolean containsJaxRsFieldInjection(final AnnotatedType annotatedType) {
+        return containAnnotation(annotatedType.getFields(), JAX_RS_INJECT_ANNOTATIONS);
+    }
+
+    private boolean containAnnotatedParameters(final Collection<AnnotatedCallable> annotatedCallables,
+                                               final Set<Class<? extends Annotation>> annotationSet) {
+        for (final AnnotatedCallable c : annotatedCallables) {
+            if (containAnnotation(c.getParameters(), annotationSet)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean containAnnotation(final Collection<Annotated> elements,
+                                      final Set<Class<? extends Annotation>> annotationSet) {
+        for (final Annotated element : elements) {
+            if (hasAnnotation(element, annotationSet)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static boolean hasAnnotation(final Annotated element, final Set<Class<? extends Annotation>> annotations) {
+        for (final Class<? extends Annotation> a : annotations) {
+            if (element.isAnnotationPresent(a)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @SuppressWarnings("unused")
+    private void afterTypeDiscovery(@Observes final AfterTypeDiscovery afterTypeDiscovery) {
+        if (LOGGER.isLoggable(Level.CONFIG) && !jerseyVetoedTypes.isEmpty()) {
+            LOGGER.config(LocalizationMessages.CDI_TYPE_VETOED(customHk2TypesProvider,
+                    listElements(new StringBuilder().append("\n"), jerseyVetoedTypes).toString()));
+        }
+    }
+
+    @SuppressWarnings("unused")
+    private void beforeBeanDiscovery(@Observes final BeforeBeanDiscovery beforeBeanDiscovery,
+            final javax.enterprise.inject.spi.BeanManager beanManager) {
+        beforeBeanDiscovery.addAnnotatedType(beanManager.createAnnotatedType(JaxRsParamProducer.class));
+    }
+
+    @SuppressWarnings("unused")
+    private void processInjectionTarget(@Observes final ProcessInjectionTarget event) {
+        final InjectionTarget it = event.getInjectionTarget();
+        final Class<?> componentClass = event.getAnnotatedType().getJavaClass();
+
+        final Set<InjectionPoint> cdiInjectionPoints = filterHk2InjectionPointsOut(it.getInjectionPoints());
+
+        for (final InjectionPoint injectionPoint : cdiInjectionPoints) {
+            final Member member = injectionPoint.getMember();
+            if (member instanceof Field) {
+                addInjecteeToSkip(componentClass, fieldsToSkip, (Field) member);
+            } else if (member instanceof Method) {
+                addInjecteeToSkip(componentClass, methodsToSkip, (Method) member);
+            }
+        }
+
+        InjectionManagerInjectedCdiTarget target = null;
+        if (isJerseyOrDependencyType(componentClass)) {
+            target = new InjectionManagerInjectedCdiTarget(it) {
+
+                @Override
+                public Set<InjectionPoint> getInjectionPoints() {
+                    // Tell CDI to ignore Jersey (or it's dependencies) classes when injecting.
+                    // CDI will not treat these classes as CDI beans (as they are not).
+                    return Collections.emptySet();
+                }
+            };
+        } else if (isJaxRsComponentType(componentClass)
+                || jaxrsInjectableTypes.contains(event.getAnnotatedType().getBaseType())) {
+            target = new InjectionManagerInjectedCdiTarget(it) {
+
+                @Override
+                public Set<InjectionPoint> getInjectionPoints() {
+                    // Inject CDI beans into JAX-RS resources/providers/application.
+                    return cdiInjectionPoints;
+                }
+            };
+        }
+
+        if (target != null) {
+            notify(target);
+            //noinspection unchecked
+            event.setInjectionTarget(target);
+        }
+    }
+
+    private Set<InjectionPoint> filterHk2InjectionPointsOut(final Set<InjectionPoint> originalInjectionPoints) {
+        final Set<InjectionPoint> filteredInjectionPoints = new HashSet<>();
+        for (final InjectionPoint ip : originalInjectionPoints) {
+            final Type injectedType = ip.getType();
+            if (customHk2TypesProvider != null && customHk2TypesProvider.getHk2Types().contains(injectedType)) {
+                //remember the type, we would need to mock it's CDI binding at runtime
+                hk2ProvidedTypes.add(injectedType);
+            } else {
+                if (injectedType instanceof Class<?>) {
+                    final Class<?> injectedClass = (Class<?>) injectedType;
+                    if (isJerseyOrDependencyType(injectedClass)) {
+                        //remember the type, we would need to mock it's CDI binding at runtime
+                        hk2ProvidedTypes.add(injectedType);
+                    } else {
+                        filteredInjectionPoints.add(ip);
+                    }
+                } else { // it is not a class, maybe provider type?:
+                    if (isInjectionProvider(injectedType)
+                            && (isProviderOfJerseyType((ParameterizedType) injectedType))) {
+                        //remember the type, we would need to mock it's CDI binding at runtime
+                        hk2ProvidedTypes.add(((ParameterizedType) injectedType).getActualTypeArguments()[0]);
+                    } else {
+                        filteredInjectionPoints.add(ip);
+                    }
+                }
+            }
+        }
+        return filteredInjectionPoints;
+    }
+
+    private boolean isInjectionProvider(final Type injectedType) {
+        return injectedType instanceof ParameterizedType
+                && ((ParameterizedType) injectedType).getRawType() == javax.inject.Provider.class;
+    }
+
+    private boolean isProviderOfJerseyType(final ParameterizedType provider) {
+        final Type firstArgumentType = provider.getActualTypeArguments()[0];
+        if (firstArgumentType instanceof Class && isJerseyOrDependencyType((Class<?>) firstArgumentType)) {
+            return true;
+        }
+        return (customHk2TypesProvider != null && customHk2TypesProvider.getHk2Types().contains(firstArgumentType));
+    }
+
+    private <T> void addInjecteeToSkip(final Class<?> componentClass, final Map<Class<?>, Set<T>> toSkip, final T member) {
+        if (!toSkip.containsKey(componentClass)) {
+            toSkip.put(componentClass, new HashSet<T>());
+        }
+        toSkip.get(componentClass).add(member);
+    }
+
+    /**
+     * Auxiliary annotation for mocked beans used to cover Jersey/HK2 injected injection points.
+     */
+    @SuppressWarnings("serial")
+    public static class CdiDefaultAnnotation extends AnnotationLiteral<Default> implements Default {
+
+        private static final long serialVersionUID = 1L;
+    }
+
+    @SuppressWarnings({"unused", "unchecked", "rawtypes"})
+    private void afterDiscoveryObserver(@Observes final AfterBeanDiscovery abd) {
+        if (customHk2TypesProvider != null) {
+            hk2ProvidedTypes.addAll(customHk2TypesProvider.getHk2Types());
+        }
+
+        for (final Type t : hk2ProvidedTypes) {
+            abd.addBean(new Hk2Bean(t));
+        }
+    }
+
+    /**
+     * Gets you fields to skip from a proxied instance.
+     * <p/>
+     * Note: Do NOT lower the visibility of this method. CDI proxies need at least this visibility.
+     *
+     * @return fields to skip when injecting via HK2
+     */
+    /* package */ Map<Class<?>, Set<Field>> getFieldsToSkip() {
+        return fieldsToSkip;
+    }
+
+    /**
+     * Gets you methods to skip (from a proxied instance).
+     * <p/>
+     * Note: Do NOT lower the visibility of this method. CDI proxies need at least this visibility.
+     *
+     * @return methods to skip when injecting via HK2
+     */
+    /* package */ Map<Class<?>, Set<Method>> getMethodsToSkip() {
+        return methodsToSkip;
+    }
+
+    /**
+     * Gets you effective injection manager.
+     * <p/>
+     * Note: Do NOT lower the visibility of this method. CDI proxies need at least this visibility.
+     *
+     * @return HK2 injection manager.
+     */
+    /* package */ InjectionManager getEffectiveInjectionManager() {
+        return injectionManagerStore.getEffectiveInjectionManager();
+    }
+
+    /**
+     * Add HK2 {@link InjectionManager injection manager} (to a proxied instance).
+     * <p/>
+     * Note: Do NOT lower the visibility of this method. CDI proxies need at least this visibility.
+     *
+     * @param injectionManager injection manager.
+     */
+    /* package */ void addInjectionManager(final InjectionManager injectionManager) {
+        injectionManagerStore.registerInjectionManager(injectionManager);
+    }
+
+    /**
+     * Notifies the {@code InjectionTargetListener injection target listener} about new
+     * {@link InjectionManagerInjectedTarget injected target}.
+     * <p/>
+     * Note: Do NOT lower the visibility of this method. CDI proxies need at least this visibility.
+     *
+     * @param target new injected target.
+     */
+    /* package */ void notify(final InjectionManagerInjectedTarget target) {
+        if (injectionManagerStore instanceof InjectionTargetListener) {
+            ((InjectionTargetListener) injectionManagerStore).notify(target);
+        }
+    }
+
+    /**
+     * Introspect given type to determine if it represents a JAX-RS component.
+     *
+     * @param clazz type to be introspected.
+     * @return true if the type represents a JAX-RS component type.
+     */
+    /* package */ boolean isJaxRsComponentType(final Class<?> clazz) {
+        return jaxRsComponentCache.apply(clazz);
+    }
+
+    private static boolean isJerseyOrDependencyType(final Class<?> clazz) {
+        if (clazz.isPrimitive() || clazz.isSynthetic()) {
+            return false;
+        }
+
+        final Package pkg = clazz.getPackage();
+        if (pkg == null) { // Class.getPackage() could return null
+            LOGGER.warning(String.format("Class %s has null package", clazz));
+            return false;
+        }
+
+        final String pkgName = pkg.getName();
+        return !clazz.isAnnotationPresent(JerseyVetoed.class)
+                && (pkgName.contains("org.glassfish.hk2")
+                            || pkgName.contains("jersey.repackaged")
+                            || pkgName.contains("org.jvnet.hk2")
+                            || (pkgName.startsWith("org.glassfish.jersey")
+                                        && !pkgName.startsWith("org.glassfish.jersey.examples")
+                                        && !pkgName.startsWith("org.glassfish.jersey.tests"))
+                            || (pkgName.startsWith("com.sun.jersey")
+                                        && !pkgName.startsWith("com.sun.jersey.examples")
+                                        && !pkgName.startsWith("com.sun.jersey.tests")));
+    }
+
+    private void bindHk2ClassAnalyzer() {
+        ClassAnalyzer defaultClassAnalyzer =
+          injectionManager.getInstance(ClassAnalyzer.class, ClassAnalyzer.DEFAULT_IMPLEMENTATION_NAME);
+
+        int skippedElements = methodsToSkip.size() + fieldsToSkip.size();
+
+        ClassAnalyzer customizedClassAnalyzer = skippedElements > 0
+                ? new InjecteeSkippingAnalyzer(defaultClassAnalyzer, methodsToSkip, fieldsToSkip)
+                : defaultClassAnalyzer;
+
+        Binder binder = new AbstractBinder() {
+            @Override
+            protected void configure() {
+                bind(customizedClassAnalyzer)
+                        .analyzeWith(ClassAnalyzer.DEFAULT_IMPLEMENTATION_NAME)
+                        .to(ClassAnalyzer.class)
+                        .named(CDI_CLASS_ANALYZER);
+            }
+        };
+        injectionManager.register(binder);
+    }
+
+    private StringBuilder listElements(final StringBuilder logMsgBuilder, final Collection<? extends Object> elements) {
+        for (final Object t : elements) {
+            logMsgBuilder.append(String.format(" - %s%n", t));
+        }
+        return logMsgBuilder;
+    }
+
+    @SuppressWarnings("unchecked")
+    private abstract class InjectionManagerInjectedCdiTarget implements InjectionManagerInjectedTarget {
+
+        private final InjectionTarget delegate;
+        private volatile InjectionManager effectiveInjectionManager;
+
+        public InjectionManagerInjectedCdiTarget(InjectionTarget delegate) {
+            this.delegate = delegate;
+        }
+
+        @Override
+        public abstract Set<InjectionPoint> getInjectionPoints();
+
+        @Override
+        public void inject(final Object t, final CreationalContext cc) {
+            delegate.inject(t, cc);
+
+            InjectionManager injectingManager = getEffectiveInjectionManager();
+            if (injectingManager == null) {
+                injectingManager = effectiveInjectionManager;
+            }
+
+            if (injectingManager != null) {
+                injectingManager.inject(t, CdiComponentProvider.CDI_CLASS_ANALYZER);
+            }
+        }
+
+        @Override
+        public void postConstruct(final Object t) {
+            delegate.postConstruct(t);
+        }
+
+        @Override
+        public void preDestroy(final Object t) {
+            delegate.preDestroy(t);
+        }
+
+        @Override
+        public Object produce(final CreationalContext cc) {
+            return delegate.produce(cc);
+        }
+
+        @Override
+        public void dispose(final Object t) {
+            delegate.dispose(t);
+        }
+
+        @Override
+        public void setInjectionManager(final InjectionManager injectionManager) {
+            this.effectiveInjectionManager = injectionManager;
+        }
+    }
+
+    private class Hk2Bean implements Bean {
+
+        private final Type t;
+
+        public Hk2Bean(final Type t) {
+            this.t = t;
+        }
+
+        @Override
+        public Class getBeanClass() {
+            return (Class) t;
+        }
+
+        @Override
+        public Set getInjectionPoints() {
+            return Collections.emptySet();
+        }
+
+        @Override
+        public boolean isNullable() {
+            return true;
+        }
+
+        @Override
+        public Object create(final CreationalContext creationalContext) {
+            return getEffectiveInjectionManager().getInstance(t);
+        }
+
+        @Override
+        public void destroy(final Object instance, final CreationalContext creationalContext) {
+        }
+
+        @Override
+        public Set getTypes() {
+            return Collections.singleton(t);
+        }
+
+        @Override
+        public Set getQualifiers() {
+            return Collections.singleton(new CdiDefaultAnnotation());
+        }
+
+        @Override
+        public Class getScope() {
+            return Dependent.class;
+        }
+
+        @Override
+        public String getName() {
+            return t.toString();
+        }
+
+        @Override
+        public Set getStereotypes() {
+            return Collections.emptySet();
+        }
+
+        @Override
+        public boolean isAlternative() {
+            return false;
+        }
+    }
+}
diff --git a/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/CdiUtil.java b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/CdiUtil.java
new file mode 100644
index 0000000..7f13ba6
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/CdiUtil.java
@@ -0,0 +1,149 @@
+/*
+ * 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.ext.cdi1x.internal;
+
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import javax.enterprise.context.spi.CreationalContext;
+import javax.enterprise.inject.spi.Bean;
+import javax.enterprise.inject.spi.BeanManager;
+import javax.inject.Qualifier;
+
+import org.glassfish.jersey.ext.cdi1x.internal.spi.BeanManagerProvider;
+import org.glassfish.jersey.ext.cdi1x.internal.spi.InjectionManagerStore;
+import org.glassfish.jersey.internal.ServiceFinder;
+import org.glassfish.jersey.model.internal.RankedComparator;
+import org.glassfish.jersey.model.internal.RankedProvider;
+
+/**
+ * Common CDI utility methods.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ * @author Michal Gajdos
+ */
+public final class CdiUtil {
+
+    private static final BeanManagerProvider BEAN_MANAGER_PROVIDER = new DefaultBeanManagerProvider();
+
+    /**
+     * Prevent instantiation.
+     */
+    private CdiUtil() {
+        throw new AssertionError("No instances allowed.");
+    }
+
+    /**
+     * Get me list of qualifiers included in given annotation list.
+     *
+     * @param annotations list of annotations to introspect
+     * @return annotations from the input list that are marked as qualifiers
+     */
+    public static Annotation[] getQualifiers(final Annotation[] annotations) {
+        final List<Annotation> result = new ArrayList<>(annotations.length);
+        for (final Annotation a : annotations) {
+            if (a.annotationType().isAnnotationPresent(Qualifier.class)) {
+                result.add(a);
+            }
+        }
+        return result.toArray(new Annotation[result.size()]);
+    }
+
+    /**
+     * Get me current bean manager. Method first tries to lookup available providers via {@code META-INF/services}. If not found
+     * the bean manager is returned from the default provider.
+     *
+     * @return bean manager
+     */
+    public static BeanManager getBeanManager() {
+        final BeanManagerProvider provider = lookupService(BeanManagerProvider.class);
+        if (provider != null) {
+            return provider.getBeanManager();
+        }
+
+        return BEAN_MANAGER_PROVIDER.getBeanManager();
+    }
+
+    /**
+     * Create new instance of {@link InjectionManagerStore}. Method first tries to lookup
+     * available manager via {@code META-INF/services} and if not found a new instance of default one is returned.
+     *
+     * @return an instance of injection manager store.
+     */
+    static InjectionManagerStore createHk2InjectionManagerStore() {
+        final InjectionManagerStore manager = lookupService(InjectionManagerStore.class);
+        return manager != null ? manager : new SingleInjectionManagerStore();
+    }
+
+    /**
+     * Look for a service of given type. If more then one service is found the method sorts them are returns the one with highest
+     * priority.
+     *
+     * @param clazz type of service to look for.
+     * @param <T>   type of service to look for
+     * @return instance of service with highest priority or {@code null} if service of given type cannot be found.
+     * @see javax.annotation.Priority
+     */
+    static <T> T lookupService(final Class<T> clazz) {
+        final List<RankedProvider<T>> providers = new LinkedList<>();
+
+        for (final T provider : ServiceFinder.find(clazz)) {
+            providers.add(new RankedProvider<>(provider));
+        }
+        Collections.sort(providers, new RankedComparator<T>(RankedComparator.Order.ASCENDING));
+
+        return providers.isEmpty() ? null : providers.get(0).getProvider();
+    }
+
+    /**
+     * Obtain a bean reference of given type from the bean manager.
+     *
+     * @param clazz         type of the bean to get reference to.
+     * @param bean          the {@link Bean} object representing the managed bean.
+     * @param beanManager   bean manager used to obtain an instance of the requested bean.
+     * @param <T>           type of the bean to be returned.
+     * @return a bean reference or {@code null} if a bean instance cannot be found.
+     */
+    static <T> T getBeanReference(final Class<T> clazz, final Bean bean, final BeanManager beanManager) {
+        final CreationalContext<?> creationalContext = beanManager.createCreationalContext(bean);
+        final Object result = beanManager.getReference(bean, clazz, creationalContext);
+
+        return clazz.cast(result);
+    }
+
+    /**
+     * Get me scope of a bean corresponding to given class.
+     *
+     * @param beanClass bean class in question.
+     * @param beanManager actual bean manager.
+     * @return actual bean scope or null, if the scope could not be determined.
+     */
+    public static Class<? extends Annotation> getBeanScope(final Class<?> beanClass, final BeanManager beanManager) {
+        final Set<Bean<?>> beans = beanManager.getBeans(beanClass);
+        if (beans.isEmpty()) {
+            return null;
+        }
+        for (Bean b : beans) {
+            return b.getScope();
+        }
+        return null;
+    }
+}
diff --git a/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/DefaultBeanManagerProvider.java b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/DefaultBeanManagerProvider.java
new file mode 100644
index 0000000..7fd8087
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/DefaultBeanManagerProvider.java
@@ -0,0 +1,63 @@
+/*
+ * 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.ext.cdi1x.internal;
+
+import java.util.logging.Logger;
+
+import javax.enterprise.inject.spi.BeanManager;
+import javax.enterprise.inject.spi.CDI;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+
+import org.glassfish.jersey.ext.cdi1x.internal.spi.BeanManagerProvider;
+
+/**
+ * Default implementation of {@link BeanManagerProvider} that works on most environments.
+ * At first the implementation tries to lookup the bean manager in JNDI, then via CDI 1.1 API. If not found {@code null} is
+ * returned.
+ *
+ * @author Michal Gajdos
+ * @since 2.17
+ */
+final class DefaultBeanManagerProvider implements BeanManagerProvider {
+
+    private static final Logger LOGGER = Logger.getLogger(DefaultBeanManagerProvider.class.getName());
+
+    @Override
+    public BeanManager getBeanManager() {
+        InitialContext initialContext = null;
+        try {
+            initialContext = new InitialContext();
+            return (BeanManager) initialContext.lookup("java:comp/BeanManager");
+        } catch (final Exception ex) {
+            try {
+                return CDI.current().getBeanManager();
+            } catch (final Exception e) {
+                LOGGER.config(LocalizationMessages.CDI_BEAN_MANAGER_JNDI_LOOKUP_FAILED());
+                return null;
+            }
+        } finally {
+            if (initialContext != null) {
+                try {
+                    initialContext.close();
+                } catch (final NamingException ignored) {
+                    // no-op
+                }
+            }
+        }
+    }
+}
diff --git a/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/GenericCdiBeanSupplier.java b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/GenericCdiBeanSupplier.java
new file mode 100644
index 0000000..1b3d374
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/GenericCdiBeanSupplier.java
@@ -0,0 +1,44 @@
+/*
+ * 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.ext.cdi1x.internal;
+
+import javax.enterprise.inject.Vetoed;
+import javax.enterprise.inject.spi.BeanManager;
+
+import org.glassfish.jersey.internal.inject.InjectionManager;
+
+/**
+ * Supplier to provide CDI managed components where
+ * there is no clear mapping between the CDI and scopes.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@Vetoed
+public final class GenericCdiBeanSupplier extends AbstractCdiBeanSupplier {
+
+    public GenericCdiBeanSupplier(Class rawType,
+                                    InjectionManager injectionManager,
+                                    BeanManager beanManager,
+                                    boolean cdiManaged) {
+        super(rawType, injectionManager, beanManager, cdiManaged);
+    }
+
+    @Override
+    public Object get() {
+        return _provide();
+    }
+}
diff --git a/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/GenericInjectionManagerStore.java b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/GenericInjectionManagerStore.java
new file mode 100644
index 0000000..3b75a6a
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/GenericInjectionManagerStore.java
@@ -0,0 +1,84 @@
+/*
+ * 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.ext.cdi1x.internal;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.glassfish.jersey.ext.cdi1x.internal.spi.InjectionManagerInjectedTarget;
+import org.glassfish.jersey.ext.cdi1x.internal.spi.InjectionManagerStore;
+import org.glassfish.jersey.ext.cdi1x.internal.spi.InjectionTargetListener;
+import org.glassfish.jersey.internal.inject.InjectionManager;
+
+/**
+ * Generic {@link InjectionManagerStore injection manager store} that allows multiple
+ * injection managers to run in parallel. {@link #lookupInjectionManager()}
+ * method must be implemented that shall be utilized at runtime in the case that more than a single
+ * injection manager has been registered.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ * @since 2.20
+ */
+public abstract class GenericInjectionManagerStore implements InjectionManagerStore, InjectionTargetListener {
+
+    private final List<InjectionManagerInjectedTarget> injectionTargets;
+
+    private volatile InjectionManager injectionManager;
+
+    private volatile boolean multipleInjectionManagers = false;
+
+    public GenericInjectionManagerStore() {
+        injectionTargets = new LinkedList<>();
+    }
+
+    @Override
+    public void registerInjectionManager(final InjectionManager injectionManager) {
+        if (!multipleInjectionManagers) {
+            if (this.injectionManager == null) { // first one
+                this.injectionManager = injectionManager;
+            } else { // second one
+                this.injectionManager = null;
+                multipleInjectionManagers = true;
+            } // first and second case
+        }
+
+        // pass the injection manager to registered injection targets anyway
+        for (final InjectionManagerInjectedTarget target : injectionTargets) {
+            target.setInjectionManager(injectionManager);
+        }
+    }
+
+    @Override
+    public InjectionManager getEffectiveInjectionManager() {
+        return !multipleInjectionManagers ? injectionManager : lookupInjectionManager();
+    }
+
+    /**
+     * CDI container specific method to obtain the actual injection manager
+     * belonging to the Jersey application where the current HTTP requests
+     * is being processed.
+     *
+     * @return actual injection manager.
+     */
+    public abstract InjectionManager lookupInjectionManager();
+
+    @Override
+    public void notify(final InjectionManagerInjectedTarget target) {
+
+        injectionTargets.add(target);
+    }
+}
diff --git a/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/InjecteeSkippingAnalyzer.java b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/InjecteeSkippingAnalyzer.java
new file mode 100644
index 0000000..7df7446
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/InjecteeSkippingAnalyzer.java
@@ -0,0 +1,101 @@
+/*
+ * 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.ext.cdi1x.internal;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import org.glassfish.jersey.internal.util.collection.Views;
+
+import org.glassfish.hk2.api.ClassAnalyzer;
+import org.glassfish.hk2.api.MultiException;
+
+/**
+ * Class analyzer that ignores given injection points.
+ * Used for CDI integration, where we need to avoid HK2 replacing CDI injected entities.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public final class InjecteeSkippingAnalyzer implements ClassAnalyzer {
+
+    private final ClassAnalyzer defaultAnalyzer;
+    private final Map<Class<?>, Set<Method>> methodsToSkip;
+    private final Map<Class<?>, Set<Field>> fieldsToSkip;
+
+    public InjecteeSkippingAnalyzer(ClassAnalyzer defaultAnalyzer,
+                                    Map<Class<?>, Set<Method>> methodsToSkip,
+                                    Map<Class<?>, Set<Field>> fieldsToSkip) {
+        this.defaultAnalyzer = defaultAnalyzer;
+        this.methodsToSkip = methodsToSkip;
+        this.fieldsToSkip = fieldsToSkip;
+    }
+
+    @Override
+    public <T> Constructor<T> getConstructor(Class<T> type) throws MultiException, NoSuchMethodException {
+        throw new IllegalStateException(LocalizationMessages.CDI_CLASS_ANALYZER_MISUSED());
+    }
+
+    @Override
+    public <T> Set<Method> getInitializerMethods(Class<T> type) throws MultiException {
+        final Set<Method> originalMethods = defaultAnalyzer.getInitializerMethods(type);
+        final Set<Method> skippedMethods = getMembersToSkip(type, methodsToSkip);
+        return Views.setDiffView(originalMethods, skippedMethods);
+    }
+
+    @Override
+    public <T> Set<Field> getFields(Class<T> type) throws MultiException {
+        final Set<Field> originalFields = defaultAnalyzer.getFields(type);
+        final Set<Field> skippedFields = getMembersToSkip(type, fieldsToSkip);
+        return Views.setDiffView(originalFields, skippedFields);
+    }
+
+    @Override
+    public <T> Method getPostConstructMethod(Class<T> type) throws MultiException {
+        throw new IllegalStateException(LocalizationMessages.CDI_CLASS_ANALYZER_MISUSED());
+    }
+
+    @Override
+    public <T> Method getPreDestroyMethod(Class<T> type) throws MultiException {
+        throw new IllegalStateException(LocalizationMessages.CDI_CLASS_ANALYZER_MISUSED());
+    }
+
+    private <M extends Member> Set<M> getMembersToSkip(final Class<?> type, final Map<Class<?>, Set<M>> skippedMembers) {
+
+        final Set<M> directResult = skippedMembers.get(type);
+
+        if (directResult != null) {
+            return directResult;
+        }
+
+        // fallback for GLASSFISH-20255
+        final Set<M> compositeResult = new HashSet<>();
+        for (Entry<Class<?>, Set<M>> type2Method : skippedMembers.entrySet()) {
+
+            if (type2Method.getKey().isAssignableFrom(type)) {
+                compositeResult.addAll(type2Method.getValue());
+            }
+        }
+
+        return compositeResult;
+    }
+}
diff --git a/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/JerseyVetoed.java b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/JerseyVetoed.java
new file mode 100644
index 0000000..f294155
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/JerseyVetoed.java
@@ -0,0 +1,33 @@
+/*
+ * 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.ext.cdi1x.internal;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Use this to annotate types included in Jersey libraries
+ * that are to be CDI managed as defined by the {@link CdiComponentProvider}
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface JerseyVetoed {
+}
diff --git a/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/RequestScopedCdiBeanSupplier.java b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/RequestScopedCdiBeanSupplier.java
new file mode 100644
index 0000000..ffe0385
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/RequestScopedCdiBeanSupplier.java
@@ -0,0 +1,48 @@
+/*
+ * 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.ext.cdi1x.internal;
+
+import javax.enterprise.inject.Vetoed;
+import javax.enterprise.inject.spi.BeanManager;
+
+import org.glassfish.jersey.internal.inject.InjectionManager;
+import org.glassfish.jersey.process.internal.RequestScoped;
+
+/**
+ * Supplier to provide CDI managed components
+ * that should be mapped into Jersey request scope.
+ * For these components, Jersey will avoid
+ * injecting dynamic proxies for JAX-RS request scoped injectees.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@Vetoed
+public final class RequestScopedCdiBeanSupplier extends AbstractCdiBeanSupplier {
+
+    public RequestScopedCdiBeanSupplier(Class rawType,
+                                          InjectionManager locator,
+                                          BeanManager beanManager,
+                                          boolean cdiManaged) {
+        super(rawType, locator, beanManager, cdiManaged);
+    }
+
+    @Override
+    @RequestScoped
+    public Object get() {
+        return _provide();
+    }
+}
diff --git a/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/SingleInjectionManagerStore.java b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/SingleInjectionManagerStore.java
new file mode 100644
index 0000000..011c011
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/SingleInjectionManagerStore.java
@@ -0,0 +1,48 @@
+/*
+ * 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.ext.cdi1x.internal;
+
+import javax.ws.rs.WebApplicationException;
+
+import org.glassfish.jersey.ext.cdi1x.internal.spi.InjectionManagerStore;
+import org.glassfish.jersey.internal.inject.InjectionManager;
+
+/**
+ * Default {@link InjectionManagerStore injection manager} that assumes only one
+ * {@link InjectionManager injection manager} per application is used.
+ *
+ * @author Michal Gajdos
+ * @since 2.17
+ */
+final class SingleInjectionManagerStore implements InjectionManagerStore {
+
+    private volatile InjectionManager injectionManager;
+
+    @Override
+    public void registerInjectionManager(final InjectionManager injectionManager) {
+        if (this.injectionManager == null) {
+            this.injectionManager = injectionManager;
+        } else if (this.injectionManager != injectionManager) {
+            throw new WebApplicationException(LocalizationMessages.CDI_MULTIPLE_LOCATORS_INTO_SIMPLE_APP());
+        }
+    }
+
+    @Override
+    public InjectionManager getEffectiveInjectionManager() {
+        return injectionManager;
+    }
+}
diff --git a/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/package-info.java b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/package-info.java
new file mode 100644
index 0000000..979906e
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/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 CDI integration.
+ */
+package org.glassfish.jersey.ext.cdi1x.internal;
diff --git a/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/spi/BeanManagerProvider.java b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/spi/BeanManagerProvider.java
new file mode 100644
index 0000000..cf953c4
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/spi/BeanManagerProvider.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.ext.cdi1x.internal.spi;
+
+import javax.enterprise.inject.spi.BeanManager;
+
+/**
+ * Provider SPI for CDI {@link javax.enterprise.inject.spi.BeanManager} for the current context.
+ * Implementations can decide how to obtain bean manager (e.g. {@link javax.naming.InitialContext}, CDI 1.1 API, ...).
+ *
+ * @author Michal Gajdos
+ * @since 2.17
+ */
+public interface BeanManagerProvider {
+
+    /**
+     * Get the CDI {@link javax.enterprise.inject.spi.BeanManager bean manager} for the current context.
+     *
+     * @return bean manager for the current context or {@code null} if no bean manager is available.
+     */
+    public BeanManager getBeanManager();
+}
diff --git a/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/spi/InjectionManagerInjectedTarget.java b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/spi/InjectionManagerInjectedTarget.java
new file mode 100644
index 0000000..34d2ea6
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/spi/InjectionManagerInjectedTarget.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.ext.cdi1x.internal.spi;
+
+import javax.enterprise.inject.spi.InjectionTarget;
+
+import org.glassfish.jersey.internal.inject.InjectionManager;
+
+/**
+ * CDI {@link javax.enterprise.inject.spi.InjectionTarget injection target} that is co-injected by injection manager.
+ * The injection is done via given {@link InjectionManager injection manager}.
+ *
+ * @author Michal Gajdos
+ */
+public interface InjectionManagerInjectedTarget extends InjectionTarget {
+
+    /**
+     * Set the locator to be used to co-inject this injection target.
+     *
+     * @param injectionManager effective injection manager.
+     */
+    void setInjectionManager(InjectionManager injectionManager);
+}
diff --git a/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/spi/InjectionManagerStore.java b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/spi/InjectionManagerStore.java
new file mode 100644
index 0000000..d282565
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/spi/InjectionManagerStore.java
@@ -0,0 +1,49 @@
+/*
+ * 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.ext.cdi1x.internal.spi;
+
+import org.glassfish.jersey.internal.inject.InjectionManager;
+
+/**
+ * {@link InjectionManager injection manager} designed for Jersey
+ * {@link javax.enterprise.inject.spi.Extension CDI extension}. This SPI is designed to support deployments that can contain
+ * more than one Jersey/InjectionManager managed CDI {@link org.glassfish.jersey.server.spi.ComponentProvider component provider}
+ * (more injection manager) but only single CDI extension instance (e.g. EAR with multiple WARs). Each CDI component provider
+ * instance acknowledges the manager about new injection manager and manager is supposed to return the effective injection manager
+ * for the current context (based on the Servlet context, for example).
+ *
+ * @author Michal Gajdos
+ * @since 2.17
+ */
+public interface InjectionManagerStore {
+
+    /**
+     * Register a new {@link InjectionManager injection manager} with this manager.
+     *
+     * @param injectionManager injection manager to be registered.
+     */
+    public void registerInjectionManager(InjectionManager injectionManager);
+
+    /**
+     * Obtain the effective {@link InjectionManager injection manager}. The implementations are supposed to
+     * decide which of the registered injection managers is the currently effective locator. The decision can be based, for
+     * example, on current Servlet context (if the application is deployed on Servlet container).
+     *
+     * @return currently effective injection manager.
+     */
+    public InjectionManager getEffectiveInjectionManager();
+}
diff --git a/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/spi/InjectionTargetListener.java b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/spi/InjectionTargetListener.java
new file mode 100644
index 0000000..1cbdb70
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/spi/InjectionTargetListener.java
@@ -0,0 +1,35 @@
+/*
+ * 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.ext.cdi1x.internal.spi;
+
+/**
+ * An extension interface for implementations of {@link InjectionManagerStore}. HK2 locator
+ * managers implementing this interface are notified when an {@link javax.enterprise.inject.spi.InjectionTarget injection target}
+ * is processed by {@link org.glassfish.jersey.ext.cdi1x.internal.CdiComponentProvider}. Locator managers can then set the
+ * effective injection manager to the processed {@link InjectionManagerInjectedTarget target}.
+ *
+ * @author Michal Gajdos
+ */
+public interface InjectionTargetListener {
+
+    /**
+     * Notify the HK2 locator manager about new injection target being processed.
+     *
+     * @param target processed injection target.
+     */
+    public void notify(final InjectionManagerInjectedTarget target);
+}
diff --git a/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/spi/package-info.java b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/spi/package-info.java
new file mode 100644
index 0000000..f8f0078
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/spi/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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
+ */
+
+/**
+ * Jersey internal SPI to support custom defined HK2 injection binding for Jersey/CDI applications.
+ */
+package org.glassfish.jersey.ext.cdi1x.internal.spi;
diff --git a/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/spi/Hk2CustomBoundTypesProvider.java b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/spi/Hk2CustomBoundTypesProvider.java
new file mode 100644
index 0000000..dcdad4b
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/spi/Hk2CustomBoundTypesProvider.java
@@ -0,0 +1,52 @@
+/*
+ * 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.ext.cdi1x.spi;
+
+import java.lang.reflect.Type;
+import java.util.Set;
+
+/**
+ * Helper SPI to help specify Jersey HK2 custom bound types that should
+ * be HK2-injectable into CDI components.
+ *
+ * <p>Implementation of this type must be registered via META-INF/services
+ * mechanism. I.e. fully qualified name of an implementation class
+ * must be written into <code>META-INF/services/org.glassfish.jersey.ext.cdi11.spi.Hk2CustomBoundTypesProvider</code>
+ * file.
+ *
+ * <p>If more than one implementation is found, only a single one is selected that has the highest priority.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public interface Hk2CustomBoundTypesProvider {
+
+    /**
+     * Provide a set of types that should became accessible
+     * by CDI container in a form of CDI beans backed by HK2.
+     *
+     * <p>Jersey will ask CDI container to veto these types
+     * and will register HK2 backed beans into CDI, so that @{@link javax.inject.Inject}
+     * marked injection points could be satisfied.
+     *
+     * <p>The end user is responsible for defining necessary HK2 bindings
+     * within Jersey application. Should any of such bindings remain
+     * undefined, runtime errors are likely to occur.
+     *
+     * @return set of types for which HK2 backed CDI beans shall be registered.
+     */
+    public Set<Type> getHk2Types();
+}
diff --git a/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/spi/package-info.java b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/spi/package-info.java
new file mode 100644
index 0000000..81e0dbf
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/spi/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * 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
+ */
+
+/**
+ * Jersey package to support custom defined HK2 injection binding
+ * for Jersey/CDI applications.
+ */
+package org.glassfish.jersey.ext.cdi1x.spi;
diff --git a/ext/cdi/jersey-cdi1x/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension b/ext/cdi/jersey-cdi1x/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
new file mode 100644
index 0000000..64adbff
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
@@ -0,0 +1 @@
+org.glassfish.jersey.ext.cdi1x.internal.CdiComponentProvider
diff --git a/ext/cdi/jersey-cdi1x/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ComponentProvider b/ext/cdi/jersey-cdi1x/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ComponentProvider
new file mode 100644
index 0000000..64adbff
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ComponentProvider
@@ -0,0 +1 @@
+org.glassfish.jersey.ext.cdi1x.internal.CdiComponentProvider
diff --git a/ext/cdi/jersey-cdi1x/src/main/resources/org/glassfish/jersey/ext/cdi1x/internal/localization.properties b/ext/cdi/jersey-cdi1x/src/main/resources/org/glassfish/jersey/ext/cdi1x/internal/localization.properties
new file mode 100644
index 0000000..82ac7de
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/main/resources/org/glassfish/jersey/ext/cdi1x/internal/localization.properties
@@ -0,0 +1,26 @@
+#
+# 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
+#
+
+cdi.bean.manager.jndi.lookup.failed=Failed to obtain BeanManager from JNDI lookup.
+cdi.class.analyzer.misused=Jersey CDI class analyzer is supposed to be used only for field and method inspection when injecting CDI beans.
+cdi.class.being.checked=Class, {0}, is being checked with Jersey CDI component provider.
+cdi.class.bound.with.cdi=Class, {0}, has been bound by Jersey CDI component provider.
+cdi.hk2.bean.registered=CDI beans backed by HK2 have been registered for the following types: {0}
+cdi.lookup.failed=Error when lookup instance of class, {0}, in CDI.
+cdi.multiple.locators.into.simple.app=Trying to register multiple service locators into single service locator application.
+cdi.provider.initialized=Jersey CDI component provider initialized.
+cdi.request.scoped.components.recognized=The following CDI types were recognized as request scoped components in Jersey: {0}.
+cdi.type.vetoed=The following types have been vetoed by Jersey as per {0} configuration: {1}
diff --git a/ext/cdi/jersey-cdi1x/src/test/java/org/glassfish/jersey/ext/cdi1x/internal/CdiComponentProviderTest.java b/ext/cdi/jersey-cdi1x/src/test/java/org/glassfish/jersey/ext/cdi1x/internal/CdiComponentProviderTest.java
new file mode 100644
index 0000000..9242f82
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/test/java/org/glassfish/jersey/ext/cdi1x/internal/CdiComponentProviderTest.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.ext.cdi1x.internal;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.Type;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.HttpMethod;
+import javax.ws.rs.Path;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyReader;
+
+import org.junit.Test;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test for {@link CdiComponentProvider}.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+public class CdiComponentProviderTest {
+
+    public static class MyMessageBodyReader implements MessageBodyReader {
+
+        @Override
+        public boolean isReadable(final Class type,
+                                  final Type genericType,
+                                  final Annotation[] annotations,
+                                  final MediaType mediaType) {
+            return true;
+        }
+
+        @Override
+        public Object readFrom(final Class type,
+                               final Type genericType,
+                               final Annotation[] annotations,
+                               final MediaType mediaType,
+                               final MultivaluedMap httpHeaders,
+                               final InputStream entityStream) throws IOException, WebApplicationException {
+            return new Object();
+        }
+    }
+
+    public static class MyOtherMessageBodyReader extends MyMessageBodyReader {
+    }
+
+    public static class MyPojo {
+    }
+
+    public static class LocatorSubResource {
+
+        @Path("/")
+        public Object locator() {
+            return this;
+        }
+    }
+
+    @Path("/")
+    public static class ResourceMethodResource {
+
+        @GET
+        public Object get() {
+            return this;
+        }
+    }
+
+    public static class ResourceMethodSubResource {
+
+        @GET
+        public Object get() {
+            return this;
+        }
+    }
+
+    @Target({ElementType.METHOD})
+    @Retention(RetentionPolicy.RUNTIME)
+    @HttpMethod(HttpMethod.DELETE)
+    public @interface BINGO {
+    }
+
+    public static class CustomResourceMethodSubResource {
+
+        @BINGO
+        public Object get() {
+            return this;
+        }
+    }
+
+    /**
+     * Test provider detection.
+     */
+    @Test
+    public void testProviders() {
+        final CdiComponentProvider provider = new CdiComponentProvider();
+        assertFalse(provider.isJaxRsComponentType(MyPojo.class));
+        assertTrue(provider.isJaxRsComponentType(MyMessageBodyReader.class));
+        assertTrue(provider.isJaxRsComponentType(MyOtherMessageBodyReader.class));
+    }
+
+    /**
+     * Test sub-resource detection.
+     */
+    @Test
+    public void testResources() {
+        final CdiComponentProvider provider = new CdiComponentProvider();
+        assertTrue(provider.isJaxRsComponentType(LocatorSubResource.class));
+        assertTrue(provider.isJaxRsComponentType(ResourceMethodResource.class));
+        assertTrue(provider.isJaxRsComponentType(ResourceMethodSubResource.class));
+        assertTrue(provider.isJaxRsComponentType(CustomResourceMethodSubResource.class));
+    }
+}
diff --git a/ext/cdi/jersey-cdi1x/src/test/java/org/glassfish/jersey/ext/cdi1x/internal/CdiUtilTest.java b/ext/cdi/jersey-cdi1x/src/test/java/org/glassfish/jersey/ext/cdi1x/internal/CdiUtilTest.java
new file mode 100644
index 0000000..0cdafe1
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/test/java/org/glassfish/jersey/ext/cdi1x/internal/CdiUtilTest.java
@@ -0,0 +1,130 @@
+/*
+ * 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.ext.cdi1x.internal;
+
+import javax.annotation.Priority;
+import javax.enterprise.inject.spi.BeanManager;
+
+import org.glassfish.jersey.ext.cdi1x.internal.spi.BeanManagerProvider;
+import org.glassfish.jersey.ext.cdi1x.internal.spi.InjectionManagerStore;
+import org.glassfish.jersey.internal.inject.InjectionManager;
+
+import org.junit.Test;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.junit.Assert.assertThat;
+
+import mockit.Mock;
+import mockit.MockUp;
+import mockit.Mocked;
+import mockit.Verifications;
+
+/**
+ * Unit tests for {@link org.glassfish.jersey.ext.cdi1x.internal.CdiUtil}.
+ *
+ * @author Michal Gajdos
+ */
+public class CdiUtilTest {
+
+    public static class TestBeanManagerProvider implements BeanManagerProvider {
+
+        @Override
+        public BeanManager getBeanManager() {
+            throw new RuntimeException("BeanManager!");
+        }
+    }
+
+    @Test
+    public void getBeanManagerCustom(@Mocked final TestBeanManagerProvider custom,
+                                     @Mocked final DefaultBeanManagerProvider fallback) throws Exception {
+        CdiUtil.getBeanManager();
+
+        new Verifications() {{
+            custom.getBeanManager(); times = 1;
+            fallback.getBeanManager(); times = 0;
+        }};
+    }
+
+    @Test
+    public void getDefaultBeanManagerDefault(@Mocked final DefaultBeanManagerProvider fallback) throws Exception {
+        new MockUp<CdiUtil>() {
+            @Mock
+            @SuppressWarnings("UnusedDeclaration")
+            <T> T lookupService(final Class<T> clazz) {
+                return null;
+            }
+        };
+
+        CdiUtil.getBeanManager();
+
+        new Verifications() {{
+            fallback.getBeanManager(); times = 1;
+        }};
+    }
+
+    @Priority(500)
+    public static class MyServiceOne implements MyService {
+    }
+
+    @Priority(100)
+    public static class MyServiceTwo implements MyService {
+    }
+
+    @Priority(300)
+    public static class MyServiceThree implements MyService {
+    }
+
+    @Test
+    public void testLookupService() throws Exception {
+        assertThat(CdiUtil.lookupService(MyService.class), instanceOf(MyServiceTwo.class));
+    }
+
+    @Test
+    public void testLookupServiceNegative() throws Exception {
+        assertThat(CdiUtil.lookupService(CdiUtil.class), nullValue());
+    }
+
+    public static class TestInjectionManagerStore implements InjectionManagerStore {
+
+        @Override
+        public void registerInjectionManager(final InjectionManager injectionManager) {
+        }
+
+        @Override
+        public InjectionManager getEffectiveInjectionManager() {
+            return null;
+        }
+    }
+
+    @Test
+    public void createHk2LocatorManagerCustom() throws Exception {
+        assertThat(CdiUtil.createHk2InjectionManagerStore(), instanceOf(TestInjectionManagerStore.class));
+    }
+
+    @Test
+    public void createHk2LocatorManagerDefault() throws Exception {
+        new MockUp<CdiUtil>() {
+            @Mock
+            @SuppressWarnings("UnusedDeclaration")
+            <T> T lookupService(final Class<T> clazz) {
+                return null;
+            }
+        };
+
+        assertThat(CdiUtil.createHk2InjectionManagerStore(), instanceOf(SingleInjectionManagerStore.class));
+    }
+}
diff --git a/ext/cdi/jersey-cdi1x/src/test/java/org/glassfish/jersey/ext/cdi1x/internal/MyService.java b/ext/cdi/jersey-cdi1x/src/test/java/org/glassfish/jersey/ext/cdi1x/internal/MyService.java
new file mode 100644
index 0000000..be45c15
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/test/java/org/glassfish/jersey/ext/cdi1x/internal/MyService.java
@@ -0,0 +1,23 @@
+/*
+ * 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.ext.cdi1x.internal;
+
+/**
+ * @author Michal Gajdos
+ */
+public interface MyService {
+}
diff --git a/ext/cdi/jersey-cdi1x/src/test/resources/META-INF/services/org.glassfish.jersey.ext.cdi1x.internal.MyService b/ext/cdi/jersey-cdi1x/src/test/resources/META-INF/services/org.glassfish.jersey.ext.cdi1x.internal.MyService
new file mode 100644
index 0000000..fd1f187
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/test/resources/META-INF/services/org.glassfish.jersey.ext.cdi1x.internal.MyService
@@ -0,0 +1,3 @@
+org.glassfish.jersey.ext.cdi1x.internal.CdiUtilTest$MyServiceOne
+org.glassfish.jersey.ext.cdi1x.internal.CdiUtilTest$MyServiceTwo
+org.glassfish.jersey.ext.cdi1x.internal.CdiUtilTest$MyServiceThree
diff --git a/ext/cdi/jersey-cdi1x/src/test/resources/META-INF/services/org.glassfish.jersey.ext.cdi1x.internal.spi.BeanManagerProvider b/ext/cdi/jersey-cdi1x/src/test/resources/META-INF/services/org.glassfish.jersey.ext.cdi1x.internal.spi.BeanManagerProvider
new file mode 100644
index 0000000..542eea7
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/test/resources/META-INF/services/org.glassfish.jersey.ext.cdi1x.internal.spi.BeanManagerProvider
@@ -0,0 +1 @@
+org.glassfish.jersey.ext.cdi1x.internal.CdiUtilTest$TestBeanManagerProvider
diff --git a/ext/cdi/jersey-cdi1x/src/test/resources/META-INF/services/org.glassfish.jersey.ext.cdi1x.internal.spi.InjectionManagerStore b/ext/cdi/jersey-cdi1x/src/test/resources/META-INF/services/org.glassfish.jersey.ext.cdi1x.internal.spi.InjectionManagerStore
new file mode 100644
index 0000000..ee3756d
--- /dev/null
+++ b/ext/cdi/jersey-cdi1x/src/test/resources/META-INF/services/org.glassfish.jersey.ext.cdi1x.internal.spi.InjectionManagerStore
@@ -0,0 +1 @@
+org.glassfish.jersey.ext.cdi1x.internal.CdiUtilTest$TestInjectionManagerStore
diff --git a/ext/cdi/jersey-weld2-se/pom.xml b/ext/cdi/jersey-weld2-se/pom.xml
new file mode 100644
index 0000000..f9e88c8
--- /dev/null
+++ b/ext/cdi/jersey-weld2-se/pom.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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
+
+-->
+
+<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.ext.cdi</groupId>
+        <artifactId>project</artifactId>
+        <version>2.28-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>jersey-weld2-se</artifactId>
+    <packaging>jar</packaging>
+    <name>jersey-ext-weld2-se</name>
+
+    <description>WELD 2.x SE support</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.jboss.weld.se</groupId>
+            <artifactId>weld-se-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.jersey.core</groupId>
+            <artifactId>jersey-server</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.jersey.ext.cdi</groupId>
+            <artifactId>jersey-cdi1x</artifactId>
+            <version>${project.version}</version>
+        </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>
+     </build>
+</project>
diff --git a/ext/cdi/jersey-weld2-se/src/main/java/org/glassfish/jersey/weld/se/WeldInjectionManagerStore.java b/ext/cdi/jersey-weld2-se/src/main/java/org/glassfish/jersey/weld/se/WeldInjectionManagerStore.java
new file mode 100644
index 0000000..38f501d
--- /dev/null
+++ b/ext/cdi/jersey-weld2-se/src/main/java/org/glassfish/jersey/weld/se/WeldInjectionManagerStore.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.weld.se;
+
+import org.glassfish.jersey.ext.cdi1x.internal.GenericInjectionManagerStore;
+import org.glassfish.jersey.ext.cdi1x.internal.spi.InjectionManagerStore;
+import org.glassfish.jersey.internal.inject.InjectionManager;
+
+/**
+ * {@link InjectionManagerStore Injection manager} for Weld SE container. The provider
+ * enables multiple Jersey applications to be deployed within a single HTTP container.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ * @since 2.20
+ */
+public class WeldInjectionManagerStore extends GenericInjectionManagerStore {
+
+    @Override
+    public InjectionManager lookupInjectionManager() {
+        return WeldRequestScope.actualInjectorManager.get();
+    }
+}
diff --git a/ext/cdi/jersey-weld2-se/src/main/java/org/glassfish/jersey/weld/se/WeldRequestScope.java b/ext/cdi/jersey-weld2-se/src/main/java/org/glassfish/jersey/weld/se/WeldRequestScope.java
new file mode 100644
index 0000000..24f8c52
--- /dev/null
+++ b/ext/cdi/jersey-weld2-se/src/main/java/org/glassfish/jersey/weld/se/WeldRequestScope.java
@@ -0,0 +1,97 @@
+/*
+ * 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.weld.se;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.inject.Inject;
+
+import org.glassfish.jersey.ext.cdi1x.internal.JerseyVetoed;
+import org.glassfish.jersey.internal.inject.InjectionManager;
+import org.glassfish.jersey.server.spi.ExternalRequestContext;
+import org.glassfish.jersey.server.spi.ExternalRequestScope;
+
+import org.jboss.weld.context.bound.BoundRequestContext;
+
+/**
+ * Weld specific request scope to align CDI request context with Jersey.
+ *
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ */
+@ApplicationScoped
+@JerseyVetoed
+public class WeldRequestScope implements ExternalRequestScope<Map<String, Object>> {
+
+    @Inject
+    private BoundRequestContext context;
+
+    private final ThreadLocal<Map<String, Object>> actualMap = new ThreadLocal<>();
+
+    public static final ThreadLocal<InjectionManager> actualInjectorManager = new ThreadLocal<>();
+
+    @Override
+    public ExternalRequestContext<Map<String, Object>> open(InjectionManager injectionManager) {
+        final Map<String, Object> newMap = new ConcurrentHashMap<>();
+        actualMap.set(newMap);
+        context.associate(newMap);
+        context.activate();
+        actualInjectorManager.set(injectionManager);
+        return new ExternalRequestContext<>(newMap);
+    }
+
+    @Override
+    public void resume(final ExternalRequestContext<Map<String, Object>> ctx, InjectionManager injectionManager) {
+        final Map<String, Object> newMap = ctx.getContext();
+        actualInjectorManager.set(injectionManager);
+        actualMap.set(newMap);
+        context.associate(newMap);
+        context.activate();
+    }
+
+    @Override
+    public void suspend(final ExternalRequestContext<Map<String, Object>> ctx, InjectionManager injectionManager) {
+        try {
+            final Map<String, Object> contextMap = actualMap.get();
+            if (contextMap != null) {
+                context.deactivate();
+                context.dissociate(contextMap);
+            }
+        } finally {
+            actualMap.remove();
+            actualInjectorManager.remove();
+        }
+    }
+
+    @Override
+    public void close() {
+        try {
+            final Map<String, Object> contextMap = actualMap.get();
+            if (contextMap != null) {
+                context.invalidate();
+                context.deactivate();
+                context.dissociate(contextMap);
+            } else {
+                context.deactivate();
+            }
+        } finally {
+            actualMap.remove();
+            actualInjectorManager.remove();
+        }
+    }
+}
diff --git a/ext/cdi/jersey-weld2-se/src/main/resources/META-INF/beans.xml b/ext/cdi/jersey-weld2-se/src/main/resources/META-INF/beans.xml
new file mode 100644
index 0000000..aaf0547
--- /dev/null
+++ b/ext/cdi/jersey-weld2-se/src/main/resources/META-INF/beans.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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
+
+-->
+
+<beans/>
diff --git a/ext/cdi/jersey-weld2-se/src/main/resources/META-INF/services/org.glassfish.jersey.ext.cdi1x.internal.spi.InjectionManagerStore b/ext/cdi/jersey-weld2-se/src/main/resources/META-INF/services/org.glassfish.jersey.ext.cdi1x.internal.spi.InjectionManagerStore
new file mode 100644
index 0000000..68dc5f1
--- /dev/null
+++ b/ext/cdi/jersey-weld2-se/src/main/resources/META-INF/services/org.glassfish.jersey.ext.cdi1x.internal.spi.InjectionManagerStore
@@ -0,0 +1 @@
+org.glassfish.jersey.weld.se.WeldInjectionManagerStore
diff --git a/ext/cdi/jersey-weld2-se/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ExternalRequestScope b/ext/cdi/jersey-weld2-se/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ExternalRequestScope
new file mode 100644
index 0000000..bc04ec2
--- /dev/null
+++ b/ext/cdi/jersey-weld2-se/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ExternalRequestScope
@@ -0,0 +1 @@
+org.glassfish.jersey.weld.se.WeldRequestScope
\ No newline at end of file
diff --git a/ext/cdi/pom.xml b/ext/cdi/pom.xml
new file mode 100644
index 0000000..a3c8254
--- /dev/null
+++ b/ext/cdi/pom.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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
+
+-->
+
+<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.ext</groupId>
+        <artifactId>project</artifactId>
+        <version>2.28-SNAPSHOT</version>
+    </parent>
+
+    <groupId>org.glassfish.jersey.ext.cdi</groupId>
+    <artifactId>project</artifactId>
+    <packaging>pom</packaging>
+    <name>jersey-cdi-support</name>
+
+    <description>Jersey CDI providers umbrella project module</description>
+
+    <modules>
+        <module>jersey-cdi1x</module>
+        <module>jersey-cdi1x-ban-custom-hk2-binding</module>
+        <module>jersey-cdi1x-servlet</module>
+        <module>jersey-cdi1x-transaction</module>
+        <module>jersey-cdi1x-validation</module>
+        <module>jersey-weld2-se</module>
+    </modules>
+
+</project>
diff --git a/ext/entity-filtering/pom.xml b/ext/entity-filtering/pom.xml
new file mode 100644
index 0000000..a5619c5
--- /dev/null
+++ b/ext/entity-filtering/pom.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0"?>
+<!--
+
+    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>
+        <groupId>org.glassfish.jersey.ext</groupId>
+        <artifactId>project</artifactId>
+        <version>2.28-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>jersey-entity-filtering</artifactId>
+    <name>jersey-ext-entity-filtering</name>
+
+    <description>
+        Jersey extension module providing support for Entity Data Filtering.
+    </description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.glassfish.jersey.core</groupId>
+            <artifactId>jersey-client</artifactId>
+            <version>${project.version}</version>
+            <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.jersey.test-framework.providers</groupId>
+            <artifactId>jersey-test-framework-provider-bundle</artifactId>
+            <version>${project.version}</version>
+            <type>pom</type>
+            <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>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Export-Package>org.glassfish.jersey.message.filtering.*;version=${project.version}</Export-Package>
+                        <Import-Package>*</Import-Package>
+                    </instructions>
+                    <unpackBundle>true</unpackBundle>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/CommonScopeProvider.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/CommonScopeProvider.java
new file mode 100644
index 0000000..fb1bbe8
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/CommonScopeProvider.java
@@ -0,0 +1,149 @@
+/*
+ * 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.message.filtering;
+
+import java.lang.annotation.Annotation;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.Spliterator;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+import javax.ws.rs.core.Configuration;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.glassfish.jersey.internal.inject.InjectionManager;
+import org.glassfish.jersey.internal.inject.Providers;
+import org.glassfish.jersey.message.filtering.internal.LocalizationMessages;
+import org.glassfish.jersey.message.filtering.spi.FilteringHelper;
+import org.glassfish.jersey.message.filtering.spi.ScopeProvider;
+import org.glassfish.jersey.message.filtering.spi.ScopeResolver;
+import org.glassfish.jersey.model.internal.RankedComparator;
+
+/**
+ * Default implementation of {@link ScopeProvider scope provider}. This class can be used on client to retrieve
+ * entity-filtering scopes from given entity annotations or injected {@link Configuration configuration}. Class can also serve
+ * as a base class for server-side implementations.
+ *
+ * @author Michal Gajdos
+ * @see ServerScopeProvider
+ */
+@Singleton
+class CommonScopeProvider implements ScopeProvider {
+
+    private static final Logger LOGGER = Logger.getLogger(CommonScopeProvider.class.getName());
+
+    private final List<ScopeResolver> resolvers;
+    private final Configuration config;
+
+    /**
+     * Create new common scope provider with injected {@link Configuration configuration} and
+     * {@link InjectionManager injection manager}.
+     */
+    @Inject
+    public CommonScopeProvider(final Configuration config, final InjectionManager injectionManager) {
+        this.config = config;
+        Spliterator<ScopeResolver> resolverSpliterator =
+                Providers.getAllProviders(injectionManager, ScopeResolver.class, new RankedComparator<>()).spliterator();
+        this.resolvers = StreamSupport.stream(resolverSpliterator, false).collect(Collectors.toList());
+    }
+
+    @Override
+    public Set<String> getFilteringScopes(final Annotation[] entityAnnotations, final boolean defaultIfNotFound) {
+        Set<String> filteringScopes = new HashSet<>();
+
+        // Entity Annotations.
+        filteringScopes.addAll(getFilteringScopes(entityAnnotations));
+
+        if (filteringScopes.isEmpty()) {
+            // Configuration.
+            filteringScopes.addAll(getFilteringScopes(config));
+        }
+
+        // Use default scope if not in other scope.
+        return returnFilteringScopes(filteringScopes, defaultIfNotFound);
+    }
+
+    /**
+     * Return the default entity-filtering scope if the given set of scopes is empty and the processing should fallback to the
+     * default.
+     *
+     * @param filteringScopes       entity-filtering scopes to be examined.
+     * @param returnDefaultFallback {@code true} if the default entity-filtering scope should be returned if the given scopes
+     *                              are empty, {@code false} otherwise.
+     * @return entity-filtering scopes.
+     */
+    protected Set<String> returnFilteringScopes(final Set<String> filteringScopes, final boolean returnDefaultFallback) {
+        return returnDefaultFallback && filteringScopes.isEmpty() ? FilteringHelper.getDefaultFilteringScope() : filteringScopes;
+    }
+
+    /**
+     * Get entity-filtering scopes from all available {@link ScopeResolver scope resolvers} for given annotations.
+     *
+     * @param annotations annotations to retrieve entity-filtering scopes from.
+     * @return entity-filtering scopes or an empty set if none scope can be resolved.
+     */
+    protected Set<String> getFilteringScopes(final Annotation[] annotations) {
+        Set<String> filteringScopes = new HashSet<>();
+        for (final ScopeResolver provider : resolvers) {
+            mergeFilteringScopes(filteringScopes, provider.resolve(annotations));
+        }
+        return filteringScopes;
+    }
+
+    /**
+     * Get entity-filtering scopes from {@link Configuration}.
+     *
+     * @param config configuration the entity-filtering scopes are obtained from.
+     * @return entity-filtering scopes or an empty set if none scope can be resolved.
+     */
+    private Set<String> getFilteringScopes(final Configuration config) {
+        final Object property = config.getProperty(EntityFilteringFeature.ENTITY_FILTERING_SCOPE);
+
+        Set<String> filteringScopes = Collections.emptySet();
+        if (property != null) {
+            if (property instanceof Annotation) {
+                filteringScopes = getFilteringScopes(new Annotation[] {(Annotation) property});
+            } else if (property instanceof Annotation[]) {
+                filteringScopes = getFilteringScopes((Annotation[]) property);
+            } else {
+                LOGGER.log(Level.CONFIG, LocalizationMessages.ENTITY_FILTERING_SCOPE_NOT_ANNOTATIONS(property));
+            }
+        }
+        return filteringScopes;
+    }
+
+    /**
+     * Merge two sets of entity-filtering scopes.
+     *
+     * @param filteringScopes existing entity-filtering scopes.
+     * @param resolvedScopes entity-filtering scopes to be added to the existing ones.
+     */
+    protected void mergeFilteringScopes(final Set<String> filteringScopes, final Set<String> resolvedScopes) {
+        if (!filteringScopes.isEmpty() && !resolvedScopes.isEmpty()) {
+            LOGGER.log(Level.FINE, LocalizationMessages.MERGING_FILTERING_SCOPES());
+        }
+
+        filteringScopes.addAll(resolvedScopes);
+    }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/DefaultEntityProcessor.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/DefaultEntityProcessor.java
new file mode 100644
index 0000000..09ba981
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/DefaultEntityProcessor.java
@@ -0,0 +1,75 @@
+/*
+ * 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.message.filtering;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+
+import javax.annotation.Priority;
+import javax.inject.Singleton;
+
+import org.glassfish.jersey.internal.util.ReflectionHelper;
+import org.glassfish.jersey.message.filtering.spi.AbstractEntityProcessor;
+import org.glassfish.jersey.message.filtering.spi.EntityGraph;
+import org.glassfish.jersey.message.filtering.spi.EntityProcessorContext;
+import org.glassfish.jersey.message.filtering.spi.FilteringHelper;
+
+/**
+ * Default entity processor. Handles unannotated properties/accessors and adds them into default entity-filtering scope.
+ *
+ * @author Michal Gajdos
+ */
+@Singleton
+@Priority(Integer.MAX_VALUE - 1000)
+final class DefaultEntityProcessor extends AbstractEntityProcessor {
+
+    @Override
+    public Result process(final EntityProcessorContext context) {
+        switch (context.getType()) {
+            case CLASS_READER:
+            case CLASS_WRITER:
+                final EntityGraph graph = context.getEntityGraph();
+                if (graph.getFilteringScopes().isEmpty()) {
+                    graph.addFilteringScopes(FilteringHelper.getDefaultFilteringScope());
+                }
+                return Result.APPLY;
+
+            case PROPERTY_READER:
+            case PROPERTY_WRITER:
+                final Field field = context.getField();
+                process(context.getEntityGraph(), field.getName(), field.getGenericType());
+                return Result.APPLY;
+
+            case METHOD_READER:
+            case METHOD_WRITER:
+                final Method method = context.getMethod();
+                process(context.getEntityGraph(), ReflectionHelper.getPropertyName(method), method.getGenericReturnType());
+                return Result.APPLY;
+
+            default:
+                // NOOP.
+        }
+        return Result.SKIP;
+    }
+
+    private void process(final EntityGraph graph, final String fieldName, final Type fieldType) {
+        if (!graph.presentInScopes(fieldName)) {
+            addFilteringScopes(fieldName, FilteringHelper.getEntityClass(fieldType), graph.getClassFilteringScopes(), graph);
+        }
+    }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EmptyEntityGraphImpl.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EmptyEntityGraphImpl.java
new file mode 100644
index 0000000..59d074b
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EmptyEntityGraphImpl.java
@@ -0,0 +1,154 @@
+/*
+ * 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.message.filtering;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+import org.glassfish.jersey.message.filtering.spi.EntityGraph;
+
+/**
+ * {@link EntityGraph} implementation that does not contain any fields/subgraphs. Methods that are supposed to modify the graph
+ * would throw an {@link UnsupportedOperationException}.
+ *
+ * @author Michal Gajdos
+ */
+final class EmptyEntityGraphImpl implements EntityGraph {
+
+    private final Class<?> clazz;
+
+    @SuppressWarnings("JavaDoc")
+    EmptyEntityGraphImpl(final Class<?> clazz) {
+        this.clazz = clazz;
+    }
+
+    @Override
+    public EntityGraph addField(final String fieldName) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public EntityGraph addField(final String fieldName, final String... filteringScopes) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public EntityGraph addField(final String fieldName, final Set<String> filteringScopes) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public EntityGraph addSubgraph(final String fieldName, final Class<?> fieldClass) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public EntityGraph addSubgraph(final String fieldName, final Class<?> fieldClass, final String... filteringScopes) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public EntityGraph addSubgraph(final String fieldName, final Class<?> fieldClass, final Set<String> filteringScopes) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Class<?> getEntityClass() {
+        return clazz;
+    }
+
+    @Override
+    public Set<String> getFields(final String filteringScope) {
+        return Collections.emptySet();
+    }
+
+    @Override
+    public Set<String> getFields(final String... filteringScopes) {
+        return Collections.emptySet();
+    }
+
+    @Override
+    public Set<String> getFields(final Set<String> filteringScopes) {
+        return Collections.emptySet();
+    }
+
+    @Override
+    public Map<String, Class<?>> getSubgraphs(final String filteringScope) {
+        return Collections.emptyMap();
+    }
+
+    @Override
+    public Map<String, Class<?>> getSubgraphs(final String... filteringScopes) {
+        return Collections.emptyMap();
+    }
+
+    @Override
+    public Map<String, Class<?>> getSubgraphs(final Set<String> filteringScopes) {
+        return Collections.emptyMap();
+    }
+
+    @Override
+    public boolean presentInScopes(final String field) {
+        return false;
+    }
+
+    @Override
+    public boolean presentInScope(final String field, String filteringScope) {
+        return false;
+    }
+
+    @Override
+    public EntityGraph remove(final String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Set<String> getFilteringScopes() {
+        return Collections.emptySet();
+    }
+
+    @Override
+    public Set<String> getClassFilteringScopes() {
+        return Collections.emptySet();
+    }
+
+    @Override
+    public EntityGraph addFilteringScopes(final Set<String> filteringScopes) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final EmptyEntityGraphImpl that = (EmptyEntityGraphImpl) o;
+
+        return clazz.equals(that.clazz);
+
+    }
+
+    @Override
+    public int hashCode() {
+        return clazz.hashCode();
+    }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EmptyObjectGraph.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EmptyObjectGraph.java
new file mode 100644
index 0000000..5e7864e
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EmptyObjectGraph.java
@@ -0,0 +1,62 @@
+/*
+ * 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.message.filtering;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+import org.glassfish.jersey.message.filtering.spi.ObjectGraph;
+
+/**
+ * Object graph representing empty domain classes.
+ *
+ * @author Michal Gajdos
+ */
+final class EmptyObjectGraph implements ObjectGraph {
+
+    private final Class<?> entityClass;
+
+    EmptyObjectGraph(final Class<?> entityClass) {
+        this.entityClass = entityClass;
+    }
+
+    @Override
+    public Class<?> getEntityClass() {
+        return entityClass;
+    }
+
+    @Override
+    public Set<String> getFields() {
+        return Collections.emptySet();
+    }
+
+    @Override
+    public Set<String> getFields(final String parent) {
+        return Collections.emptySet();
+    }
+
+    @Override
+    public Map<String, ObjectGraph> getSubgraphs() {
+        return Collections.emptyMap();
+    }
+
+    @Override
+    public Map<String, ObjectGraph> getSubgraphs(final String parent) {
+        return Collections.emptyMap();
+    }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityFiltering.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityFiltering.java
new file mode 100644
index 0000000..98d7d7d
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityFiltering.java
@@ -0,0 +1,104 @@
+/*
+ * 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.message.filtering;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Meta-annotation used to create entity filtering annotations for entity (model) classes and resource methods and resources.
+ * <p>
+ * Entity Data Filtering via annotations is supposed to be used to annotate:
+ * <ul>
+ * <li>entity classes (supported on both, server and client sides), and</li>
+ * <li>resource methods / resource classes (server side)</li>
+ * </ul>
+ * </p>
+ * <p>
+ * In entity filtering, a <i>entity-filtering</i> annotation is first defined using the {@code @EntityFiltering} meta-annotation:
+ * <pre>
+ *  &#64;Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD })
+ *  &#64;Retention(value = RetentionPolicy.RUNTIME)
+ *  <b>&#64;EntityFiltering</b>
+ *  <b>public @interface DetailedView</b> {
+ *
+ *      public static class Factory extends <b>AnnotationLiteral&lt;DetailedView&gt;</b> implements <b>DetailedView</b> {
+ *
+ *         public static <b>DetailedView</b> get() {
+               return new Factory();
+           }
+ *      }
+ *  }
+ * </pre>
+ * </p>
+ * <p>
+ * Entity-filtering annotation should provide a factory class/method to create an instance of the annotation. Example of such
+ * factory can be seen in the {@code DetailedView} above. Such instances can be then passed to the client/server runtime to
+ * define/override entity-filtering scopes.
+ * </p>
+ * <p>
+ * The defined entity-filtering annotation is then used to decorate a entity, it's property accessors or fields (more than one
+ * entity may be decorated with the same entity-filtering annotation):
+ * <pre>
+ *  public class MyEntityClass {
+ *
+ *      <b>&#64;DetailedView</b>
+ *      private String myField;
+ *
+ *      ...
+ *  }
+ * </pre>
+ * </p>
+ * <p>
+ * At last, on the server-side, the entity-filtering annotations are applied to the resource or resource method(s) to which the
+ * entity-filtering should be applied:
+ * <pre>
+ *  &#64;Path("/")
+ *  public class MyResourceClass {
+ *
+ *      &#64;GET
+ *      &#64;Produces("text/plain")
+ *      &#64;Path("{id}")
+ *      <b>&#64;DetailedView</b>
+ *      public MyEntityClass get(@PathParam("id") String id) {
+ *          // Return MyEntityClass.
+ *      }
+ *  }
+ * </pre>
+ * </p>
+ * <p>
+ * At last, on the client-side, the entity-filtering annotations are passed to the runtime via
+ * {@link javax.ws.rs.client.Entity#entity(Object, javax.ws.rs.core.MediaType, java.lang.annotation.Annotation[]) Entity.entity()}
+ * method and the entity-filtering scopes are then derived from the annotations:
+ * <pre>
+ *  ClientBuilder.newClient()
+ *      .target("resource")
+ *      .request()
+ *      .post(Entity.entity(myentity, "application/json", <b>new Annotation[] {MyEntityClass.Factory.get()}</b>));
+ * </pre>
+ * </p>
+ *
+ * @author Michal Gajdos
+ */
+@Target(ElementType.ANNOTATION_TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface EntityFiltering {
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityFilteringBinder.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityFilteringBinder.java
new file mode 100644
index 0000000..8990270
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityFilteringBinder.java
@@ -0,0 +1,62 @@
+/*
+ * 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.message.filtering;
+
+import javax.ws.rs.core.GenericType;
+
+import javax.inject.Singleton;
+
+import org.glassfish.jersey.internal.inject.AbstractBinder;
+import org.glassfish.jersey.message.filtering.spi.EntityGraphProvider;
+import org.glassfish.jersey.message.filtering.spi.EntityInspector;
+import org.glassfish.jersey.message.filtering.spi.ObjectGraph;
+import org.glassfish.jersey.message.filtering.spi.ObjectGraphTransformer;
+import org.glassfish.jersey.message.filtering.spi.ObjectProvider;
+
+/**
+ * Binder for Entity Data Filtering feature.
+ *
+ * @author Michal Gajdos
+ */
+final class EntityFilteringBinder extends AbstractBinder {
+
+    @Override
+    protected void configure() {
+        // Entity Inspector.
+        bind(EntityInspectorImpl.class)
+                .to(EntityInspector.class)
+                .in(Singleton.class);
+
+        // Entity Graph Provider.
+        bind(EntityGraphProviderImpl.class)
+                .to(EntityGraphProvider.class)
+                .in(Singleton.class);
+
+        // Object Provider & Object Graph Transformer.
+        bindAsContract(ObjectGraphProvider.class)
+                // FilteringObjectProvider.
+                .to(ObjectProvider.class)
+                .to(new GenericType<ObjectProvider<Object>>() {})
+                .to(new GenericType<ObjectProvider<ObjectGraph>>() {})
+                // FilteringGraphTransformer.
+                .to(ObjectGraphTransformer.class)
+                .to(new GenericType<ObjectGraphTransformer<Object>>() {})
+                .to(new GenericType<ObjectGraphTransformer<ObjectGraph>>() {})
+                // Scope.
+                .in(Singleton.class);
+    }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityFilteringFeature.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityFilteringFeature.java
new file mode 100644
index 0000000..4c4e1eb
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityFilteringFeature.java
@@ -0,0 +1,98 @@
+/*
+ * 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.message.filtering;
+
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.Feature;
+import javax.ws.rs.core.FeatureContext;
+
+/**
+ * {@link Feature} used to add support for Entity Data Filtering feature for entity-filtering annotations based on
+ * {@link EntityFiltering} meta-annotation.
+ *
+ * @author Michal Gajdos
+ */
+public final class EntityFilteringFeature implements Feature {
+
+    /**
+     * Defines one or more annotations that should be used as entity-filtering scope when reading/writing an entity.
+     * <p>
+     * The property can be used on client to define the scope as well as on server to override the scope derived from current
+     * request processing context (resource methods / resource classes).
+     * </p>
+     * <p>
+     * If the property is set, the specified annotations will be used to create (override) entity-filtering scope.
+     * </p>
+     * <p>
+     * The property value MUST be an instance of {@link java.lang.annotation.Annotation} or {@code Annotation[]} array. To obtain
+     * the annotation instances refer to the {@link EntityFiltering} for requirements on creating entity-filtering annotations.
+     * </p>
+     * <p>
+     * A default value is not set.
+     * </p>
+     * <p>
+     * The name of the configuration property is <tt>{@value}</tt>.
+     * </p>
+     *
+     * @see EntityFiltering
+     */
+    public static final String ENTITY_FILTERING_SCOPE = "jersey.config.entityFiltering.scope";
+
+    @Override
+    public boolean configure(final FeatureContext context) {
+        final Configuration config = context.getConfiguration();
+
+        if (!config.isRegistered(EntityFilteringProcessor.class)) {
+            // Binder (FilteringObjectProvider/FilteringGraphTransformer).
+            if (!config.isRegistered(EntityFilteringBinder.class)) {
+                context.register(new EntityFilteringBinder());
+            }
+
+            // Entity Processors.
+            context.register(EntityFilteringProcessor.class);
+            if (!config.isRegistered(DefaultEntityProcessor.class)) {
+                context.register(DefaultEntityProcessor.class);
+            }
+
+            // Scope Providers.
+            context.register(EntityFilteringScopeResolver.class);
+
+            // Scope Resolver.
+            if (RuntimeType.SERVER == config.getRuntimeType()) {
+                context.register(ServerScopeProvider.class);
+            } else {
+                context.register(CommonScopeProvider.class);
+            }
+
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Return {@code true} whether at least one of the entity filtering features is registered in the given config.
+     *
+     * @param config config to be examined for presence of entity filtering feature.
+     * @return {@code true} if entity filtering is enabled for given config, {@code false} otherwise.
+     */
+    public static boolean enabled(final Configuration config) {
+        return config.isRegistered(EntityFilteringFeature.class)
+                || config.isRegistered(SecurityEntityFilteringFeature.class)
+                || config.isRegistered(SelectableEntityFilteringFeature.class);
+    }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityFilteringHelper.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityFilteringHelper.java
new file mode 100644
index 0000000..2dc188d
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityFilteringHelper.java
@@ -0,0 +1,109 @@
+/*
+ * 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.message.filtering;
+
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.glassfish.jersey.message.filtering.spi.FilteringHelper;
+
+/**
+ * Utility methods for Entity Data Filtering.
+ *
+ * @author Michal Gajdos
+ */
+final class EntityFilteringHelper {
+
+    /**
+     * Get entity-filtering scopes from given annotations. Scopes are only derived from entity-filtering annotations.
+     *
+     * @param annotations list of arbitrary annotations.
+     * @return a set of entity-filtering scopes.
+     */
+    public static Set<String> getFilteringScopes(final Annotation[] annotations) {
+        return getFilteringScopes(annotations, true);
+    }
+
+    /**
+     * Get entity-filtering scopes from given annotations. Scopes are only derived from entity-filtering annotations.
+     *
+     * @param annotations list of arbitrary annotations.
+     * @param filter {@code true} whether the given annotation should be reduced to only entity-filtering annotations,
+     * {@code false} otherwise.
+     * @return a set of entity-filtering scopes.
+     */
+    public static Set<String> getFilteringScopes(Annotation[] annotations, final boolean filter) {
+        if (annotations.length == 0) {
+            return Collections.emptySet();
+        }
+
+        final Set<String> contexts = new HashSet<>(annotations.length);
+
+        annotations = filter ? getFilteringAnnotations(annotations) : annotations;
+        for (final Annotation annotation : annotations) {
+            contexts.add(annotation.annotationType().getName());
+        }
+
+        return contexts;
+    }
+
+    /**
+     * Filter given annotations and return only entity-filtering ones.
+     *
+     * @param annotations list of arbitrary annotations.
+     * @return entity-filtering annotations or an empty array.
+     */
+    public static Annotation[] getFilteringAnnotations(final Annotation[] annotations) {
+        if (annotations == null || annotations.length == 0) {
+            return FilteringHelper.EMPTY_ANNOTATIONS;
+        }
+
+        final List<Annotation> filteringAnnotations = new ArrayList<>(annotations.length);
+
+        for (final Annotation annotation : annotations) {
+            final Class<? extends Annotation> annotationType = annotation.annotationType();
+
+            for (final Annotation metaAnnotation : annotationType.getDeclaredAnnotations()) {
+                if (metaAnnotation instanceof EntityFiltering) {
+                    filteringAnnotations.add(annotation);
+                }
+            }
+        }
+
+        return filteringAnnotations.toArray(new Annotation[filteringAnnotations.size()]);
+    }
+
+    public static <T extends Annotation> T getAnnotation(final Annotation[] annotations, final Class<T> clazz) {
+        for (final Annotation annotation : annotations) {
+            if (annotation.annotationType().getClass().isAssignableFrom(clazz)) {
+                //noinspection unchecked
+                return (T) annotation;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Prevent instantiation.
+     */
+    private EntityFilteringHelper() {
+    }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityFilteringProcessor.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityFilteringProcessor.java
new file mode 100644
index 0000000..4af5311
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityFilteringProcessor.java
@@ -0,0 +1,77 @@
+/*
+ * 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.message.filtering;
+
+import java.lang.annotation.Annotation;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.annotation.Priority;
+import javax.inject.Singleton;
+
+import org.glassfish.jersey.message.filtering.spi.AbstractEntityProcessor;
+import org.glassfish.jersey.message.filtering.spi.EntityGraph;
+import org.glassfish.jersey.message.filtering.spi.EntityProcessorContext;
+
+/**
+ * Entity processor handling entity-filtering annotations.
+ *
+ * @author Michal Gajdos
+ */
+@Singleton
+@Priority(Integer.MAX_VALUE - 2000)
+final class EntityFilteringProcessor extends AbstractEntityProcessor {
+
+    @Override
+    public Result process(final EntityProcessorContext context) {
+        switch (context.getType()) {
+            case CLASS_READER:
+            case CLASS_WRITER:
+                addGlobalScopes(EntityFilteringHelper.getFilteringScopes(context.getEntityClass().getDeclaredAnnotations()),
+                        context.getEntityGraph());
+                break;
+            default:
+                // NOOP.
+                break;
+        }
+        return super.process(context);
+    }
+
+    @Override
+    protected Result process(final String field, final Class<?> fieldClass, final Annotation[] fieldAnnotations,
+                             final Annotation[] annotations, final EntityGraph graph) {
+        final Set<String> filteringScopes = new HashSet<>();
+
+        if (fieldAnnotations.length > 0) {
+            filteringScopes.addAll(EntityFilteringHelper.getFilteringScopes(fieldAnnotations));
+        }
+        if (annotations.length > 0) {
+            filteringScopes.addAll(EntityFilteringHelper.getFilteringScopes(annotations));
+        }
+
+        if (!filteringScopes.isEmpty()) {
+            if (field != null) {
+                addFilteringScopes(field, fieldClass, filteringScopes, graph);
+            } else {
+                addGlobalScopes(filteringScopes, graph);
+            }
+            return Result.APPLY;
+        } else {
+            return Result.SKIP;
+        }
+    }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityFilteringScopeResolver.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityFilteringScopeResolver.java
new file mode 100644
index 0000000..cd71ec8
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityFilteringScopeResolver.java
@@ -0,0 +1,39 @@
+/*
+ * 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.message.filtering;
+
+import java.lang.annotation.Annotation;
+import java.util.Set;
+
+import javax.inject.Singleton;
+
+import org.glassfish.jersey.message.filtering.spi.ScopeResolver;
+
+/**
+ * {@link ScopeResolver Scope provider} processing entity-filtering annotations created using
+ * {@link org.glassfish.jersey.message.filtering.EntityFiltering @EntityFiltering} meta-annotation.
+ *
+ * @author Michal Gajdos
+ */
+@Singleton
+final class EntityFilteringScopeResolver implements ScopeResolver {
+
+    @Override
+    public Set<String> resolve(final Annotation[] annotations) {
+        return EntityFilteringHelper.getFilteringScopes(annotations);
+    }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityGraphImpl.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityGraphImpl.java
new file mode 100644
index 0000000..3d4962c
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityGraphImpl.java
@@ -0,0 +1,250 @@
+/*
+ * 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.message.filtering;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.glassfish.jersey.internal.guava.HashBasedTable;
+import org.glassfish.jersey.internal.guava.HashMultimap;
+import org.glassfish.jersey.internal.guava.Table;
+import org.glassfish.jersey.message.filtering.spi.EntityGraph;
+import org.glassfish.jersey.message.filtering.spi.ScopeProvider;
+
+/**
+ * Default implementation of {@link EntityGraph}.
+ *
+ * @author Michal Gajdos
+ */
+final class EntityGraphImpl implements EntityGraph {
+
+    private final Class<?> entityClass;
+
+    private final Set<String> globalScopes;
+    private final Set<String> localScopes;
+
+    // <FilteringScope, FieldName>
+    private final HashMultimap<String, String> fields;
+    // <FilteringScope, FieldName, Class>
+    private final Table<String, String, Class<?>> subgraphs;
+
+    /**
+     * Create an entity graph for given class.
+     *
+     * @param entityClass entity class the graph should be created for.
+     */
+    public EntityGraphImpl(final Class<?> entityClass) {
+        this.entityClass = entityClass;
+
+        this.fields = HashMultimap.create();
+        this.subgraphs = HashBasedTable.create();
+
+        this.globalScopes = new HashSet<>();
+        this.localScopes = new HashSet<>();
+    }
+
+    @Override
+    public EntityGraphImpl addField(final String fieldName) {
+        return addField(fieldName, globalScopes);
+    }
+
+    @Override
+    public EntityGraphImpl addField(final String fieldName, final String... filteringScopes) {
+        return addField(fieldName, Arrays.stream(filteringScopes).collect(Collectors.toSet()));
+    }
+
+    @Override
+    public EntityGraphImpl addField(final String fieldName, final Set<String> filteringScopes) {
+        for (final String filteringScope : filteringScopes) {
+            createFilteringScope(filteringScope);
+            fields.get(filteringScope).add(fieldName);
+        }
+
+        return this;
+    }
+
+    @Override
+    public EntityGraphImpl addFilteringScopes(final Set<String> filteringScopes) {
+        this.globalScopes.addAll(filteringScopes);
+        return this;
+    }
+
+    @Override
+    public EntityGraphImpl addSubgraph(final String fieldName, final Class<?> fieldClass) {
+        return addSubgraph(fieldName, fieldClass, globalScopes);
+    }
+
+    @Override
+    public EntityGraphImpl addSubgraph(final String fieldName, final Class<?> fieldClass, final String... filteringScopes) {
+        return addSubgraph(fieldName, fieldClass, Arrays.stream(filteringScopes).collect(Collectors.toSet()));
+    }
+
+    @Override
+    public EntityGraphImpl addSubgraph(final String fieldName, final Class<?> fieldClass, final Set<String> filteringScopes) {
+        for (final String filteringScope : filteringScopes) {
+            createFilteringScope(filteringScope);
+            subgraphs.put(filteringScope, fieldName, fieldClass);
+        }
+
+        return this;
+    }
+
+    @Override
+    public Class<?> getEntityClass() {
+        return entityClass;
+    }
+
+    @Override
+    public Set<String> getFields(final String filteringScope) {
+        return fields.containsKey(filteringScope)
+                ? Collections.unmodifiableSet(fields.get(filteringScope)) : Collections.<String>emptySet();
+    }
+
+    @Override
+    public Set<String> getFields(final String... filteringScopes) {
+        return filteringScopes.length == 0 ? Collections.<String>emptySet()
+                : (filteringScopes.length == 1
+                ? getFields(filteringScopes[0])
+                : getFields(Arrays.stream(filteringScopes).collect(Collectors.toSet())));
+    }
+
+    @Override
+    public Set<String> getFields(final Set<String> filteringScopes) {
+        final Set<String> matched = new HashSet<>();
+
+        for (final String filteringContext : filteringScopes) {
+            matched.addAll(fields.get(filteringContext));
+        }
+
+        return matched;
+    }
+
+    @Override
+    public Set<String> getFilteringScopes() {
+        HashSet<String> strings = new HashSet<>(globalScopes);
+        strings.addAll(localScopes);
+        return Collections.unmodifiableSet(strings);
+    }
+
+    @Override
+    public Set<String> getClassFilteringScopes() {
+        return Collections.unmodifiableSet(globalScopes);
+    }
+
+    @Override
+    public Map<String, Class<?>> getSubgraphs(final String filteringScope) {
+        return subgraphs.containsRow(filteringScope)
+                ? Collections.unmodifiableMap(subgraphs.row(filteringScope)) : Collections.<String, Class<?>>emptyMap();
+    }
+
+    @Override
+    public Map<String, Class<?>> getSubgraphs(final String... filteringScopes) {
+        return filteringScopes.length == 0
+                ? Collections.<String, Class<?>>emptyMap()
+                : (filteringScopes.length == 1
+                           ? getSubgraphs(filteringScopes[0])
+                           : getSubgraphs(Arrays.stream(filteringScopes).collect(Collectors.toSet())));
+    }
+
+    @Override
+    public Map<String, Class<?>> getSubgraphs(final Set<String> filteringScopes) {
+        final Map<String, Class<?>> matched = new HashMap<>();
+
+        for (final String filteringContext : filteringScopes) {
+            matched.putAll(subgraphs.row(filteringContext));
+        }
+
+        return matched;
+    }
+
+    @Override
+    public boolean presentInScopes(final String name) {
+        return fields.containsValue(name) || subgraphs.containsColumn(name);
+    }
+
+    @Override
+    public boolean presentInScope(final String field, final String filteringScope) {
+        return fields.containsEntry(filteringScope, field) || subgraphs.contains(filteringScope, field);
+    }
+
+    @Override
+    public EntityGraphImpl remove(final String fieldName) {
+        for (final String scope : getFilteringScopes()) {
+            if (fields.containsEntry(scope, fieldName)) {
+                fields.remove(scope, fieldName);
+            }
+            if (subgraphs.containsColumn(fieldName)) {
+                subgraphs.remove(scope, fieldName);
+            }
+        }
+        return this;
+    }
+
+    /**
+     * Create a new entity-filtering scope based on the {@link ScopeProvider#DEFAULT_SCOPE default one}.
+     *
+     * @param filteringScope entity-filtering scope to be created.
+     */
+    private void createFilteringScope(final String filteringScope) {
+        // Do not create a scope if it already exists.
+        if (!getFilteringScopes().contains(filteringScope)) {
+            // Copy contents of default scope into the new one.
+            if (localScopes.contains(ScopeProvider.DEFAULT_SCOPE)) {
+                fields.putAll(filteringScope, fields.get(ScopeProvider.DEFAULT_SCOPE));
+
+                final Map<String, Class<?>> row = subgraphs.row(ScopeProvider.DEFAULT_SCOPE);
+                for (final Map.Entry<String, Class<?>> entry : row.entrySet()) {
+                    subgraphs.put(filteringScope, entry.getKey(), entry.getValue());
+                }
+            }
+            localScopes.add(filteringScope);
+        }
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final EntityGraphImpl that = (EntityGraphImpl) o;
+
+        return entityClass.equals(that.entityClass)
+                && fields.equals(that.fields)
+                && globalScopes.equals(that.globalScopes)
+                && localScopes.equals(that.localScopes)
+                && subgraphs.equals(that.subgraphs);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = entityClass.hashCode();
+        result = 53 * result + globalScopes.hashCode();
+        result = 53 * result + localScopes.hashCode();
+        result = 53 * result + fields.hashCode();
+        result = 53 * result + subgraphs.hashCode();
+        return result;
+    }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityGraphProviderImpl.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityGraphProviderImpl.java
new file mode 100644
index 0000000..ec67e9e
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityGraphProviderImpl.java
@@ -0,0 +1,85 @@
+/*
+ * 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.message.filtering;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentMap;
+
+import org.glassfish.jersey.internal.util.collection.DataStructures;
+import org.glassfish.jersey.message.filtering.spi.EntityGraph;
+import org.glassfish.jersey.message.filtering.spi.EntityGraphProvider;
+import org.glassfish.jersey.message.filtering.spi.ObjectGraph;
+
+/**
+ * Provides {@link EntityGraph entity graph} and {@link ObjectGraph object graph} instances.
+ *
+ * @author Michal Gajdos
+ */
+final class EntityGraphProviderImpl implements EntityGraphProvider {
+
+    private final ConcurrentMap<Class<?>, EntityGraph> writerClassToGraph = DataStructures.createConcurrentMap();
+    private final ConcurrentMap<Class<?>, EntityGraph> readerClassToGraph = DataStructures.createConcurrentMap();
+
+    @Override
+    public EntityGraph getOrCreateEntityGraph(final Class<?> entityClass, final boolean forWriter) {
+        final ConcurrentMap<Class<?>, EntityGraph> classToGraph = forWriter ? writerClassToGraph : readerClassToGraph;
+
+        if (!classToGraph.containsKey(entityClass)) {
+            classToGraph.putIfAbsent(entityClass, new EntityGraphImpl(entityClass));
+        }
+        return classToGraph.get(entityClass);
+    }
+
+    @Override
+    public EntityGraph getOrCreateEmptyEntityGraph(final Class<?> entityClass, final boolean forWriter) {
+        final ConcurrentMap<Class<?>, EntityGraph> classToGraph = forWriter ? writerClassToGraph : readerClassToGraph;
+
+        if (!classToGraph.containsKey(entityClass)
+                || !(classToGraph.get(entityClass) instanceof EmptyEntityGraphImpl)) {
+            classToGraph.put(entityClass, new EmptyEntityGraphImpl(entityClass));
+        }
+        return classToGraph.get(entityClass);
+    }
+
+    /**
+     * Return an unmodifiable map of entity graphs for reader/writer.
+     *
+     * @param forWriter flag determining whether the returned map should be for writer/reader.
+     * @return an unmodifiable map of entity graphs.
+     */
+    public Map<Class<?>, EntityGraph> asMap(final boolean forWriter) {
+        return Collections.unmodifiableMap(forWriter ? writerClassToGraph : readerClassToGraph);
+    }
+
+    @Override
+    public boolean containsEntityGraph(final Class<?> entityClass, final boolean forWriter) {
+        return forWriter ? writerClassToGraph.containsKey(entityClass) : readerClassToGraph.containsKey(entityClass);
+    }
+
+    @Override
+    public ObjectGraph createObjectGraph(final Class<?> entityClass, final Set<String> filteringScopes,
+                                         final boolean forWriter) {
+        final Map<Class<?>, EntityGraph> classToGraph = forWriter ? writerClassToGraph : readerClassToGraph;
+        final EntityGraph entityGraph = classToGraph.get(entityClass);
+
+        return entityGraph == null
+                ? new EmptyObjectGraph(entityClass)
+                : new ObjectGraphImpl(classToGraph, entityGraph, filteringScopes);
+    }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityInspectorImpl.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityInspectorImpl.java
new file mode 100644
index 0000000..76af833
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityInspectorImpl.java
@@ -0,0 +1,190 @@
+/*
+ * 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.message.filtering;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.security.AccessController;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Spliterator;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.glassfish.jersey.internal.inject.InjectionManager;
+import org.glassfish.jersey.internal.inject.Providers;
+import org.glassfish.jersey.internal.util.ReflectionHelper;
+import org.glassfish.jersey.message.filtering.spi.EntityGraph;
+import org.glassfish.jersey.message.filtering.spi.EntityGraphProvider;
+import org.glassfish.jersey.message.filtering.spi.EntityInspector;
+import org.glassfish.jersey.message.filtering.spi.EntityProcessor;
+import org.glassfish.jersey.message.filtering.spi.EntityProcessorContext;
+import org.glassfish.jersey.message.filtering.spi.FilteringHelper;
+import org.glassfish.jersey.model.internal.RankedComparator;
+
+/**
+ * Class responsible for inspecting entity classes. This class invokes all available {@link EntityProcessor entity processors} in
+ * different {@link EntityProcessorContext contexts}.
+ *
+ * @author Michal Gajdos
+ */
+@Singleton
+final class EntityInspectorImpl implements EntityInspector {
+
+    private final List<EntityProcessor> entityProcessors;
+
+    @Inject
+    private EntityGraphProvider graphProvider;
+
+    /**
+     * Constructor expecting {@link InjectionManager} to be injected.
+     *
+     * @param injectionManager injection manager to be injected.
+     */
+    @Inject
+    public EntityInspectorImpl(final InjectionManager injectionManager) {
+        Spliterator<EntityProcessor> entities =
+                Providers.getAllProviders(injectionManager, EntityProcessor.class, new RankedComparator<>()).spliterator();
+        this.entityProcessors = StreamSupport.stream(entities, false).collect(Collectors.toList());
+    }
+
+    @Override
+    public void inspect(final Class<?> entityClass, final boolean forWriter) {
+        if (!graphProvider.containsEntityGraph(entityClass, forWriter)) {
+            final EntityGraph graph = graphProvider.getOrCreateEntityGraph(entityClass, forWriter);
+            final Set<Class<?>> inspect = new HashSet<>();
+
+            // Class.
+            if (!inspectEntityClass(entityClass, graph, forWriter)) {
+                // Properties.
+                final Map<String, Method> unmatchedAccessors = inspectEntityProperties(entityClass, graph, inspect, forWriter);
+
+                // Setters/Getters without fields.
+                inspectStandaloneAccessors(unmatchedAccessors, graph, forWriter);
+
+                // Inspect new classes.
+                for (final Class<?> clazz : inspect) {
+                    inspect(clazz, forWriter);
+                }
+            }
+        }
+    }
+
+    /**
+     * Invoke available {@link EntityProcessor}s on given entity class.
+     *
+     * @param entityClass entity class to be examined.
+     * @param graph entity graph to be modified by examination.
+     * @param forWriter flag determining whether the class should be examined for reader or writer.
+     * @return {@code true} if the inspecting should be roll-backed, {@code false} otherwise.
+     */
+    private boolean inspectEntityClass(final Class<?> entityClass, final EntityGraph graph, final boolean forWriter) {
+        final EntityProcessorContextImpl context = new EntityProcessorContextImpl(
+                forWriter ? EntityProcessorContext.Type.CLASS_WRITER : EntityProcessorContext.Type.CLASS_READER,
+                entityClass, graph);
+
+        for (final EntityProcessor processor : entityProcessors) {
+            final EntityProcessor.Result result = processor.process(context);
+
+            if (EntityProcessor.Result.ROLLBACK == result) {
+                graphProvider.getOrCreateEmptyEntityGraph(entityClass, false);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Invoke available {@link EntityProcessor}s on fields of given entity class. Method returns a map ({@code fieldName},
+     * {@code method}) of unprocessed property accessors (getters/setters) and fills {@code inspect} set with entity classes
+     * that should be further processed.
+     *
+     * @param entityClass entity class to obtain properties to be examined.
+     * @param graph entity graph to be modified by examination.
+     * @param inspect non-null set of classes to-be-examined.
+     * @param forWriter flag determining whether the class should be examined for reader or writer.
+     * @return map of unprocessed property accessors.
+     */
+    private Map<String, Method> inspectEntityProperties(final Class<?> entityClass, final EntityGraph graph,
+                                                        final Set<Class<?>> inspect, final boolean forWriter) {
+        final Field[] fields = AccessController.doPrivileged(ReflectionHelper.getAllFieldsPA(entityClass));
+        final Map<String, Method> methods = FilteringHelper.getPropertyMethods(entityClass, forWriter);
+
+        for (final Field field : fields) {
+            // Ignore static fields.
+            if (Modifier.isStatic(field.getModifiers())) {
+                continue;
+            }
+
+            final String name = field.getName();
+            final Class<?> clazz = FilteringHelper.getEntityClass(field.getGenericType());
+            final Method method = methods.remove(name);
+
+            final EntityProcessorContextImpl context = new EntityProcessorContextImpl(
+                    forWriter ? EntityProcessorContext.Type.PROPERTY_WRITER : EntityProcessorContext.Type.PROPERTY_READER,
+                    field, method, graph);
+
+            boolean rollback = false;
+            for (final EntityProcessor processor : entityProcessors) {
+                final EntityProcessor.Result result = processor.process(context);
+
+                if (EntityProcessor.Result.ROLLBACK == result) {
+                    rollback = true;
+                    graph.remove(name);
+                    break;
+                }
+            }
+
+            if (!rollback && FilteringHelper.filterableEntityClass(clazz)) {
+                inspect.add(clazz);
+            }
+        }
+
+        return methods;
+    }
+
+    /**
+     * Invoke available {@link EntityProcessor}s on accessors (getter/setter) that has no match in classes' fields.
+     *
+     * @param unprocessedAccessors map of unprocessed accessors.
+     * @param graph entity graph to be modified by examination.
+     * @param forWriter flag determining whether the class should be examined for reader or writer.
+     */
+    private void inspectStandaloneAccessors(final Map<String, Method> unprocessedAccessors, final EntityGraph graph,
+                                            final boolean forWriter) {
+        for (final Map.Entry<String, Method> entry : unprocessedAccessors.entrySet()) {
+            final EntityProcessorContextImpl context = new EntityProcessorContextImpl(
+                    forWriter ? EntityProcessorContext.Type.METHOD_WRITER : EntityProcessorContext.Type.METHOD_READER,
+                    entry.getValue(), graph);
+
+            for (final EntityProcessor processor : entityProcessors) {
+                final EntityProcessor.Result result = processor.process(context);
+
+                if (EntityProcessor.Result.ROLLBACK == result) {
+                    graph.remove(entry.getKey());
+                    break;
+                }
+            }
+        }
+    }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityProcessorContextImpl.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityProcessorContextImpl.java
new file mode 100644
index 0000000..a47160e
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/EntityProcessorContextImpl.java
@@ -0,0 +1,118 @@
+/*
+ * 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.message.filtering;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+import org.glassfish.jersey.message.filtering.spi.EntityGraph;
+import org.glassfish.jersey.message.filtering.spi.EntityProcessorContext;
+
+/**
+ * Default {@link EntityProcessorContext entity processor context} implementation.
+ *
+ * @author Michal Gajdos
+ */
+final class EntityProcessorContextImpl implements EntityProcessorContext {
+
+    private final Type type;
+
+    private final Class<?> clazz;
+
+    private final Field field;
+
+    private final Method method;
+
+    private final EntityGraph graph;
+
+    /**
+     * Create entity processor context for processing entity classes.
+     *
+     * @param type {@link Type#CLASS_READER} or {@link Type#CLASS_WRITER}.
+     * @param clazz entity class.
+     * @param graph entity-filtering graph associated with entity class.
+     */
+    public EntityProcessorContextImpl(final Type type, final Class<?> clazz, final EntityGraph graph) {
+        this(type, clazz, null, null, graph);
+    }
+
+    /**
+     * Create entity processor context for processing entity properties.
+     *
+     * @param type {@link Type#PROPERTY_READER} or {@link Type#PROPERTY_WRITER}.
+     * @param field entity property field.
+     * @param method entity property accessor.
+     * @param graph entity-filtering graph associated with entity class.
+     */
+    public EntityProcessorContextImpl(final Type type, final Field field, final Method method, final EntityGraph graph) {
+        this(type, null, field, method, graph);
+    }
+
+    /**
+     * Create entity processor context for processing entity accessors.
+     *
+     * @param type {@link Type#METHOD_READER} or {@link Type#METHOD_WRITER}.
+     * @param method entity property accessor.
+     * @param graph entity-filtering graph associated with entity class.
+     */
+    public EntityProcessorContextImpl(final Type type, final Method method, final EntityGraph graph) {
+        this(type, null, null, method, graph);
+    }
+
+    /**
+     * Create entity processor context for processing entity accessors.
+     *
+     * @param type type on entity processor context.
+     * @param clazz entity class.
+     * @param field entity property field.
+     * @param method entity property method.
+     * @param graph entity-filtering graph associated with entity class.
+     */
+    public EntityProcessorContextImpl(final Type type, final Class<?> clazz, final Field field, final Method method,
+                                      final EntityGraph graph) {
+        this.type = type;
+        this.clazz = clazz;
+        this.field = field;
+        this.method = method;
+        this.graph = graph;
+    }
+
+    @Override
+    public Type getType() {
+        return type;
+    }
+
+    @Override
+    public Class<?> getEntityClass() {
+        return clazz;
+    }
+
+    @Override
+    public Field getField() {
+        return field;
+    }
+
+    @Override
+    public Method getMethod() {
+        return method;
+    }
+
+    @Override
+    public EntityGraph getEntityGraph() {
+        return graph;
+    }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/ObjectGraphImpl.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/ObjectGraphImpl.java
new file mode 100644
index 0000000..798a74f
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/ObjectGraphImpl.java
@@ -0,0 +1,122 @@
+/*
+ * 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.message.filtering;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.glassfish.jersey.internal.util.collection.Views;
+import org.glassfish.jersey.message.filtering.spi.EntityGraph;
+import org.glassfish.jersey.message.filtering.spi.ObjectGraph;
+import org.glassfish.jersey.message.filtering.spi.ScopeProvider;
+
+/**
+ * Default implementation of {@link ObjectGraph}.
+ *
+ * @author Michal Gajdos
+ */
+final class ObjectGraphImpl implements ObjectGraph {
+
+    private final Set<String> filteringScopes;
+
+    private final Map<Class<?>, EntityGraph> classToGraph;
+    private final EntityGraph graph;
+
+    private Set<String> fields;
+    private Map<String, ObjectGraph> subgraphs;
+
+    ObjectGraphImpl(final Map<Class<?>, EntityGraph> classToGraph, final EntityGraph graph, final Set<String> filteringScopes) {
+        this.filteringScopes = filteringScopes;
+
+        this.classToGraph = classToGraph;
+        this.graph = graph;
+    }
+
+    @Override
+    public Class<?> getEntityClass() {
+        return graph.getEntityClass();
+    }
+
+    @Override
+    public Set<String> getFields() {
+        return getFields(null);
+    }
+
+    @Override
+    public Set<String> getFields(final String parent) {
+        final Set<String> childFilteringScopes = getFilteringScopes(parent);
+        if (fields == null) {
+            fields = graph.getFields(
+                    Views.setUnionView(
+                            childFilteringScopes,
+                            Collections.singleton(ScopeProvider.DEFAULT_SCOPE)));
+        }
+        return fields;
+    }
+
+    @Override
+    public Map<String, ObjectGraph> getSubgraphs() {
+        return getSubgraphs(null);
+    }
+
+    @Override
+    public Map<String, ObjectGraph> getSubgraphs(final String parent) {
+        final Set<String> childFilteringScopes = getFilteringScopes(parent);
+
+        if (subgraphs == null) {
+            final Map<String, Class<?>> contextSubgraphs = graph.getSubgraphs(childFilteringScopes);
+            contextSubgraphs.putAll(graph.getSubgraphs(ScopeProvider.DEFAULT_SCOPE));
+
+            subgraphs = Views.mapView(contextSubgraphs, new Function<Class<?>, ObjectGraph>() {
+
+                @Override
+                public ObjectGraph apply(final Class<?> clazz) {
+                    final EntityGraph entityGraph = classToGraph.get(clazz);
+
+                    return entityGraph == null
+                        ? new EmptyObjectGraph(clazz)
+                        : new ObjectGraphImpl(classToGraph, entityGraph, filteringScopes);
+                }
+            });
+        }
+        return subgraphs;
+    }
+
+    private Set<String> getFilteringScopes(final String parent) {
+        Set<String> childFilteringScopes = new HashSet<>();
+        if (filteringScopes.contains(SelectableScopeResolver.DEFAULT_SCOPE) || parent == null) {
+            childFilteringScopes = filteringScopes;
+        } else {
+            for (final String filteringScope : filteringScopes) {
+                final Pattern p = Pattern.compile(SelectableScopeResolver.PREFIX + parent + "\\.(\\w+)(\\.\\w+)*$");
+                final Matcher m = p.matcher(filteringScope);
+                if (m.matches()) {
+                    childFilteringScopes.add(SelectableScopeResolver.PREFIX + m.group(1));
+                } else {
+                    childFilteringScopes.add(filteringScope);
+                }
+            }
+        }
+        return childFilteringScopes;
+    }
+
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/ObjectGraphProvider.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/ObjectGraphProvider.java
new file mode 100644
index 0000000..5f911e3
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/ObjectGraphProvider.java
@@ -0,0 +1,35 @@
+/*
+ * 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.message.filtering;
+
+import org.glassfish.jersey.message.filtering.spi.AbstractObjectProvider;
+import org.glassfish.jersey.message.filtering.spi.ObjectGraph;
+
+/**
+ * {@link org.glassfish.jersey.message.filtering.spi.ObjectProvider Object provider} and
+ * {@link org.glassfish.jersey.message.filtering.spi.ObjectGraphTransformer object graph transformer} implementation for
+ * {@link ObjectGraph object graph}. These providers are present in runtime by default.
+ *
+ * @author Michal Gajdos
+ */
+final class ObjectGraphProvider extends AbstractObjectProvider<ObjectGraph> {
+
+    @Override
+    public ObjectGraph transform(final ObjectGraph graph) {
+        return graph;
+    }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SecurityAnnotations.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SecurityAnnotations.java
new file mode 100644
index 0000000..55f55fe
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SecurityAnnotations.java
@@ -0,0 +1,106 @@
+/*
+ * 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.message.filtering;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.security.DenyAll;
+import javax.annotation.security.PermitAll;
+import javax.annotation.security.RolesAllowed;
+
+import org.glassfish.jersey.internal.inject.AnnotationLiteral;
+
+/**
+ * Convenience utility methods for creating instances of security annotations.
+ *
+ * @author Michal Gajdos
+ */
+public final class SecurityAnnotations {
+
+    /**
+     * Create {@link RolesAllowed} annotation implementation for given set of roles.
+     *
+     * @param roles roles to be part of the annotation.
+     * @return annotation implementation.
+     */
+    public static RolesAllowed rolesAllowed(final String... roles) {
+        final List<String> list = new ArrayList<>(roles.length);
+        for (final String role : roles) {
+            if (role != null) {
+                list.add(role);
+            }
+        }
+        return new RolesAllowedImpl(list.toArray(new String[list.size()]));
+    }
+
+    /**
+     * Create {@link PermitAll} annotation implementation.
+     *
+     * @return annotation implementation.
+     */
+    public static PermitAll permitAll() {
+        return new PermitAllImpl();
+    }
+
+    /**
+     * Create {@link DenyAll} annotation implementation.
+     *
+     * @return annotation implementation.
+     */
+    public static DenyAll denyAll() {
+        return new DenyAllImpl();
+    }
+
+    /**
+     * DenyAll annotation implementation.
+     */
+    @SuppressWarnings("ClassExplicitlyAnnotation")
+    private static final class RolesAllowedImpl extends AnnotationLiteral<RolesAllowed> implements RolesAllowed {
+
+        private final String[] roles;
+
+        private RolesAllowedImpl(final String[] roles) {
+            this.roles = roles;
+        }
+
+        @Override
+        public String[] value() {
+            return roles;
+        }
+    }
+
+    /**
+     * DenyAll annotation implementation.
+     */
+    @SuppressWarnings("ClassExplicitlyAnnotation")
+    private static final class DenyAllImpl extends AnnotationLiteral<DenyAll> implements DenyAll {
+    }
+
+    /**
+     * PermitAll annotation implementation.
+     */
+    @SuppressWarnings("ClassExplicitlyAnnotation")
+    private static class PermitAllImpl extends AnnotationLiteral<PermitAll> implements PermitAll {
+    }
+
+    /**
+     * Prevent instantiation.
+     */
+    private SecurityAnnotations() {
+    }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SecurityEntityFilteringFeature.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SecurityEntityFilteringFeature.java
new file mode 100644
index 0000000..729917c
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SecurityEntityFilteringFeature.java
@@ -0,0 +1,86 @@
+/*
+ * 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.message.filtering;
+
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.Feature;
+import javax.ws.rs.core.FeatureContext;
+
+import org.glassfish.jersey.server.filter.RolesAllowedDynamicFeature;
+
+/**
+ * {@link Feature} used to add support for Java Security annotations (<code>javax.annotation.security</code>) for Entity Data
+ * Filtering feature.
+ * <p>
+ * Supported annotations are:
+ * <ul>
+ * <li>{@link javax.annotation.security.PermitAll}</li>
+ * <li>{@link javax.annotation.security.RolesAllowed}</li>
+ * <li>{@link javax.annotation.security.DenyAll}</li>
+ * </ul>
+ * </p>
+ * <p>
+ * It is sufficient to annotate only property accessors of an entity without annotating resource method / resource class although
+ * it is not recommended.
+ * </p>
+ * Note: This feature also registers the {@link EntityFilteringFeature}.
+ *
+ * @author Michal Gajdos
+ * @see org.glassfish.jersey.message.filtering.EntityFilteringFeature
+ */
+public final class SecurityEntityFilteringFeature implements Feature {
+
+    @Override
+    public boolean configure(final FeatureContext context) {
+        final Configuration config = context.getConfiguration();
+
+        if (!config.isRegistered(SecurityEntityProcessor.class)) {
+            // RolesAllowed feature.
+            if (!config.isRegistered(RolesAllowedDynamicFeature.class)) {
+                context.register(RolesAllowedDynamicFeature.class);
+            }
+
+            // Binder (FilteringObjectProvider/FilteringGraphTransformer).
+            if (!config.isRegistered(EntityFilteringBinder.class)) {
+                context.register(new EntityFilteringBinder());
+            }
+
+            // Entity Processors.
+            context.register(SecurityEntityProcessor.class);
+            if (!config.isRegistered(DefaultEntityProcessor.class)) {
+                context.register(DefaultEntityProcessor.class);
+            }
+
+            // Scope Providers.
+            context.register(SecurityScopeResolver.class);
+            if (RuntimeType.SERVER.equals(config.getRuntimeType())) {
+                context.register(SecurityServerScopeResolver.class);
+            }
+
+            // Scope Resolver.
+            if (RuntimeType.SERVER == config.getRuntimeType()) {
+                context.register(SecurityServerScopeProvider.class);
+            } else {
+                context.register(CommonScopeProvider.class);
+            }
+
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SecurityEntityProcessor.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SecurityEntityProcessor.java
new file mode 100644
index 0000000..7971b67
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SecurityEntityProcessor.java
@@ -0,0 +1,59 @@
+/*
+ * 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.message.filtering;
+
+import java.lang.annotation.Annotation;
+import java.util.Set;
+
+import javax.annotation.Priority;
+import javax.inject.Singleton;
+
+import org.glassfish.jersey.message.filtering.spi.AbstractEntityProcessor;
+import org.glassfish.jersey.message.filtering.spi.EntityGraph;
+import org.glassfish.jersey.message.filtering.spi.EntityProcessor;
+
+/**
+ * {@link EntityProcessor Entity processor} handling security annotations on model entity classes.
+ *
+ * @author Michal Gajdos
+ */
+@Singleton
+@Priority(Integer.MAX_VALUE - 3000)
+final class SecurityEntityProcessor extends AbstractEntityProcessor {
+
+    @Override
+    protected Result process(final String fieldName, final Class<?> fieldClass, final Annotation[] fieldAnnotations,
+                             final Annotation[] annotations, final EntityGraph graph) {
+        if (annotations.length > 0) {
+            final Set<String> filteringScopes = SecurityHelper.getFilteringScopes(annotations);
+
+            if (filteringScopes == null) {
+                return EntityProcessor.Result.ROLLBACK;
+            } else if (!filteringScopes.isEmpty()) {
+                if (fieldName != null) {
+                    // For field.
+                    addFilteringScopes(fieldName, fieldClass, filteringScopes, graph);
+                } else {
+                    // For entire class into graph.
+                    addGlobalScopes(filteringScopes, graph);
+                }
+            }
+        }
+
+        return EntityProcessor.Result.APPLY;
+    }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SecurityHelper.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SecurityHelper.java
new file mode 100644
index 0000000..69fc968
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SecurityHelper.java
@@ -0,0 +1,124 @@
+/*
+ * 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.message.filtering;
+
+import java.lang.annotation.Annotation;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.ws.rs.core.SecurityContext;
+
+import javax.annotation.security.DenyAll;
+import javax.annotation.security.PermitAll;
+import javax.annotation.security.RolesAllowed;
+
+import org.glassfish.jersey.message.filtering.spi.FilteringHelper;
+
+/**
+ * Utility methods for security Entity Data Filtering.
+ *
+ * @author Michal Gajdos
+ */
+final class SecurityHelper {
+
+    private static final Set<String> roles = new HashSet<>();
+
+    /**
+     * Get entity-filtering scopes of security annotations present among given annotations.
+     * <p>
+     * A scope look like:
+     * <ul>
+     * <li>&lt;fully qualified annotation class name&gt;, or</li>
+     * <li>&lt;fully qualified annotation class name&gt;_&lt;role&gt;</li>
+     * </ul>
+     * </p>
+     *
+     * @param annotations a list of annotations (doesn't need to contain only security annotations)
+     * @return a set of entity-filtering scopes.
+     */
+    static Set<String> getFilteringScopes(final Annotation[] annotations) {
+        return getFilteringScopes(null, annotations);
+    }
+
+    /**
+     * Get entity-filtering scopes of security annotations present among given annotations with respect to given
+     * {@link SecurityContext}. Resulting set contains only scopes that pass the security context check.
+     * <p>
+     * A scope look like:
+     * <ul>
+     * <li>&lt;fully qualified annotation class name&gt;, or</li>
+     * <li>&lt;fully qualified annotation class name&gt;_&lt;role&gt;</li>
+     * </ul>
+     * </p>
+     *
+     * @param securityContext security context to check whether a user is in specified logical role.
+     * @param annotations a list of annotations (doesn't need to contain only security annotations)
+     * @return a set of entity-filtering scopes.
+     */
+    static Set<String> getFilteringScopes(final SecurityContext securityContext, final Annotation[] annotations) {
+        if (annotations.length == 0) {
+            return Collections.emptySet();
+        }
+
+        for (final Annotation annotation : annotations) {
+            if (annotation instanceof RolesAllowed) {
+                final Set<String> bindings = new HashSet<>();
+
+                for (final String role : ((RolesAllowed) annotation).value()) {
+                    if (securityContext == null || securityContext.isUserInRole(role)) {
+                        bindings.add(getRolesAllowedScope(role));
+                    }
+                }
+
+                return bindings;
+            } else if (annotation instanceof PermitAll) {
+                return FilteringHelper.getDefaultFilteringScope();
+            } else if (annotation instanceof DenyAll) {
+                return null;
+            }
+        }
+
+        return Collections.emptySet();
+    }
+
+    /**
+     * Get entity-filtering scope for {@link RolesAllowed}s role.
+     *
+     * @param role role to retrieve entity-filtering scope for.
+     * @return entity-filtering scope.
+     */
+    static String getRolesAllowedScope(final String role) {
+        roles.add(role);
+        return RolesAllowed.class.getName() + "_" + role;
+    }
+
+    /**
+     * Get authorization roles that has been derived from examining entity classes.
+     *
+     * @return already processed authorization roles.
+     */
+    static Set<String> getProcessedRoles() {
+        return roles;
+    }
+
+    /**
+     * Prevent instantiation.
+     */
+    private SecurityHelper() {
+    }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SecurityScopeResolver.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SecurityScopeResolver.java
new file mode 100644
index 0000000..896d2e3
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SecurityScopeResolver.java
@@ -0,0 +1,38 @@
+/*
+ * 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.message.filtering;
+
+import java.lang.annotation.Annotation;
+import java.util.Set;
+
+import javax.inject.Singleton;
+
+import org.glassfish.jersey.message.filtering.spi.ScopeResolver;
+
+/**
+ * {@link ScopeResolver scope provider} resolving entity-filtering scopes from security annotations.
+ *
+ * @author Michal Gajdos
+ */
+@Singleton
+final class SecurityScopeResolver implements ScopeResolver {
+
+    @Override
+    public Set<String> resolve(final Annotation[] annotations) {
+        return SecurityHelper.getFilteringScopes(annotations);
+    }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SecurityServerScopeProvider.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SecurityServerScopeProvider.java
new file mode 100644
index 0000000..6be0444
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SecurityServerScopeProvider.java
@@ -0,0 +1,67 @@
+/*
+ * 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.message.filtering;
+
+import java.lang.annotation.Annotation;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.ws.rs.ConstrainedTo;
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.SecurityContext;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.glassfish.jersey.internal.inject.InjectionManager;
+
+/**
+ * @author Michal Gajdos
+ */
+@Singleton
+@ConstrainedTo(RuntimeType.SERVER)
+final class SecurityServerScopeProvider extends ServerScopeProvider {
+
+    @Context
+    private SecurityContext securityContext;
+
+    @Inject
+    public SecurityServerScopeProvider(final Configuration config, final InjectionManager injectionManager) {
+        super(config, injectionManager);
+    }
+
+    @Override
+    public Set<String> getFilteringScopes(final Annotation[] entityAnnotations, final boolean defaultIfNotFound) {
+        Set<String> filteringScope = super.getFilteringScopes(entityAnnotations, false);
+
+        if (filteringScope.isEmpty()) {
+            filteringScope = new HashSet<>();
+
+            // Get all roles collected from entities and check with current security context.
+            for (final String role : SecurityHelper.getProcessedRoles()) {
+                if (securityContext.isUserInRole(role)) {
+                    filteringScope.add(SecurityHelper.getRolesAllowedScope(role));
+                }
+            }
+        }
+
+        // Use default scope if not in other scope.
+        return returnFilteringScopes(filteringScope, defaultIfNotFound);
+    }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SecurityServerScopeResolver.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SecurityServerScopeResolver.java
new file mode 100644
index 0000000..0504de5
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SecurityServerScopeResolver.java
@@ -0,0 +1,51 @@
+/*
+ * 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.message.filtering;
+
+import java.lang.annotation.Annotation;
+import java.util.Set;
+
+import javax.ws.rs.ConstrainedTo;
+import javax.ws.rs.Priorities;
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.SecurityContext;
+
+import javax.annotation.Priority;
+import javax.inject.Singleton;
+
+import org.glassfish.jersey.message.filtering.spi.ScopeResolver;
+
+/**
+ * Server-side {@link ScopeResolver scope provider} resolving entity-filtering scopes from security annotations
+ * with respect to user's roles defined in {@link SecurityContext}.
+ *
+ * @author Michal Gajdos
+ */
+@Singleton
+@Priority(Priorities.ENTITY_CODER + 100)
+@ConstrainedTo(RuntimeType.SERVER)
+final class SecurityServerScopeResolver implements ScopeResolver {
+
+    @Context
+    private SecurityContext securityContext;
+
+    @Override
+    public Set<String> resolve(final Annotation[] annotations) {
+        return SecurityHelper.getFilteringScopes(securityContext, annotations);
+    }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SelectableEntityFilteringFeature.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SelectableEntityFilteringFeature.java
new file mode 100644
index 0000000..e89bea1
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SelectableEntityFilteringFeature.java
@@ -0,0 +1,54 @@
+/*
+ * 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.message.filtering;
+
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.Feature;
+import javax.ws.rs.core.FeatureContext;
+
+/**
+ * {@link Feature} used to add support for custom query parameter filtering for
+ * Entity Data Filtering feature. </p> Note: This feature also registers the
+ * {@link EntityFilteringFeature}.
+ *
+ * @author Andy Pemberton (pembertona at gmail.com)
+ * @see org.glassfish.jersey.message.filtering.EntityFilteringFeature
+ */
+public final class SelectableEntityFilteringFeature implements Feature {
+
+    public static final String QUERY_PARAM_NAME = "jersey.config.entityFiltering.selectable.query";
+
+    @Override
+    public boolean configure(final FeatureContext context) {
+        final Configuration config = context.getConfiguration();
+
+        if (!config.isRegistered(SelectableEntityProcessor.class)) {
+
+            // register EntityFilteringFeature
+            if (!config.isRegistered(EntityFilteringFeature.class)) {
+                context.register(EntityFilteringFeature.class);
+            }
+            // Entity Processors.
+            context.register(SelectableEntityProcessor.class);
+            // Scope Resolver.
+            context.register(SelectableScopeResolver.class);
+
+            return true;
+        }
+        return true;
+    }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SelectableEntityProcessor.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SelectableEntityProcessor.java
new file mode 100644
index 0000000..0f478a6
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SelectableEntityProcessor.java
@@ -0,0 +1,52 @@
+/*
+ * 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.message.filtering;
+
+import java.lang.annotation.Annotation;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.annotation.Priority;
+import javax.inject.Singleton;
+
+import org.glassfish.jersey.message.filtering.spi.AbstractEntityProcessor;
+import org.glassfish.jersey.message.filtering.spi.EntityGraph;
+import org.glassfish.jersey.message.filtering.spi.EntityProcessor;
+
+@Singleton
+@Priority(Integer.MAX_VALUE - 5000)
+public class SelectableEntityProcessor extends AbstractEntityProcessor {
+
+    protected Result process(final String fieldName, final Class<?> fieldClass, final Annotation[] fieldAnnotations,
+                             final Annotation[] annotations, final EntityGraph graph) {
+
+        if (fieldName != null) {
+            final Set<String> scopes = new HashSet<>();
+
+            // add default selectable scope in case of none requested
+            scopes.add(SelectableScopeResolver.DEFAULT_SCOPE);
+
+            // add specific scope in case of specific request
+            scopes.add(SelectableScopeResolver.PREFIX + fieldName);
+
+            addFilteringScopes(fieldName, fieldClass, scopes, graph);
+        }
+
+        return EntityProcessor.Result.APPLY;
+    }
+
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SelectableScopeResolver.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SelectableScopeResolver.java
new file mode 100644
index 0000000..6e22afb
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/SelectableScopeResolver.java
@@ -0,0 +1,95 @@
+/*
+ * 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.message.filtering;
+
+import java.lang.annotation.Annotation;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.UriInfo;
+
+import javax.annotation.PostConstruct;
+import javax.inject.Singleton;
+
+import org.glassfish.jersey.internal.util.Tokenizer;
+import org.glassfish.jersey.message.filtering.spi.ScopeResolver;
+
+@Singleton
+public class SelectableScopeResolver implements ScopeResolver {
+
+    /**
+     * Prefix for all selectable scopes
+     */
+    public static final String PREFIX = SelectableScopeResolver.class.getName() + "_";
+
+    /**
+     * Scope used for selecting all fields, i.e.: when no filter is applied
+     */
+    public static final String DEFAULT_SCOPE = PREFIX + "*";
+
+    /**
+     * Query parameter name for selectable feature, set to default value
+     */
+    private static String SELECTABLE_PARAM_NAME = "select";
+
+    @Context
+    private Configuration configuration;
+
+    @Context
+    private UriInfo uriInfo;
+
+    @PostConstruct
+    private void init() {
+        final String paramName = (String) configuration.getProperty(SelectableEntityFilteringFeature.QUERY_PARAM_NAME);
+        SELECTABLE_PARAM_NAME = paramName != null ? paramName : SELECTABLE_PARAM_NAME;
+    }
+
+    @Override
+    public Set<String> resolve(final Annotation[] annotations) {
+        final Set<String> scopes = new HashSet<>();
+
+        final List<String> fields = uriInfo.getQueryParameters().get(SELECTABLE_PARAM_NAME);
+        if (fields != null && !fields.isEmpty()) {
+            for (final String field : fields) {
+                scopes.addAll(getScopesForField(field));
+            }
+        } else {
+            scopes.add(DEFAULT_SCOPE);
+        }
+        return scopes;
+    }
+
+    private Set<String> getScopesForField(final String fieldName) {
+        final Set<String> scopes = new HashSet<>();
+
+        // add specific scope in case of specific request
+        final String[] fields = Tokenizer.tokenize(fieldName, ",");
+        for (final String field : fields) {
+            final String[] subfields = Tokenizer.tokenize(field, ".");
+            // in case of nested path, add first level as stand-alone to ensure subgraph is added
+            scopes.add(SelectableScopeResolver.PREFIX + subfields[0]);
+            if (subfields.length > 1) {
+                scopes.add(SelectableScopeResolver.PREFIX + field);
+            }
+        }
+
+        return scopes;
+    }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/ServerScopeProvider.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/ServerScopeProvider.java
new file mode 100644
index 0000000..872317b
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/ServerScopeProvider.java
@@ -0,0 +1,127 @@
+/*
+ * 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.message.filtering;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ConcurrentMap;
+
+import javax.ws.rs.ConstrainedTo;
+import javax.ws.rs.Priorities;
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.core.Configuration;
+
+import javax.annotation.Priority;
+import javax.inject.Inject;
+import javax.inject.Provider;
+import javax.inject.Singleton;
+
+import org.glassfish.jersey.internal.inject.InjectionManager;
+import org.glassfish.jersey.internal.util.collection.DataStructures;
+import org.glassfish.jersey.server.ExtendedUriInfo;
+import org.glassfish.jersey.server.model.Invocable;
+import org.glassfish.jersey.server.model.ResourceMethod;
+
+/**
+ * Server-side implementation of {@link org.glassfish.jersey.message.filtering.spi.ScopeProvider scope provider}. In addition to
+ * {@link CommonScopeProvider base implementation} this class provides entity-filtering scopes by examining matched resource
+ * method and sub-resource locators. This examination comes into play only in case if entity-filtering scopes cannot be found in
+ * entity annotations or application configuration.
+ *
+ * @author Michal Gajdos
+ */
+@Singleton
+@Priority(Priorities.ENTITY_CODER + 200)
+@ConstrainedTo(RuntimeType.SERVER)
+class ServerScopeProvider extends CommonScopeProvider {
+
+    @Inject
+    private Provider<ExtendedUriInfo> uriInfoProvider;
+
+    private final ConcurrentMap<String, Set<String>> uriToContexts;
+
+    /**
+     * Create new server scope provider with injected {@link Configuration configuration} and
+     * {@link InjectionManager jersey injection manager}.
+     */
+    @Inject
+    public ServerScopeProvider(final Configuration config, final InjectionManager injectionManager) {
+        super(config, injectionManager);
+        this.uriToContexts = DataStructures.createConcurrentMap();
+    }
+
+    @Override
+    public Set<String> getFilteringScopes(final Annotation[] entityAnnotations, final boolean defaultIfNotFound) {
+        Set<String> filteringScope = super.getFilteringScopes(entityAnnotations, false);
+
+        if (filteringScope.isEmpty()) {
+            final ExtendedUriInfo uriInfo = uriInfoProvider.get();
+            final String path = uriInfo.getPath();
+
+            if (uriToContexts.containsKey(path)) {
+                return uriToContexts.get(path);
+            }
+
+            for (final ResourceMethod method : ServerScopeProvider.getMatchedMethods(uriInfo)) {
+                final Invocable invocable = method.getInvocable();
+
+                mergeFilteringScopes(filteringScope,
+                        getFilteringScopes(invocable.getHandlingMethod(), invocable.getHandler().getHandlerClass()));
+
+                if (!filteringScope.isEmpty()) {
+                    uriToContexts.putIfAbsent(path, filteringScope);
+                    return filteringScope;
+                }
+            }
+        }
+
+        // Use default scope if not in other scope.
+        return returnFilteringScopes(filteringScope, defaultIfNotFound);
+    }
+
+    /**
+     * Get entity-filtering scopes from examining annotations present on resource method and resource class.
+     *
+     * @param resourceMethod matched resource method to be examined.
+     * @param resourceClass matched resource class to be examined.
+     * @return entity-filtering scopes or an empty set if the scopes cannot be obtained.
+     */
+    protected Set<String> getFilteringScopes(final Method resourceMethod, final Class<?> resourceClass) {
+        // Method annotations first.
+        Set<String> scope = getFilteringScopes(resourceMethod.getAnnotations());
+
+        // Class annotations second.
+        if (scope.isEmpty()) {
+            scope = getFilteringScopes(resourceClass.getAnnotations());
+        }
+
+        return scope;
+    }
+
+    private static List<ResourceMethod> getMatchedMethods(final ExtendedUriInfo uriInfo) {
+        final List<ResourceMethod> matchedResourceLocators = uriInfo.getMatchedResourceLocators();
+        final List<ResourceMethod> methods = new ArrayList<>(1 + matchedResourceLocators.size());
+
+        methods.add(uriInfo.getMatchedResourceMethod());
+        methods.addAll(matchedResourceLocators);
+
+        return methods;
+    }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/package-info.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/package-info.java
new file mode 100644
index 0000000..7741b43
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/package-info.java
@@ -0,0 +1,34 @@
+/*
+ * 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
+ */
+
+/**
+ * Support for Entity Data Filtering in Jersey.
+ * <p/>
+ * To use Entity Data Filtering one of the provided {@link javax.ws.rs.core.Feature features} has to be registered in an
+ * application:
+ * <ul>
+ *     <li>{@link org.glassfish.jersey.message.filtering.EntityFilteringFeature} - adds support for entity-filtering
+ *     annotations based on {@link org.glassfish.jersey.message.filtering.EntityFiltering} meta-annotation.</li>
+ *     <li>{@link org.glassfish.jersey.message.filtering.SecurityEntityFilteringFeature} - add support for entity-filtering using
+ *     Java Security annotations (<code>javax.annotation.security</code>).</li>
+ * </ul>
+ * <p/>
+ * To define own entity-filtering annotations, refer to the {@link org.glassfish.jersey.message.filtering.EntityFiltering}
+ * meta-annotation.
+ *
+ * @since 2.3
+ */
+package org.glassfish.jersey.message.filtering;
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/AbstractEntityProcessor.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/AbstractEntityProcessor.java
new file mode 100644
index 0000000..ad86114
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/AbstractEntityProcessor.java
@@ -0,0 +1,124 @@
+/*
+ * 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.message.filtering.spi;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.Set;
+
+import org.glassfish.jersey.internal.util.ReflectionHelper;
+
+/**
+ * Common {@link EntityProcessor entity processor} supposed to be used as a base class for custom implementations. Provides
+ * convenient methods for adding entity-filtering scopes to {@link EntityGraph entity graph} as well as a common implementation
+ * of {@link #process(org.glassfish.jersey.message.filtering.spi.EntityProcessorContext)}.
+ *
+ * @author Michal Gajdos
+ */
+public abstract class AbstractEntityProcessor implements EntityProcessor {
+
+    @Override
+    public EntityProcessor.Result process(final EntityProcessorContext context) {
+        switch (context.getType()) {
+            case CLASS_READER:
+            case CLASS_WRITER:
+                return process(null, null, FilteringHelper.EMPTY_ANNOTATIONS, context.getEntityClass().getDeclaredAnnotations(),
+                        context.getEntityGraph());
+
+            case PROPERTY_READER:
+            case PROPERTY_WRITER:
+            case METHOD_READER:
+            case METHOD_WRITER:
+                final Field field = context.getField();
+                final Method method = context.getMethod();
+
+                final boolean isProperty = field != null;
+                String fieldName;
+                Type fieldType;
+
+                if (isProperty) {
+                    fieldName = field.getName();
+                    fieldType = field.getGenericType();
+                } else {
+                    fieldName = ReflectionHelper.getPropertyName(method);
+                    fieldType = ReflectionHelper.isGetter(method) ? method.getGenericReturnType() : method
+                            .getGenericParameterTypes()[0];
+                }
+
+                return process(fieldName, FilteringHelper.getEntityClass(fieldType), getAnnotations(field),
+                        getAnnotations(method), context.getEntityGraph());
+
+            default:
+                // NOOP.
+        }
+        return EntityProcessor.Result.SKIP;
+    }
+
+    private Annotation[] getAnnotations(final AccessibleObject accessibleObject) {
+        return accessibleObject == null ? FilteringHelper.EMPTY_ANNOTATIONS : accessibleObject.getDeclaredAnnotations();
+    }
+
+    /**
+     * Method is called from the default implementation of
+     * {@link #process(org.glassfish.jersey.message.filtering.spi.EntityProcessorContext)} and is supposed to be overridden by
+     * custom implementations of this class.
+     *
+     * @param fieldName name of the field (can be {@code null}).
+     * @param fieldClass class of the field (can be {@code null}).
+     * @param fieldAnnotations annotations associated with the field (cannot be {@code null}).
+     * @param annotations annotations associated with class/accessor (cannot be {@code null}).
+     * @param graph entity graph to be processed.
+     * @return result of the processing (default is {@link Result#SKIP}).
+     */
+    protected Result process(final String fieldName, final Class<?> fieldClass, final Annotation[] fieldAnnotations,
+                             final Annotation[] annotations, final EntityGraph graph) {
+        return Result.SKIP;
+    }
+
+    /**
+     * Add entity-filtering scopes of a field to an entity-graph. The method determines whether the field should be added as a
+     * simple field or a subgraph.
+     *
+     * @param field name of a field to be added to the graph.
+     * @param fieldClass class of the field.
+     * @param filteringScopes entity-filtering scopes the field will be associated with in the graph.
+     * @param graph entity graph the field will be added to.
+     */
+    protected final void addFilteringScopes(final String field, final Class<?> fieldClass, final Set<String> filteringScopes,
+                                            final EntityGraph graph) {
+        if (!filteringScopes.isEmpty()) {
+            if (FilteringHelper.filterableEntityClass(fieldClass)) {
+                graph.addSubgraph(field, fieldClass, filteringScopes);
+            } else {
+                graph.addField(field, filteringScopes);
+            }
+        }
+    }
+
+    /**
+     * Add entity-filtering scopes into given graph. This method should be called only in class-level context.
+     *
+     * @param filteringScopes entity-filtering scopes to be added to graph.
+     * @param graph entity graph to be enhanced by new scopes.
+     */
+    protected final void addGlobalScopes(final Set<String> filteringScopes, final EntityGraph graph) {
+        graph.addFilteringScopes(filteringScopes);
+    }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/AbstractObjectProvider.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/AbstractObjectProvider.java
new file mode 100644
index 0000000..94118cd
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/AbstractObjectProvider.java
@@ -0,0 +1,184 @@
+/*
+ * 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.message.filtering.spi;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Proxy;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.inject.Inject;
+
+import org.glassfish.jersey.internal.guava.Cache;
+import org.glassfish.jersey.internal.guava.CacheBuilder;
+
+/**
+ * Common implementation of {@link ObjectProvider object provider} and {@link ObjectGraphTransformer object graph transformer}.
+ * <p>
+ * Extensions of this class must provide a type of entity-filtering object (via generic type parameter) the requesting provider
+ * (e.g. message body worker) is familiar with and an implementation of
+ * {@link ObjectGraphTransformer#transform(ObjectGraph)} method for this type.
+ * </p>
+ *
+ * @param <T> representation of entity data filtering requested by provider.
+ * @author Michal Gajdos
+ */
+public abstract class AbstractObjectProvider<T> implements ObjectProvider<T>, ObjectGraphTransformer<T> {
+
+    private static final int PROVIDER_CACHE_SIZE = 1000;
+
+    private final Cache<EntityContext, T> filteringObjects = CacheBuilder.newBuilder().maximumSize(PROVIDER_CACHE_SIZE).build();
+
+    @Inject
+    private ScopeProvider scopeProvider;
+
+    @Inject
+    private EntityInspector entityInspector;
+
+    @Inject
+    private EntityGraphProvider graphProvider;
+
+    @Override
+    public final T getFilteringObject(final Type genericType, final boolean forWriter, final Annotation... annotations) {
+        return getFilteringObject(FilteringHelper.getEntityClass(genericType), forWriter, annotations);
+    }
+
+    private T getFilteringObject(final Class<?> entityClass, final boolean forWriter, final Annotation... annotations) {
+        if (FilteringHelper.filterableEntityClass(entityClass)) {
+            // Inspect.
+            entityInspector.inspect(entityClass, forWriter);
+
+            // Obtain runtime/resource scope.
+            final Set<String> filteringScope = scopeProvider.getFilteringScopes(getEntityAnnotations(annotations), true);
+
+            // Look into the cache.
+            final EntityContext entityContext = new EntityContext(entityClass, filteringScope);
+            T filteringObject = filteringObjects.getIfPresent(entityContext);
+
+            // Create new if not available.
+            if (filteringObject == null) {
+                filteringObject = createFilteringObject(entityClass, filteringScope, forWriter);
+                filteringObjects.put(entityContext, filteringObject);
+            }
+
+            return filteringObject;
+        }
+        return null;
+    }
+
+    /**
+     * Get entity annotations passed to request/response. This method filters annotations that are proxy instances (proxy
+     * annotations are taken from resource method and passed in this list).
+     *
+     * @param annotations annotations obtained from provider.
+     * @return annoations passed to request/response.
+     */
+    private Annotation[] getEntityAnnotations(final Annotation[] annotations) {
+        final ArrayList<Annotation> entityAnnotations = new ArrayList<>();
+
+        for (final Annotation annotation : annotations) {
+            if (!(annotation instanceof Proxy)) {
+                entityAnnotations.add(annotation);
+            }
+        }
+
+        return entityAnnotations.toArray(new Annotation[entityAnnotations.size()]);
+    }
+
+    /**
+     * Create entity-filtering object after this object has not been found in the cache.
+     *
+     * @param entityClass     entity class the entity-filtering object should be created for.
+     * @param filteringScopes entity-filtering scopes to create the entity-filtering object for.
+     * @param forWriter       flag determining whether the class should be examined for reader or writer.
+     * @return entity-filtering object.
+     */
+    private T createFilteringObject(final Class<?> entityClass, final Set<String> filteringScopes, final boolean forWriter) {
+        // Obtain the filtering object.
+        return transform(graphProvider.createObjectGraph(entityClass, filteringScopes, forWriter));
+    }
+
+    /**
+     * A helper method for a creation of an immutable set based on a provided set together with a given item.
+     *
+     * @param set  The set to create the immutable set from.
+     * @param item The item to add to the set before it's made immutable.
+     * @return The immutable set from given set and item.
+     */
+    protected Set<String> immutableSetOf(final Set<String> set, final String item) {
+        final Set<String> duplicate = new HashSet<>(set);
+        duplicate.add(item);
+        return Collections.unmodifiableSet(duplicate);
+    }
+
+    /**
+     * Creates a string identifier of a sub-graph.
+     *
+     * @param parent     The parent class.
+     * @param field      The field name.
+     * @param fieldClass The class of the field.
+     * @return The string identifier of the sub-graph.
+     */
+    protected String subgraphIdentifier(final Class<?> parent, final String field, final Class<?> fieldClass) {
+        return parent.getName() + "_" + field + "_" + fieldClass.getName();
+    }
+
+    /**
+     * Class to be used as a key in cache ({@code EntityContext} -&gt; filtering object) when processing similar requests.
+     */
+    private static final class EntityContext {
+
+        private final Class<?> entityClass;
+
+        private final Set<String> filteringContext;
+
+        /**
+         * Create entity context class for given entity class and set of entity-filtering scopes.
+         *
+         * @param entityClass     entity class.
+         * @param filteringScopes entity-filtering scopes.
+         */
+        private EntityContext(final Class<?> entityClass, final Set<String> filteringScopes) {
+            this.entityClass = entityClass;
+            this.filteringContext = filteringScopes;
+        }
+
+        @Override
+        public boolean equals(final Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (!(o instanceof EntityContext)) {
+                return false;
+            }
+
+            final EntityContext that = (EntityContext) o;
+
+            return entityClass.equals(that.entityClass) && filteringContext.equals(that.filteringContext);
+        }
+
+        @Override
+        public int hashCode() {
+            int result = entityClass.hashCode();
+            result = 47 * result + filteringContext.hashCode();
+            return result;
+        }
+    }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/EntityGraph.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/EntityGraph.java
new file mode 100644
index 0000000..6dc95e1
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/EntityGraph.java
@@ -0,0 +1,198 @@
+/*
+ * 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.message.filtering.spi;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Class available to {@link EntityProcessor entity-filtering processors} providing means to add/remove entity-filtering scopes
+ * (e.g. based on annotations) for entity members.
+ * <p/>
+ * Differences between this class and {@link ObjectGraph object graph}:
+ * <ul>
+ * <li>{@code EntityGraph} can be modified, {@code ObjectGraph} is read-only.</li>
+ * <li>{@code EntityGraph} contains information about all entity-filtering scopes found during inspecting an entity class,
+ * {@code ObjectGraph} provides information about entity to create a filtering object for a subset of these scopes
+ * (which are determined from the current context).</li>
+ * </ul>
+ * <p/>
+ * Note: Definition of entity-filtering scopes can be found in {@link ScopeResolver}.
+ *
+ * @author Michal Gajdos
+ * @see ScopeResolver
+ */
+public interface EntityGraph {
+
+    /**
+     * Add a field into this graph for all existing entity-filtering scopes.
+     *
+     * @param fieldName name of the field to be added.
+     * @return an entity-filtering graph instance.
+     */
+    public EntityGraph addField(final String fieldName);
+
+    /**
+     * Add a field into this graph for given list of entity-filtering scopes.
+     *
+     * @param fieldName name of the field to be added.
+     * @param filteringScopes entity-filtering scopes for the field.
+     * @return an entity-filtering graph instance.
+     */
+    public EntityGraph addField(final String fieldName, final String... filteringScopes);
+
+    /**
+     * Add a field into this graph for given set of entity-filtering scopes.
+     *
+     * @param fieldName name of the field to be added.
+     * @param filteringScopes entity-filtering scopes for the field.
+     * @return an entity-filtering graph instance.
+     */
+    public EntityGraph addField(final String fieldName, final Set<String> filteringScopes);
+
+    /**
+     * Add a subgraph into this graph for all existing entity-filtering scopes.
+     *
+     * @param fieldName name of the subgraph field to be added.
+     * @param fieldClass entity class representing the subgraph.
+     * @return an entity-filtering graph instance.
+     */
+    public EntityGraph addSubgraph(final String fieldName, final Class<?> fieldClass);
+
+    /**
+     * Add a subgraph into this graph for given list of entity-filtering scopes.
+     *
+     * @param fieldName name of the subgraph field to be added.
+     * @param fieldClass entity class representing the subgraph.
+     * @param filteringScopes entity-filtering scopes for the subgraph.
+     * @return an entity-filtering graph instance.
+     */
+    public EntityGraph addSubgraph(final String fieldName, final Class<?> fieldClass, final String... filteringScopes);
+
+    /**
+     * Add a subgraph into this graph for given set of entity-filtering scopes.
+     *
+     * @param fieldName name of the subgraph field to be added.
+     * @param fieldClass entity class representing the subgraph.
+     * @param filteringScopes entity-filtering scopes for the subgraph.
+     * @return an entity-filtering graph instance.
+     */
+    public EntityGraph addSubgraph(final String fieldName, final Class<?> fieldClass, final Set<String> filteringScopes);
+
+    /**
+     * Add a set of entity-filtering scopes to this graph.
+     *
+     * @param filteringScopes entity-filtering scopes to be added.
+     * @return an entity-filtering graph instance.
+     */
+    public EntityGraph addFilteringScopes(final Set<String> filteringScopes);
+
+    /**
+     * Determines whether a field/subgraph is present in ANY of the given scopes. If no scopes are given the return value
+     * determines whether the field is present in any scope.
+     *
+     * @param field field to be checked.
+     * @param filteringScope entity-filtering scope.
+     * @return {@code true} if field is present in the given scope, {@code false} otherwise.
+     */
+    public boolean presentInScope(final String field, final String filteringScope);
+
+    /**
+     * Determines whether a field/subgraph is present in ANY of the existing scopes.
+     *
+     * @param field field to be checked.
+     * @return {@code true} if field is present in ANY of the existing scopes, {@code false} otherwise.
+     */
+    public boolean presentInScopes(final String field);
+
+    /**
+     * Get an entity class this graph is created for.
+     *
+     * @return an entity class.
+     */
+    public Class<?> getEntityClass();
+
+    /**
+     * Get fields for given entity-filtering scope.
+     *
+     * @param filteringScope scope the returned fields have to be in.
+     * @return set of fields present in given scope.
+     */
+    public Set<String> getFields(final String filteringScope);
+
+    /**
+     * Get fields for given entity-filtering scopes.
+     *
+     * @param filteringScopes scopes the returned fields have to be in.
+     * @return set of fields present in given scopes.
+     */
+    public Set<String> getFields(final String... filteringScopes);
+
+    /**
+     * Get fields for given entity-filtering scopes.
+     *
+     * @param filteringScopes scopes the returned fields have to be in.
+     * @return set of fields present in given scopes.
+     */
+    public Set<String> getFields(final Set<String> filteringScopes);
+
+    /**
+     * Get all available entity-filtering scopes.
+     *
+     * @return all available entity-filtering scopes.
+     */
+    public Set<String> getFilteringScopes();
+
+    /**
+     * Get all available entity-filtering scopes defined on a class.
+     *
+     * @return all available entity-filtering scopes.
+     */
+    public Set<String> getClassFilteringScopes();
+
+    /**
+     * Get subgraphs for given entity-filtering scope.
+     *
+     * @param filteringScope scope the returned subgraphs have to be in.
+     * @return map of subgraphs present in given scope.
+     */
+    public Map<String, Class<?>> getSubgraphs(final String filteringScope);
+
+    /**
+     * Get subgraphs for given entity-filtering scopes.
+     *
+     * @param filteringScopes scopes the returned subgraphs have to be in.
+     * @return map of subgraphs present in given scopes.
+     */
+    public Map<String, Class<?>> getSubgraphs(final String... filteringScopes);
+
+    /**
+     * Get subgraphs for given entity-filtering scopes.
+     *
+     * @param filteringScopes scopes the returned subgraphs have to be in.
+     * @return map of subgraphs present in given scopes.
+     */
+    public Map<String, Class<?>> getSubgraphs(final Set<String> filteringScopes);
+
+    /**
+     * Remove a field/subgraph from the graph (all entity-filtering scopes).
+     *
+     * @param name name of the field/subgraph to be removed.
+     * @return an entity-filtering graph instance.
+     */
+    public EntityGraph remove(final String name);
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/EntityGraphProvider.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/EntityGraphProvider.java
new file mode 100644
index 0000000..1f67159
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/EntityGraphProvider.java
@@ -0,0 +1,69 @@
+/*
+ * 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.message.filtering.spi;
+
+import java.util.Set;
+
+import org.glassfish.jersey.spi.Contract;
+
+/**
+ * Provides {@link EntityGraph entity graph} and {@link ObjectGraph object graph} instances.
+ *
+ * @author Michal Gajdos
+ */
+@Contract
+public interface EntityGraphProvider {
+
+    /**
+     * Get an entity graph for given class. New graph is created if no graph exists for given class.
+     *
+     * @param entityClass entity class the graph should be created for.
+     * @param forWriter flag determining whether the graph should be created for writer/reader.
+     * @return an entity graph.
+     */
+    public EntityGraph getOrCreateEntityGraph(final Class<?> entityClass, final boolean forWriter);
+
+    /**
+     * Get an empty entity graph for given class. New graph is created if the stored one is not an empty entity graph or no graph
+     * exists for given class. This method overrides the graph created by {@link #getOrCreateEntityGraph(Class, boolean)} method.
+     *
+     * @param entityClass entity class the graph should be created for.
+     * @param forWriter flag determining whether the graph should be created for writer/reader.
+     * @return an empty entity graph.
+     */
+    public EntityGraph getOrCreateEmptyEntityGraph(final Class<?> entityClass, final boolean forWriter);
+
+    /**
+     * Determine whether an entity graph for given entity class has been created by this provider.
+     *
+     * @param entityClass entity class for which the graph should be checked.
+     * @param forWriter flag determining whether the check should be in writer/reader graphs.
+     * @return {@code true} if the entity graph already exists, {@code false} otherwise.
+     */
+    public boolean containsEntityGraph(final Class<?> entityClass, final boolean forWriter);
+
+    /**
+     * Create an {@code ObjectGraph} for given parameters. Every time this method is called a new instance of object graph is
+     * created.
+     *
+     * @param entityClass entity class which the object graph should be created for.
+     * @param filteringScopes entity-filtering scopes the graph should be created for.
+     * @param forWriter flag determining whether the graph should be created for writer/reader.
+     * @return an entity-filtering object graph instance.
+     */
+    public ObjectGraph createObjectGraph(final Class<?> entityClass, final Set<String> filteringScopes, final boolean forWriter);
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/EntityInspector.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/EntityInspector.java
new file mode 100644
index 0000000..d2ca722
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/EntityInspector.java
@@ -0,0 +1,44 @@
+/*
+ * 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.message.filtering.spi;
+
+import org.glassfish.jersey.spi.Contract;
+
+/**
+ * Responsible for inspecting entity classes. This class invokes all available {@link EntityProcessor entity processors} with
+ * different {@link EntityProcessorContext contexts}.
+ *
+ * @author Michal Gajdos
+ */
+@Contract
+public interface EntityInspector {
+
+    /**
+     * Inspect entity class and create/update {@link EntityGraph} for reader/writer. The entity graph will be used to create
+     * entity-filtering object which is requested by {@code #createFilteringObject(...)}.
+     * <p>
+     * Method recursively inspects entity fields classes suitable for inspecting.
+     * </p>
+     * <p>
+     * Method uses {@link EntityProcessor}s for inspecting.
+     * </p>
+     *
+     * @param entityClass entity class to be examined.
+     * @param forWriter flag determining whether the class should be examined for reader or writer.
+     */
+    public void inspect(final Class<?> entityClass, final boolean forWriter);
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/EntityProcessor.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/EntityProcessor.java
new file mode 100644
index 0000000..c7a9461
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/EntityProcessor.java
@@ -0,0 +1,59 @@
+/*
+ * 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.message.filtering.spi;
+
+import org.glassfish.jersey.spi.Contract;
+
+/**
+ * Contract supposed to process entity classes for Entity Data Filtering. Implementations will be given a
+ * {@link EntityProcessorContext context} providing necessary information to process particular
+ * {@link EntityProcessorContext.Type context type}. Contexts are created for: class / properties / accessors.
+ *
+ * @author Michal Gajdos
+ */
+@Contract
+public interface EntityProcessor {
+
+    /**
+     * Result type of processing an context.
+     */
+    public enum Result {
+
+        /**
+         * Processing of an context resulted in modification of the provided entity graph.
+         */
+        APPLY,
+
+        /**
+         * Entity processor didn't modify the provided entity graph.
+         */
+        SKIP,
+
+        /**
+         * Rollback every entity graph modification done in current context.
+         */
+        ROLLBACK
+    }
+
+    /**
+     * Process given (class/property/accessor) {@link EntityProcessorContext context} by modifying provided {@link EntityGraph}.
+     *
+     * @param context context to be processed.
+     * @return result of processing a context.
+     */
+    public Result process(final EntityProcessorContext context);
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/EntityProcessorContext.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/EntityProcessorContext.java
new file mode 100644
index 0000000..47efbd2
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/EntityProcessorContext.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.message.filtering.spi;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+/**
+ * {@link EntityProcessor Entity processor} context providing details about entity processing.
+ * <p/>
+ * The context contains the {@link Type} which distinguishes between types of context. There are various properties in the
+ * context (accessible by getters) and some of them might be relevant only to specific context types.
+ *
+ * @author Michal Gajdos
+ */
+public interface EntityProcessorContext {
+
+    /**
+     * The type of the context which describes in which entity processing phase the
+     * {@link EntityProcessor#process(EntityProcessorContext)} is triggered.
+     */
+    public enum Type {
+
+        /**
+         * Context created to process entity class when reading entity from an input stream into an Java object. Properties
+         * available for this type are: {@link #getEntityClass()}, {@link #getEntityGraph()}.
+         */
+        CLASS_READER,
+
+        /**
+         * Context created to process entity class when writing entity to an output stream from an Java object. Properties
+         * available for this type are: {@link #getEntityClass()}, {@link #getEntityGraph()}.
+         */
+        CLASS_WRITER,
+
+        /**
+         * Context created to process entity properties when reading entity from an input stream into an Java object. Properties
+         * available for this type are: {@link #getField()}, {@link #getMethod()}, {@link #getEntityGraph()}.
+         */
+        PROPERTY_READER,
+
+        /**
+         * Context created to process entity properties when writing entity to an output stream from an Java object. Properties
+         * available for this type are: {@link #getField()}, {@link #getMethod()}, {@link #getEntityGraph()}.
+         */
+        PROPERTY_WRITER,
+
+        /**
+         * Context created to process property accessors when reading entity from an input stream into an Java object. Properties
+         * available for this type are: {@link #getMethod()}, {@link #getEntityGraph()}.
+         */
+        METHOD_READER,
+
+        /**
+         * Context created to process property accessors when writing entity to an output stream from an Java object. Properties
+         * available for this type are: {@link #getMethod()}, {@link #getEntityGraph()}.
+         */
+        METHOD_WRITER
+    }
+
+    /**
+     * Get the {@link Type type} of this context.
+     *
+     * @return entity processing context type.
+     */
+    public Type getType();
+
+    /**
+     * Get entity class to be processed. The entity class is available only for {@link Type#CLASS_WRITER} and
+     * {@link Type#CLASS_READER} context types.
+     *
+     * @return entity class or {@code null} if the class is not available.
+     */
+    public Class<?> getEntityClass();
+
+    /**
+     * Get field to be processed. The field is available only for {@link Type#PROPERTY_WRITER} and
+     * {@link Type#PROPERTY_READER} context types.
+     *
+     * @return field or {@code null} if the field is not available.
+     */
+    public Field getField();
+
+    /**
+     * Get method to be processed. The method is available for {@link Type#PROPERTY_WRITER}, {@link Type#PROPERTY_READER},
+     * {@link Type#METHOD_WRITER}, {@link Type#METHOD_READER} context types.
+     *
+     * @return method or {@code null} if the method is not available.
+     */
+    public Method getMethod();
+
+    /**
+     * Get entity graph to be modified by the processing. The entity graph is available for all context types.
+     *
+     * @return entity graph.
+     */
+    public EntityGraph getEntityGraph();
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/FilteringHelper.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/FilteringHelper.java
new file mode 100644
index 0000000..a0f2fa1
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/FilteringHelper.java
@@ -0,0 +1,165 @@
+/*
+ * 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.message.filtering.spi;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.lang.reflect.WildcardType;
+import java.security.AccessController;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentMap;
+
+import javax.xml.bind.JAXBElement;
+
+import org.glassfish.jersey.internal.util.ReflectionHelper;
+import org.glassfish.jersey.internal.util.collection.DataStructures;
+
+/**
+ * SPI utility methods for entity filtering.
+ *
+ * @author Michal Gajdos
+ */
+public final class FilteringHelper {
+
+    /**
+     * Empty annotation array.
+     */
+    public static final Annotation[] EMPTY_ANNOTATIONS = new Annotation[0];
+
+    private static final ConcurrentMap<Type, Class<?>> ENTITY_CLASSES = DataStructures.createConcurrentMap();
+
+    /**
+     * Determine whether given class is filterable by entity-filtering. Filterable classes are all classes that are not primitives
+     * and are not in a package matching <code>java.*</code>.
+     *
+     * @param clazz entity class to be examined.
+     * @return {@code true} whether the class is filterable, {@code false otherwise}.
+     */
+    public static boolean filterableEntityClass(final Class<?> clazz) {
+        return !ReflectionHelper.isPrimitive(clazz) && !clazz.getPackage().getName().startsWith("java.");
+    }
+
+    /**
+     * A convenience method to get the domain class (i.e. <i>Customer</i>) from the generic type (i.e. <i>Customer</i>,
+     * <i>List&lt;Customer></i>, <i>JAXBElement&lt;Customer></i>, <i>JAXBElement&lt;? extends Customer></i>,
+     * <i>List&lt;JAXBElement&lt;Customer>></i>, or <i>List&lt;JAXBElement&lt;? extends Customer>></i>).
+     *
+     * @param genericType type to obtain entity domain class for.
+     * @return entity domain class.
+     */
+    public static Class<?> getEntityClass(final Type genericType) {
+        if (!ENTITY_CLASSES.containsKey(genericType)) {
+            ENTITY_CLASSES.putIfAbsent(genericType, _getEntityClass(genericType));
+        }
+        return ENTITY_CLASSES.get(genericType);
+    }
+
+    /**
+     * Note: This method was copied from {@code MOXyJsonProvider}.
+     */
+    @SuppressWarnings("JavaDoc")
+    private static Class<?> _getEntityClass(final Type genericType) {
+        if (null == genericType) {
+            return Object.class;
+        }
+        if (genericType instanceof Class && genericType != JAXBElement.class) {
+            final Class<?> clazz = (Class<?>) genericType;
+            if (clazz.isArray()) {
+                return _getEntityClass(clazz.getComponentType());
+            }
+            return clazz;
+        } else if (genericType instanceof ParameterizedType) {
+            final ParameterizedType parameterizedType = (ParameterizedType) genericType;
+            final Type[] arguments = parameterizedType.getActualTypeArguments();
+
+            final Type type = parameterizedType.getRawType() == Map.class ? arguments[1] : arguments[0];
+            if (type instanceof ParameterizedType) {
+                final Type rawType = ((ParameterizedType) type).getRawType();
+                if (rawType == JAXBElement.class) {
+                    return _getEntityClass(type);
+                }
+            } else if (type instanceof WildcardType) {
+                final Type[] upperTypes = ((WildcardType) type).getUpperBounds();
+                if (upperTypes.length > 0) {
+                    final Type upperType = upperTypes[0];
+                    if (upperType instanceof Class) {
+                        return (Class<?>) upperType;
+                    }
+                }
+            } else if (JAXBElement.class == type
+                    || type instanceof TypeVariable) {
+                return Object.class;
+            }
+            //noinspection ConstantConditions
+            return (Class<?>) type;
+        } else if (genericType instanceof GenericArrayType) {
+            final GenericArrayType genericArrayType = (GenericArrayType) genericType;
+            return _getEntityClass(genericArrayType.getGenericComponentType());
+        } else {
+            return Object.class;
+        }
+    }
+
+    /**
+     * Get accessor method mappings (field -> getter/setter) for given class.
+     *
+     * @param clazz entity class to obtain property methods for.
+     * @param isGetter flag determining whether to look for getters or setters.
+     * @return non-null map of field-accessor mappings.
+     */
+    public static Map<String, Method> getPropertyMethods(final Class<?> clazz, final boolean isGetter) {
+        final Map<String, Method> methods = new HashMap<>();
+
+        for (final Method method : AccessController.doPrivileged(ReflectionHelper.getDeclaredMethodsPA(clazz))) {
+            if ((isGetter && ReflectionHelper.isGetter(method))
+                    || (!isGetter && ReflectionHelper.isSetter(method))) {
+
+                methods.put(ReflectionHelper.getPropertyName(method), method);
+            }
+        }
+
+        final Class<?> parent = clazz.getSuperclass();
+        // We're interested in fields/accessors in superclasses but not those from i.e. Object/Enum.
+        if (parent != null && !parent.getPackage().getName().startsWith("java.lang")) {
+            methods.putAll(getPropertyMethods(parent, isGetter));
+        }
+
+        return methods;
+    }
+
+    /**
+     * Get a set containing default filtering scope.
+     *
+     * @return default filtering scope.
+     */
+    public static Set<String> getDefaultFilteringScope() {
+        return Collections.singleton(ScopeProvider.DEFAULT_SCOPE);
+    }
+
+    /**
+     * Prevent instantiation.
+     */
+    private FilteringHelper() {
+    }
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/ObjectGraph.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/ObjectGraph.java
new file mode 100644
index 0000000..99f326e
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/ObjectGraph.java
@@ -0,0 +1,79 @@
+/*
+ * 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.message.filtering.spi;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Read-only graph containing representations of an entity class that should be processed in entity-filtering. The
+ * representations are twofolds: simple (primitive/non-filterable) fields and further-filterable fields (represented by
+ * subgraphs).
+ * <p/>
+ * Object graph instances are created for entity-filtering scopes that are obtained from entity annotations, configuration or
+ * scopes defined on resource methods / classes (on server).
+ *
+ * @author Michal Gajdos
+ * @see ObjectGraphTransformer
+ * @see ScopeResolver
+ */
+public interface ObjectGraph {
+
+    /**
+     * Get entity domain class of this graph.
+     *
+     * @return entity domain class.
+     */
+    public Class<?> getEntityClass();
+
+    /**
+     * Get a set of all simple (non-filterable) fields of entity class. Value of each of these fields is either primitive or
+     * the entity-filtering feature cannot be applied to this field. Values of these fields can be directly processed.
+     *
+     * @return non-filterable fields.
+     */
+    public Set<String> getFields();
+
+    /**
+     * Get fields with the given parent path. The parent path, which may exist in the requested filtering scopes, is
+     * used for context to match against the field at the subgraph level.
+     *
+     * @param parent name of parent field.
+     * @return non-filterable fields.
+     */
+    public Set<String> getFields(String parent);
+
+    /**
+     * Get a map of all further-filterable fields of entity class. Mappings are represented as:
+     * <pre>
+     * &lt;field&gt; -&gt; &lt;object-graph&gt;</pre>
+     * It is supposed that object graphs contained in this map would be processed further.
+     *
+     * @return further-filterable map of fields.
+     */
+    public Map<String, ObjectGraph> getSubgraphs();
+
+    /**
+     * Get subgraphs with the given parent path. The parent path, which may exist in the requested filtering scopes, is
+     * used for context to match against the subgraph level.
+     *
+     * @param parent name of parent field.
+     * @return further-filterable map of fields.
+     *
+     */
+    public Map<String, ObjectGraph> getSubgraphs(String parent);
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/ObjectGraphTransformer.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/ObjectGraphTransformer.java
new file mode 100644
index 0000000..f3261c7
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/ObjectGraphTransformer.java
@@ -0,0 +1,72 @@
+/*
+ * 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.message.filtering.spi;
+
+import org.glassfish.jersey.internal.inject.AbstractBinder;
+import org.glassfish.jersey.spi.Contract;
+
+/**
+ * This contract brings support for transforming an internal representation of entity data filtering feature into an object
+ * familiar to underlying provider (e.g. message body worker).
+ * <p>
+ * This interface is supposed to be implemented by modules providing JAX-RS/Jersey providers / configuration object (e.g. message
+ * body workers) that can directly affect reading/writing of an entity.
+ * </p>
+ * <p>
+ * Implementations should be registered into client/server runtime via
+ * {@link AbstractBinder jersey binder} (for more information and common implementation see
+ * {@link AbstractObjectProvider}):
+ * <pre>
+ * bindAsContract(MyObjectGraphTransformer.class)
+ *       // FilteringGraphTransformer.
+ *       .to(new TypeLiteral&lt;ObjectGraphTransformer&lt;MyFilteringObject&gt;&gt;() {})
+ *       // Scope.
+ *       .in(Singleton.class);
+ * </pre>
+ * The custom transformer can be then {@link javax.inject.Inject injected} as one these injection point types:
+ * <ul>
+ * <li>{@code MyObjectGraphTransformer}</li>
+ * <li>{@code javax.inject.Provider&lt;ObjectGraphTransformer&lt;MyFilteringObject&gt;&gt;}</li>
+ * </ul>
+ * </p>
+ * <p>
+ * By default a {@code ObjectGraph} -&gt; {@code ObjectGraph} transformer is available in the runtime. This transformer can be
+ * injected (via {@link javax.inject.Inject @Inject}) into the following types:
+ * <ul>
+ * <li>{@code ObjectGraphTransformer}</li>
+ * <li>{@code javax.inject.Provider&lt;ObjectGraphTransformer&lt;Object&gt;&gt;}</li>
+ * <li>{@code javax.inject.Provider&lt;ObjectGraphTransformer&lt;ObjectGraph&gt;&gt;}</li>
+ * </ul>
+ * </p>
+ *
+ * @param <T> representation of entity data filtering requested by provider.
+ * @author Michal Gajdos
+ * @see AbstractObjectProvider
+ * @see ObjectProvider
+ */
+@Contract
+public interface ObjectGraphTransformer<T> {
+
+    /**
+     * Transform a given graph into an entity-filtering object. The entire graph (incl. it's subgraphs) should be processed by
+     * this method as this method is invoked only once for a root entity class.
+     *
+     * @param graph object graph to be transformed.
+     * @return entity-filtering object requested by provider.
+     */
+    public T transform(final ObjectGraph graph);
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/ObjectProvider.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/ObjectProvider.java
new file mode 100644
index 0000000..f22e77d
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/ObjectProvider.java
@@ -0,0 +1,92 @@
+/*
+ * 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.message.filtering.spi;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+
+import org.glassfish.jersey.internal.inject.AbstractBinder;
+import org.glassfish.jersey.spi.Contract;
+
+/**
+ * Entry point of Entity Data Filtering feature for providers working with custom entities and media types (reading/writing).
+ * Exposed methods are supposed to provide an entity-filtering object of defined type (generic parameter type
+ * {@code &lt;T&gt;}) for given types/classes that is requested by underlying provider (e.g. message body worker).
+ * <p>
+ * Methods are also accepting a list of entity annotations which entity-filtering scopes and then particular entity-filtering
+ * object are determined from. Entity annotations can be passed to the runtime via:
+ * <ul>
+ * <li>{@link javax.ws.rs.client.Entity#entity(Object, javax.ws.rs.core.MediaType, java.lang.annotation.Annotation[])} on the
+ * client, or</li>
+ * <li>{@link javax.ws.rs.core.Response.ResponseBuilder#entity(Object, java.lang.annotation.Annotation[])} on the server</li>
+ * </ul>
+ * </p>
+ * <p>
+ * Custom implementations should, during processing, look up for available {@link EntityProcessor entity processors} to examine
+ * given entity classes and {@link ScopeResolver scope providers} to determine the current entity-filtering scope. Entity class
+ * and entity-filtering scopes determine the {@link ObjectGraph object graph} passed to {@link ObjectGraphTransformer object graph
+ * transformer} and hence the resulting entity-filtering object.
+ * </p>
+ * <p>
+ * Implementations should be registered into client/server runtime via
+ * {@link AbstractBinder jersey binder} (for more information and common implementation see
+ * {@link AbstractObjectProvider}):
+ * <pre>
+ * bindAsContract(MyObjectProvider.class)
+ *       // FilteringGraphTransformer.
+ *       .to(new TypeLiteral&lt;ObjectGraphTransformer&lt;MyFilteringObject&gt;&gt;() {})
+ *       // Scope.
+ *       .in(Singleton.class);
+ * </pre>
+ * The custom provider can be then {@link javax.inject.Inject injected} as one these injection point types:
+ * <ul>
+ * <li>{@code MyObjectProvider}</li>
+ * <li>{@code javax.inject.Provider&lt;ObjectProvider&lt;MyFilteringObject&gt;&gt;}</li>
+ * </ul>
+ * </p>
+ * <p>
+ * By default a {@code ObjectGraph} provider is available in the runtime. This object provider can be injected (via
+ * {@link javax.inject.Inject @Inject}) into the following types:
+ * <ul>
+ * <li>{@code ObjectProvider}</li>
+ * <li>{@code javax.inject.Provider&lt;ObjectProvider&lt;Object&gt;&gt;}</li>
+ * <li>{@code javax.inject.Provider&lt;ObjectProvider&lt;ObjectGraph&gt;&gt;}</li>
+ * </ul>
+ * </p>
+ * <p>
+ * Note: For most of the cases it is sufficient that users implement {@link ObjectGraphTransformer object graph transformer} by
+ * extending {@link AbstractObjectProvider} class.
+ * </p>
+ *
+ * @param <T> representation of entity data filtering requested by provider.
+ * @author Michal Gajdos
+ * @see AbstractObjectProvider
+ * @see ObjectGraphTransformer
+ */
+@Contract
+public interface ObjectProvider<T> {
+
+    /**
+     * Get reader/writer entity-filtering object for given type.
+     *
+     * @param genericType type for which the object is requested.
+     * @param forWriter flag to determine whether to create object for reading/writing purposes.
+     * @param annotations entity annotations to determine the runtime scope.
+     * @return entity-filtering object.
+     */
+    public T getFilteringObject(Type genericType, boolean forWriter, final Annotation... annotations);
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/ScopeProvider.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/ScopeProvider.java
new file mode 100644
index 0000000..9bd5c11
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/ScopeProvider.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.message.filtering.spi;
+
+import java.lang.annotation.Annotation;
+import java.util.Set;
+
+import org.glassfish.jersey.message.filtering.EntityFiltering;
+import org.glassfish.jersey.spi.Contract;
+
+/**
+ * Entry point for obtaining entity-filtering scopes used to process a request/response entity. Entity-filtering scopes are
+ * obtained from (sorted by priority):
+ * <ul>
+ * <li>entity annotations - provided with entity when creating request/response</li>
+ * <li>annotations stored under
+ * {@value org.glassfish.jersey.message.filtering.EntityFilteringFeature#ENTITY_FILTERING_SCOPE} property obtained from
+ * {@link javax.security.auth.login.Configuration configuration}
+ * </li>
+ * <li>entity-filtering annotations present on resource methods/classes (on server)</li>
+ * </ul>
+ * <p/>
+ * Note: Definition of entity-filtering scopes can be found in {@link ScopeResolver}.
+ *
+ * @author Michal Gajdos
+ * @see ScopeResolver
+ */
+@Contract
+public interface ScopeProvider {
+
+    /**
+     * Default entity-filtering scope.
+     * <p/>
+     * Default scope is used in {@link ObjectGraph object graph} to retrieve a subgraph instance at the moment subgraph's entity
+     * class does not define any entity-filtering scope the object graph was created for.
+     * <p/>
+     * This scope is created for an {@link EntityGraph entity graph} if no other entity-filtering / security annotation is present
+     * on a class.
+     */
+    public static final String DEFAULT_SCOPE = EntityFiltering.class.getName();
+
+    /**
+     * Get entity-filtering scopes to be used to process an entity.
+     *
+     * @param entityAnnotations entity annotations provided with entity when creating request/response.
+     * @param defaultIfNotFound flag determining whether the default entity-filtering scope should be returned if no other
+     * scope can be obtained.
+     * @return non-null entity-filtering scopes.
+     */
+    public Set<String> getFilteringScopes(final Annotation[] entityAnnotations, final boolean defaultIfNotFound);
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/ScopeResolver.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/ScopeResolver.java
new file mode 100644
index 0000000..c1403d2
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/ScopeResolver.java
@@ -0,0 +1,53 @@
+/*
+ * 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.message.filtering.spi;
+
+import java.lang.annotation.Annotation;
+import java.util.Set;
+
+import org.glassfish.jersey.spi.Contract;
+
+/**
+ * Class used to resolve entity-filtering scopes from annotations. Annotations passed to {@code #resolve()} method
+ * can be one of the following: entity annotations (provided when creating request/response entity),
+ * annotations obtained from {@link javax.ws.rs.core.Configuration configuration}, resource method / resource class annotations.
+ * <p/>
+ * Entity-filtering scope is supposed to be an unique string that can be derived from an annotations and that can be further used
+ * in internal entity data filtering structures. Examples of such unique strings are:
+ * <ul>
+ * <li><code>@MyDetailedView</code> -&gt; <code>my.package.MyDetailedView</code></li>
+ * <li>
+ * <code>@RolesAllowed({"manager", "user"})</code> -&gt; <code>javax.annotation.security.RolesAllowed_manager</code> and
+ * <code>javax.annotation.security.RolesAllowed_user</code>
+ * </li>
+ * </ul>
+ * <p/>
+ * {@link ScopeResolver Scope resolvers} are invoked from {@link ScopeProvider scope provider} instance.
+ *
+ * @author Michal Gajdos
+ */
+@Contract
+public interface ScopeResolver {
+
+    /**
+     * Resolve entity-filtering scopes for given annotations.
+     *
+     * @param annotations list of arbitrary annotations.
+     * @return non-null set of entity-filtering scopes.
+     */
+    public Set<String> resolve(final Annotation[] annotations);
+}
diff --git a/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/package-info.java b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/package-info.java
new file mode 100644
index 0000000..d10dba7
--- /dev/null
+++ b/ext/entity-filtering/src/main/java/org/glassfish/jersey/message/filtering/spi/package-info.java
@@ -0,0 +1,38 @@
+/*
+ * 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
+ */
+
+/**
+ * SPI for Entity Data Filtering in Jersey.
+ * <p/>
+ * To create a custom entity-filtering annotation with special handling (e.g. field aggregator annotation used to annotate
+ * classes), refer to:
+ * <ul>
+ * <li>{@link org.glassfish.jersey.message.filtering.spi.EntityProcessor}</li>
+ * <li>{@link org.glassfish.jersey.message.filtering.spi.AbstractEntityProcessor}</li>
+ * <li>{@link org.glassfish.jersey.message.filtering.spi.ScopeResolver}</li>
+ * </ul>
+ * <p/>
+ * To support Entity Data Filtering in custom providers (e.g. message body workers), refer to:
+ * <ul>
+ * <li>{@link org.glassfish.jersey.message.filtering.spi.ObjectProvider}</li>
+ * <li>{@link org.glassfish.jersey.message.filtering.spi.ObjectGraphTransformer}</li>
+ * <li>{@link org.glassfish.jersey.message.filtering.spi.AbstractObjectProvider}</li>
+ * </ul>
+ * <p/>
+ *
+ * @since 2.3
+ */
+package org.glassfish.jersey.message.filtering.spi;
diff --git a/ext/entity-filtering/src/main/resources/org/glassfish/jersey/message/filtering/internal/localization.properties b/ext/entity-filtering/src/main/resources/org/glassfish/jersey/message/filtering/internal/localization.properties
new file mode 100644
index 0000000..0656696
--- /dev/null
+++ b/ext/entity-filtering/src/main/resources/org/glassfish/jersey/message/filtering/internal/localization.properties
@@ -0,0 +1,19 @@
+#
+# 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} - value of the property
+entity.filtering.scope.not.annotations=The entity-filtering scope property does not contain an instance of Annotation or Annotation[] ({0}).
+merging.filtering.scopes=Merging two sets of entity-filtering scopes obtained from two different scope resolvers.
diff --git a/ext/entity-filtering/src/test/java/org/glassfish/jersey/message/filtering/DenyAllEntity.java b/ext/entity-filtering/src/test/java/org/glassfish/jersey/message/filtering/DenyAllEntity.java
new file mode 100644
index 0000000..94922c6
--- /dev/null
+++ b/ext/entity-filtering/src/test/java/org/glassfish/jersey/message/filtering/DenyAllEntity.java
@@ -0,0 +1,47 @@
+/*
+ * 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.message.filtering;
+
+import javax.annotation.security.DenyAll;
+
+/**
+ * @author Michal Gajdos
+ */
+@DenyAll
+public class DenyAllEntity {
+
+    private String field;
+
+    @DenyAll
+    public String getField() {
+        return field;
+    }
+
+    @DenyAll
+    public void setField(final String field) {
+        this.field = field;
+    }
+
+    @DenyAll
+    public SubEntity getSubgraph() {
+        return null;
+    }
+
+    @DenyAll
+    public void setSubgraph(final SubEntity subgraph) {
+    }
+}
diff --git a/ext/entity-filtering/src/test/java/org/glassfish/jersey/message/filtering/PermitAllEntity.java b/ext/entity-filtering/src/test/java/org/glassfish/jersey/message/filtering/PermitAllEntity.java
new file mode 100644
index 0000000..1a95adb
--- /dev/null
+++ b/ext/entity-filtering/src/test/java/org/glassfish/jersey/message/filtering/PermitAllEntity.java
@@ -0,0 +1,47 @@
+/*
+ * 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.message.filtering;
+
+import javax.annotation.security.PermitAll;
+
+/**
+ * @author Michal Gajdos
+ */
+@PermitAll
+public class PermitAllEntity {
+
+    private String field;
+
+    @PermitAll
+    public String getField() {
+        return field;
+    }
+
+    @PermitAll
+    public void setField(final String field) {
+        this.field = field;
+    }
+
+    @PermitAll
+    public SubEntity getSubgraph() {
+        return null;
+    }
+
+    @PermitAll
+    public void setSubgraph(final SubEntity subgraph) {
+    }
+}
diff --git a/ext/entity-filtering/src/test/java/org/glassfish/jersey/message/filtering/RolesAllowedEntity.java b/ext/entity-filtering/src/test/java/org/glassfish/jersey/message/filtering/RolesAllowedEntity.java
new file mode 100644
index 0000000..f82ef9d
--- /dev/null
+++ b/ext/entity-filtering/src/test/java/org/glassfish/jersey/message/filtering/RolesAllowedEntity.java
@@ -0,0 +1,47 @@
+/*
+ * 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.message.filtering;
+
+import javax.annotation.security.RolesAllowed;
+
+/**
+ * @author Michal Gajdos
+ */
+@RolesAllowed({"manager", "client"})
+public class RolesAllowedEntity {
+
+    private String field;
+
+    @RolesAllowed("manager")
+    public String getField() {
+        return field;
+    }
+
+    @RolesAllowed("client")
+    public void setField(final String field) {
+        this.field = field;
+    }
+
+    @RolesAllowed("manager")
+    public SubEntity getSubgraph() {
+        return null;
+    }
+
+    @RolesAllowed("client")
+    public void setSubgraph(final SubEntity subgraph) {
+    }
+}
diff --git a/ext/entity-filtering/src/test/java/org/glassfish/jersey/message/filtering/SecurityAnnotationsTest.java b/ext/entity-filtering/src/test/java/org/glassfish/jersey/message/filtering/SecurityAnnotationsTest.java
new file mode 100644
index 0000000..8c5fa49
--- /dev/null
+++ b/ext/entity-filtering/src/test/java/org/glassfish/jersey/message/filtering/SecurityAnnotationsTest.java
@@ -0,0 +1,40 @@
+/*
+ * 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.message.filtering;
+
+import org.junit.Test;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+/**
+ * {@link org.glassfish.jersey.message.filtering.SecurityAnnotations} unit tests.
+ *
+ * @author Michal Gajdos
+ */
+public class SecurityAnnotationsTest {
+
+    @Test
+    public void testCreateRolesAllowed() throws Exception {
+        assertThat(new String[] {"manager", "user"}, equalTo(SecurityAnnotations.rolesAllowed("manager", "user").value()));
+    }
+
+    @Test
+    public void testCreateRolesAllowedNegative() throws Exception {
+        assertThat(new String[] {}, equalTo(SecurityAnnotations.rolesAllowed((String) null).value()));
+        assertThat(new String[] {"manager"}, equalTo(SecurityAnnotations.rolesAllowed("manager", null).value()));
+    }
+}
diff --git a/ext/entity-filtering/src/test/java/org/glassfish/jersey/message/filtering/SecurityEntityProcessorTest.java b/ext/entity-filtering/src/test/java/org/glassfish/jersey/message/filtering/SecurityEntityProcessorTest.java
new file mode 100644
index 0000000..21edda4
--- /dev/null
+++ b/ext/entity-filtering/src/test/java/org/glassfish/jersey/message/filtering/SecurityEntityProcessorTest.java
@@ -0,0 +1,227 @@
+/*
+ * 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.message.filtering;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.stream.Collectors;
+
+import org.glassfish.jersey.message.filtering.spi.EntityGraph;
+import org.glassfish.jersey.message.filtering.spi.EntityProcessor;
+import org.glassfish.jersey.message.filtering.spi.EntityProcessorContext;
+import org.glassfish.jersey.message.filtering.spi.FilteringHelper;
+import org.glassfish.jersey.message.filtering.spi.ScopeProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+/**
+ * {@link org.glassfish.jersey.message.filtering.SecurityEntityProcessor} unit tests.
+ *
+ * @author Michal Gajdos
+ */
+@SuppressWarnings("JavaDoc")
+public class SecurityEntityProcessorTest {
+
+    private SecurityEntityProcessor processor;
+
+    @Before
+    public void setUp() throws Exception {
+        processor = new SecurityEntityProcessor();
+    }
+
+    @Test
+    public void testProcessPermitAllClass() throws Exception {
+        final EntityGraph actual = new EntityGraphImpl(PermitAllEntity.class);
+
+        final EntityGraph expected = new EntityGraphImpl(PermitAllEntity.class);
+        expected.addFilteringScopes(FilteringHelper.getDefaultFilteringScope());
+
+        for (final boolean forWriter : new boolean[] {true, false}) {
+            final EntityProcessor.Result result = testProcessClass(PermitAllEntity.class, actual, forWriter);
+
+            assertThat(result, equalTo(EntityProcessor.Result.APPLY));
+            assertThat(actual, equalTo(expected));
+        }
+    }
+
+    @Test
+    public void testProcessDenyAllClass() throws Exception {
+        final EntityGraph actual = new EntityGraphImpl(DenyAllEntity.class);
+        final EntityGraph expected = new EntityGraphImpl(DenyAllEntity.class);
+
+        for (final boolean forWriter : new boolean[] {true, false}) {
+            final EntityProcessor.Result result = testProcessClass(DenyAllEntity.class, actual, forWriter);
+
+            assertThat(result, equalTo(EntityProcessor.Result.ROLLBACK));
+            assertThat(actual, equalTo(expected));
+        }
+    }
+
+    @Test
+    public void testProcessRolesAllowedClass() throws Exception {
+        final EntityGraph actual = new EntityGraphImpl(RolesAllowedEntity.class);
+
+        final EntityGraph expected = new EntityGraphImpl(RolesAllowedEntity.class);
+        expected.addFilteringScopes(
+                Arrays.asList(
+                        SecurityHelper.getRolesAllowedScope("manager"), SecurityHelper.getRolesAllowedScope("client"))
+                      .stream()
+                      .collect(Collectors.toSet()));
+
+        for (final boolean forWriter : new boolean[] {true, false}) {
+            final EntityProcessor.Result result = testProcessClass(RolesAllowedEntity.class, actual, forWriter);
+
+            assertThat(result, equalTo(EntityProcessor.Result.APPLY));
+            assertThat(actual, equalTo(expected));
+        }
+    }
+
+    private EntityProcessor.Result testProcessClass(final Class<?> clazz, final EntityGraph graph, final boolean forWriter)
+            throws Exception {
+
+        final EntityProcessorContext context = new EntityProcessorContextImpl(
+                forWriter ? EntityProcessorContext.Type.CLASS_WRITER : EntityProcessorContext.Type.CLASS_READER,
+                clazz, graph);
+
+        return processor.process(context);
+    }
+
+    @Test
+    public void testProcessPermitAllProperties() throws Exception {
+        final EntityGraph actual = new EntityGraphImpl(PermitAllEntity.class);
+
+        final EntityGraph expected = new EntityGraphImpl(PermitAllEntity.class);
+        expected.addField("field", ScopeProvider.DEFAULT_SCOPE);
+
+        for (final boolean forWriter : new boolean[] {true, false}) {
+            final EntityProcessor.Result result = testProcessProperty(PermitAllEntity.class, actual, forWriter);
+
+            assertThat(result, equalTo(EntityProcessor.Result.APPLY));
+            assertThat(actual, equalTo(expected));
+        }
+    }
+
+    @Test
+    public void testProcessDenyAllProperties() throws Exception {
+        final EntityGraph actual = new EntityGraphImpl(DenyAllEntity.class);
+        final EntityGraph expected = new EntityGraphImpl(DenyAllEntity.class);
+
+        for (final boolean forWriter : new boolean[] {true, false}) {
+            final EntityProcessor.Result result = testProcessProperty(DenyAllEntity.class, actual, forWriter);
+
+            assertThat(result, equalTo(EntityProcessor.Result.ROLLBACK));
+            assertThat(actual, equalTo(expected));
+        }
+    }
+
+    @Test
+    public void testProcessRolesAllowedProperties() throws Exception {
+        final EntityGraph actual = new EntityGraphImpl(RolesAllowedEntity.class);
+        final EntityGraph expected = new EntityGraphImpl(RolesAllowedEntity.class);
+
+        for (final boolean forWriter : new boolean[] {true, false}) {
+            final EntityProcessor.Result result = testProcessProperty(RolesAllowedEntity.class, actual, forWriter);
+
+            if (forWriter) {
+                expected.addField("field", SecurityHelper.getRolesAllowedScope("manager"));
+            } else {
+                expected.addField("field", SecurityHelper.getRolesAllowedScope("client"));
+            }
+
+            assertThat(result, equalTo(EntityProcessor.Result.APPLY));
+            assertThat(actual, equalTo(expected));
+        }
+    }
+
+    private EntityProcessor.Result testProcessProperty(final Class<?> clazz, final EntityGraph graph, final boolean forWriter)
+            throws Exception {
+
+        final Field field = clazz.getDeclaredField("field");
+        final Method method = forWriter ? clazz.getMethod("getField") : clazz.getMethod("setField", String.class);
+
+        final EntityProcessorContext context = new EntityProcessorContextImpl(
+                forWriter ? EntityProcessorContext.Type.PROPERTY_WRITER : EntityProcessorContext.Type.PROPERTY_WRITER,
+                field, method, graph);
+
+        return processor.process(context);
+    }
+
+    @Test
+    public void testProcessPermitAllAccessors() throws Exception {
+        final EntityGraph actual = new EntityGraphImpl(PermitAllEntity.class);
+        actual.addFilteringScopes(FilteringHelper.getDefaultFilteringScope());
+
+        final EntityGraph expected = new EntityGraphImpl(PermitAllEntity.class);
+        expected.addFilteringScopes(FilteringHelper.getDefaultFilteringScope());
+        expected.addSubgraph("subgraph", SubEntity.class, ScopeProvider.DEFAULT_SCOPE);
+
+        for (final boolean forWriter : new boolean[] {true, false}) {
+            final EntityProcessor.Result result = testProcessAccessor(PermitAllEntity.class, actual, forWriter);
+
+            assertThat(result, equalTo(EntityProcessor.Result.APPLY));
+            assertThat(actual, equalTo(expected));
+        }
+    }
+
+    @Test
+    public void testProcessDenyAllAccessors() throws Exception {
+        final EntityGraph actual = new EntityGraphImpl(DenyAllEntity.class);
+        final EntityGraph expected = new EntityGraphImpl(DenyAllEntity.class);
+
+        for (final boolean forWriter : new boolean[] {true, false}) {
+            final EntityProcessor.Result result = testProcessAccessor(DenyAllEntity.class, actual, forWriter);
+
+            assertThat(result, equalTo(EntityProcessor.Result.ROLLBACK));
+            assertThat(actual, equalTo(expected));
+        }
+    }
+
+    @Test
+    public void testProcessRolesAllowedAccessor() throws Exception {
+        final EntityGraph actual = new EntityGraphImpl(RolesAllowedEntity.class);
+        final EntityGraph expected = new EntityGraphImpl(RolesAllowedEntity.class);
+
+        for (final boolean forWriter : new boolean[] {true, false}) {
+            final EntityProcessor.Result result = testProcessAccessor(RolesAllowedEntity.class, actual, forWriter);
+
+            if (forWriter) {
+                expected.addSubgraph("subgraph", SubEntity.class, SecurityHelper.getRolesAllowedScope("manager"));
+            } else {
+                expected.addSubgraph("subgraph", SubEntity.class, SecurityHelper.getRolesAllowedScope("client"));
+            }
+
+            assertThat(result, equalTo(EntityProcessor.Result.APPLY));
+            assertThat(actual, equalTo(expected));
+        }
+    }
+
+    private EntityProcessor.Result testProcessAccessor(final Class<?> clazz, final EntityGraph graph, final boolean forWriter)
+            throws Exception {
+
+        final Method method = forWriter ? clazz.getMethod("getSubgraph") : clazz.getMethod("setSubgraph", SubEntity.class);
+
+        final EntityProcessorContext context = new EntityProcessorContextImpl(
+                forWriter ? EntityProcessorContext.Type.PROPERTY_WRITER : EntityProcessorContext.Type.PROPERTY_WRITER,
+                method, graph);
+
+        return processor.process(context);
+    }
+}
diff --git a/ext/entity-filtering/src/test/java/org/glassfish/jersey/message/filtering/SecurityHelperTest.java b/ext/entity-filtering/src/test/java/org/glassfish/jersey/message/filtering/SecurityHelperTest.java
new file mode 100644
index 0000000..e86b1d3
--- /dev/null
+++ b/ext/entity-filtering/src/test/java/org/glassfish/jersey/message/filtering/SecurityHelperTest.java
@@ -0,0 +1,113 @@
+/*
+ * 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.message.filtering;
+
+import java.lang.annotation.Annotation;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import javax.ws.rs.core.SecurityContext;
+
+import javax.annotation.security.RolesAllowed;
+
+import org.glassfish.jersey.internal.inject.CustomAnnotationLiteral;
+import org.glassfish.jersey.message.filtering.spi.FilteringHelper;
+
+import org.junit.Test;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+/**
+ * {@link org.glassfish.jersey.message.filtering.SecurityHelper} unit tests.
+ *
+ * @author Michal Gajdos
+ */
+public class SecurityHelperTest {
+
+    @Test
+    public void testFilteringScopes() throws Exception {
+        Annotation[] annotations;
+        Set<String> expected;
+
+        // Empty annotations.
+        annotations = new Annotation[0];
+        assertThat(SecurityHelper.getFilteringScopes(annotations), equalTo(Collections.<String>emptySet()));
+
+        // Not security annotations.
+        annotations = new Annotation[] {CustomAnnotationLiteral.INSTANCE, CustomAnnotationLiteral.INSTANCE};
+        assertThat(SecurityHelper.getFilteringScopes(annotations), equalTo(Collections.<String>emptySet()));
+
+        // Mixed.
+        annotations = new Annotation[] {CustomAnnotationLiteral.INSTANCE, SecurityAnnotations
+                .rolesAllowed("manager"), CustomAnnotationLiteral.INSTANCE};
+        expected = Collections.singleton(RolesAllowed.class.getName() + "_manager");
+        assertThat(SecurityHelper.getFilteringScopes(annotations), equalTo(expected));
+
+        // Multiple.
+        annotations = new Annotation[] {SecurityAnnotations.rolesAllowed("manager", "user")};
+        expected = Arrays.asList(RolesAllowed.class.getName() + "_manager", RolesAllowed.class.getName() + "_user")
+                         .stream()
+                         .collect(Collectors.toSet());
+        assertThat(SecurityHelper.getFilteringScopes(annotations), equalTo(expected));
+
+        // PermitAll weirdo.
+        annotations = new Annotation[] {SecurityAnnotations.permitAll()};
+        assertThat(SecurityHelper.getFilteringScopes(annotations), equalTo(FilteringHelper.getDefaultFilteringScope()));
+
+        // DenyAll weirdo.
+        annotations = new Annotation[] {SecurityAnnotations.denyAll()};
+        assertThat(SecurityHelper.getFilteringScopes(annotations), equalTo(null));
+    }
+
+    @Test
+    public void testFilteringScopesWithContext() throws Exception {
+        final SecurityContext context = new TestSecurityContext();
+
+        Annotation[] annotations;
+        Set<String> expected;
+
+        // Empty annotations.
+        annotations = new Annotation[0];
+        assertThat(SecurityHelper.getFilteringScopes(context, annotations), equalTo(Collections.<String>emptySet()));
+
+        // Not security annotations.
+        annotations = new Annotation[] {CustomAnnotationLiteral.INSTANCE, CustomAnnotationLiteral.INSTANCE};
+        assertThat(SecurityHelper.getFilteringScopes(context, annotations), equalTo(Collections.<String>emptySet()));
+
+        // Mixed.
+        annotations = new Annotation[] {CustomAnnotationLiteral.INSTANCE, SecurityAnnotations
+                .rolesAllowed("manager"), CustomAnnotationLiteral.INSTANCE};
+        expected = Collections.singleton(RolesAllowed.class.getName() + "_manager");
+        assertThat(SecurityHelper.getFilteringScopes(context, annotations), equalTo(expected));
+
+        // Multiple.
+        annotations = new Annotation[] {SecurityAnnotations.rolesAllowed("client", "user")};
+        expected = Collections.singleton(RolesAllowed.class.getName() + "_user");
+        assertThat(SecurityHelper.getFilteringScopes(context, annotations), equalTo(expected));
+
+        // PermitAll weirdo.
+        annotations = new Annotation[] {SecurityAnnotations.permitAll()};
+        assertThat(SecurityHelper.getFilteringScopes(context, annotations), equalTo(FilteringHelper.getDefaultFilteringScope()));
+
+        // DenyAll weirdo.
+        annotations = new Annotation[] {SecurityAnnotations.denyAll()};
+        assertThat(SecurityHelper.getFilteringScopes(context, annotations), equalTo(null));
+    }
+
+}
diff --git a/ext/entity-filtering/src/test/java/org/glassfish/jersey/message/filtering/SubEntity.java b/ext/entity-filtering/src/test/java/org/glassfish/jersey/message/filtering/SubEntity.java
new file mode 100644
index 0000000..e874eb9
--- /dev/null
+++ b/ext/entity-filtering/src/test/java/org/glassfish/jersey/message/filtering/SubEntity.java
@@ -0,0 +1,24 @@
+/*
+ * 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.message.filtering;
+
+/**
+ * @author Michal Gajdos
+ */
+public class SubEntity {
+
+}
diff --git a/ext/entity-filtering/src/test/java/org/glassfish/jersey/message/filtering/TestSecurityContext.java b/ext/entity-filtering/src/test/java/org/glassfish/jersey/message/filtering/TestSecurityContext.java
new file mode 100644
index 0000000..1e75312
--- /dev/null
+++ b/ext/entity-filtering/src/test/java/org/glassfish/jersey/message/filtering/TestSecurityContext.java
@@ -0,0 +1,52 @@
+/*
+ * 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.message.filtering;
+
+import java.security.Principal;
+
+import javax.ws.rs.core.SecurityContext;
+
+/**
+* @author Michal Gajdos
+*/
+class TestSecurityContext implements SecurityContext {
+
+    @Override
+    public Principal getUserPrincipal() {
+        return new Principal() {
+            @Override
+            public String getName() {
+                return "";
+            }
+        };
+    }
+
+    @Override
+    public boolean isUserInRole(final String role) {
+        return "manager".equals(role) || "user".equals(role);
+    }
+
+    @Override
+    public boolean isSecure() {
+        return false;
+    }
+
+    @Override
+    public String getAuthenticationScheme() {
+        return null;
+    }
+}
diff --git a/ext/metainf-services/pom.xml b/ext/metainf-services/pom.xml
new file mode 100644
index 0000000..8eef68a
--- /dev/null
+++ b/ext/metainf-services/pom.xml
@@ -0,0 +1,102 @@
+<?xml version="1.0"?>
+<!--
+
+    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
+
+-->
+
+<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.ext</groupId>
+        <artifactId>project</artifactId>
+        <version>2.28-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>jersey-metainf-services</artifactId>
+    <name>jersey-ext-metainf-services</name>
+
+    <description>
+        Jersey extension module enabling automatic registration of JAX-RS providers (MBW/MBR/EM) via META-INF/services mechanism.
+    </description>
+
+    <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>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <inherited>false</inherited>
+                <configuration>
+                    <source>${java.version}</source>
+                    <target>${java.version}</target>
+                    <compilerArguments>
+                        <!-- Do not warn about using sun.misc.Unsafe -->
+                        <XDignore.symbol.file />
+                    </compilerArguments>
+                    <showWarnings>false</showWarnings>
+                    <fork>false</fork>
+                </configuration>
+            </plugin>
+            <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>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.glassfish.jersey.core</groupId>
+            <artifactId>jersey-common</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+            <artifactId>jersey-test-framework-provider-bundle</artifactId>
+            <version>${project.version}</version>
+            <type>pom</type>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/ext/metainf-services/src/main/java/org/glassfish/jersey/spidiscovery/internal/MetaInfServicesAutoDiscoverable.java b/ext/metainf-services/src/main/java/org/glassfish/jersey/spidiscovery/internal/MetaInfServicesAutoDiscoverable.java
new file mode 100644
index 0000000..118acd3
--- /dev/null
+++ b/ext/metainf-services/src/main/java/org/glassfish/jersey/spidiscovery/internal/MetaInfServicesAutoDiscoverable.java
@@ -0,0 +1,56 @@
+/*
+ * 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.spidiscovery.internal;
+
+import java.util.Map;
+
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.core.FeatureContext;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.MessageBodyReader;
+import javax.ws.rs.ext.MessageBodyWriter;
+
+import javax.annotation.Priority;
+
+import org.glassfish.jersey.internal.ServiceFinderBinder;
+import org.glassfish.jersey.internal.inject.AbstractBinder;
+import org.glassfish.jersey.internal.spi.AutoDiscoverable;
+import org.glassfish.jersey.internal.spi.ForcedAutoDiscoverable;
+
+/**
+ * @author Michal Gajdos
+ */
+@Priority(AutoDiscoverable.DEFAULT_PRIORITY)
+public class MetaInfServicesAutoDiscoverable implements ForcedAutoDiscoverable {
+
+    @Override
+    public void configure(final FeatureContext context) {
+        final Map<String, Object> properties = context.getConfiguration().getProperties();
+        final RuntimeType runtimeType = context.getConfiguration().getRuntimeType();
+
+        context.register(new AbstractBinder() {
+            @Override
+            protected void configure() {
+                // Message Body providers.
+                install(new ServiceFinderBinder<MessageBodyReader>(MessageBodyReader.class, properties, runtimeType));
+                install(new ServiceFinderBinder<MessageBodyWriter>(MessageBodyWriter.class, properties, runtimeType));
+                // Exception Mappers.
+                install(new ServiceFinderBinder<ExceptionMapper>(ExceptionMapper.class, properties, runtimeType));
+            }
+        });
+    }
+}
diff --git a/ext/metainf-services/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.ForcedAutoDiscoverable b/ext/metainf-services/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.ForcedAutoDiscoverable
new file mode 100644
index 0000000..f4c3e99
--- /dev/null
+++ b/ext/metainf-services/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.ForcedAutoDiscoverable
@@ -0,0 +1 @@
+org.glassfish.jersey.spidiscovery.internal.MetaInfServicesAutoDiscoverable
diff --git a/ext/metainf-services/src/test/java/org/glassfish/jersey/message/MetaInfServicesTest.java b/ext/metainf-services/src/test/java/org/glassfish/jersey/message/MetaInfServicesTest.java
new file mode 100644
index 0000000..e764ef5
--- /dev/null
+++ b/ext/metainf-services/src/test/java/org/glassfish/jersey/message/MetaInfServicesTest.java
@@ -0,0 +1,172 @@
+/*
+ * 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.message;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.MessageBodyReader;
+import javax.ws.rs.ext.MessageBodyWriter;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.client.ClientProperties;
+import org.glassfish.jersey.message.internal.ReaderWriter;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.ServerProperties;
+import org.glassfish.jersey.test.JerseyTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+/**
+ * @author Michal Gajdos
+ */
+@RunWith(Suite.class)
+@Suite.SuiteClasses({MetaInfServicesTest.Enable.class, MetaInfServicesTest.DisableServer.class,
+        MetaInfServicesTest.DisableClient.class})
+public class MetaInfServicesTest {
+
+    public static class MetaInf {
+
+        private String value;
+
+        public MetaInf(final String value) {
+            this.value = value;
+        }
+    }
+
+    @Path("resource")
+    public static class MetaInfServicesResource {
+
+        @POST
+        public MetaInf post(final MetaInf entity) {
+            return entity;
+        }
+    }
+
+    public static class MessageProvider implements MessageBodyReader<MetaInf>, MessageBodyWriter<MetaInf> {
+
+        @Context
+        private Configuration config;
+
+        @Override
+        public boolean isReadable(final Class<?> type, final Type genericType, final Annotation[] annotations,
+                                  final MediaType mediaType) {
+            return true;
+        }
+
+        @Override
+        public MetaInf readFrom(final Class<MetaInf> type, final Type genericType, final Annotation[] annotations,
+                               final MediaType mediaType, final MultivaluedMap<String, String> httpHeaders,
+                               final InputStream entityStream) throws IOException, WebApplicationException {
+            return new MetaInf(ReaderWriter.readFromAsString(entityStream, mediaType)
+                    + "_read_" + config.getRuntimeType().name());
+        }
+
+        @Override
+        public boolean isWriteable(final Class<?> type, final Type genericType, final Annotation[] annotations,
+                                   final MediaType mediaType) {
+            return true;
+        }
+
+        @Override
+        public long getSize(final MetaInf s, final Class<?> type, final Type genericType, final Annotation[] annotations,
+                            final MediaType mediaType) {
+            return -1;
+        }
+
+        @Override
+        public void writeTo(final MetaInf s, final Class<?> type, final Type genericType, final Annotation[] annotations,
+                            final MediaType mediaType, final MultivaluedMap<String, Object> httpHeaders,
+                            final OutputStream entityStream) throws IOException, WebApplicationException {
+            entityStream.write((s.value + "_write_" + config.getRuntimeType().name()).getBytes());
+        }
+    }
+
+    public static class Enable extends JerseyTest {
+
+        @Override
+        protected Application configure() {
+            return new ResourceConfig(MetaInfServicesResource.class);
+        }
+
+        @Test
+        public void testEnable() throws Exception {
+            final Response response = target("resource").request().post(Entity.text(new MetaInf("foo")));
+
+            assertThat(response.getStatus(), is(200));
+            assertThat(response.readEntity(MetaInf.class).value,
+                    is("foo_write_CLIENT_read_SERVER_write_SERVER_read_CLIENT"));
+        }
+    }
+
+    public static class DisableServer extends JerseyTest {
+
+        @Override
+        protected Application configure() {
+            return new ResourceConfig(MetaInfServicesResource.class)
+                    .property(ServerProperties.FEATURE_AUTO_DISCOVERY_DISABLE, true);
+        }
+
+        @Test
+        public void testDisableServer() throws Exception {
+            final Response response = target("resource").request().post(Entity.text(new MetaInf("foo")));
+
+            assertThat(response.getStatus(), is(200));
+            assertThat(response.readEntity(MetaInf.class).value,
+                    is("foo_write_CLIENT_read_SERVER_write_SERVER_read_CLIENT"));
+        }
+    }
+
+    public static class DisableClient extends JerseyTest {
+
+        @Override
+        protected Application configure() {
+            return new ResourceConfig(MetaInfServicesResource.class);
+        }
+
+        @Override
+        protected void configureClient(final ClientConfig config) {
+            config.property(ClientProperties.FEATURE_AUTO_DISCOVERY_DISABLE, true);
+        }
+
+        @Test
+        public void testDisableServer() throws Exception {
+            final Response response = target("resource").request().post(Entity.text(new MetaInf("foo")));
+
+            assertThat(response.getStatus(), is(200));
+            assertThat(response.readEntity(MetaInf.class).value,
+                    is("foo_write_CLIENT_read_SERVER_write_SERVER_read_CLIENT"));
+        }
+    }
+}
diff --git a/ext/metainf-services/src/test/resources/META-INF/services/javax.ws.rs.ext.MessageBodyReader b/ext/metainf-services/src/test/resources/META-INF/services/javax.ws.rs.ext.MessageBodyReader
new file mode 100644
index 0000000..5be0cee
--- /dev/null
+++ b/ext/metainf-services/src/test/resources/META-INF/services/javax.ws.rs.ext.MessageBodyReader
@@ -0,0 +1 @@
+org.glassfish.jersey.message.MetaInfServicesTest$MessageProvider
\ No newline at end of file
diff --git a/ext/metainf-services/src/test/resources/META-INF/services/javax.ws.rs.ext.MessageBodyWriter b/ext/metainf-services/src/test/resources/META-INF/services/javax.ws.rs.ext.MessageBodyWriter
new file mode 100644
index 0000000..5be0cee
--- /dev/null
+++ b/ext/metainf-services/src/test/resources/META-INF/services/javax.ws.rs.ext.MessageBodyWriter
@@ -0,0 +1 @@
+org.glassfish.jersey.message.MetaInfServicesTest$MessageProvider
\ No newline at end of file
diff --git a/ext/mvc-bean-validation/pom.xml b/ext/mvc-bean-validation/pom.xml
new file mode 100644
index 0000000..4fa6f35
--- /dev/null
+++ b/ext/mvc-bean-validation/pom.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0"?>
+<!--
+
+    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>
+        <groupId>org.glassfish.jersey.ext</groupId>
+        <artifactId>project</artifactId>
+        <version>2.28-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>jersey-mvc-bean-validation</artifactId>
+    <name>jersey-ext-mvc-bean-validation</name>
+
+    <description>
+        Jersey extension module providing support for Bean Validation in MVC.
+    </description>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <inherited>true</inherited>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.glassfish.jersey.ext</groupId>
+            <artifactId>jersey-mvc</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.jersey.ext</groupId>
+            <artifactId>jersey-bean-validation</artifactId>
+            <version>${project.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>javax.el</groupId>
+                    <artifactId>javax.el-api</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/ext/mvc-bean-validation/src/main/java/org/glassfish/jersey/server/mvc/beanvalidation/MvcBeanValidationFeature.java b/ext/mvc-bean-validation/src/main/java/org/glassfish/jersey/server/mvc/beanvalidation/MvcBeanValidationFeature.java
new file mode 100644
index 0000000..9927536
--- /dev/null
+++ b/ext/mvc-bean-validation/src/main/java/org/glassfish/jersey/server/mvc/beanvalidation/MvcBeanValidationFeature.java
@@ -0,0 +1,68 @@
+/*
+ * 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.server.mvc.beanvalidation;
+
+import javax.ws.rs.ConstrainedTo;
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.Feature;
+import javax.ws.rs.core.FeatureContext;
+
+import org.glassfish.jersey.server.mvc.MvcFeature;
+import org.glassfish.jersey.server.validation.ValidationFeature;
+
+/**
+ * {@link Feature} used to add support for {@link MvcFeature MVC} and Bean Validation.
+ * <p/>
+ * {@link org.glassfish.jersey.server.mvc.Viewable Viewable} (template) defined by
+ * {@link org.glassfish.jersey.server.mvc.ErrorTemplate ErrorTemplate} annotation, present directly on an executed resource method
+ * or on a resource class the resource method is defined in, is processed to display an error message caused by an
+ * {@link javax.validation.ConstraintViolationException Bean Validation exception}. Model is, in this case, a list of
+ * {@link org.glassfish.jersey.server.validation.ValidationError validation errors}.
+ * <p/>
+ * Note: This feature also registers {@link MvcFeature}.
+ *
+ * @author Michal Gajdos
+ * @see org.glassfish.jersey.server.mvc.ErrorTemplate
+ * @since 2.3
+ */
+@ConstrainedTo(RuntimeType.SERVER)
+public class MvcBeanValidationFeature implements Feature {
+
+    @Override
+    public boolean configure(final FeatureContext context) {
+        final Configuration config = context.getConfiguration();
+
+        if (!config.isRegistered(ValidationErrorTemplateExceptionMapper.class)) {
+            // Exception Mapper.
+            context.register(ValidationErrorTemplateExceptionMapper.class);
+
+            // BeanValidation feature.
+            if (!config.isRegistered(ValidationFeature.class)) {
+                context.register(ValidationFeature.class);
+            }
+
+            // Mvc feature.
+            if (!config.isRegistered(MvcFeature.class)) {
+                context.register(MvcFeature.class);
+            }
+
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/ext/mvc-bean-validation/src/main/java/org/glassfish/jersey/server/mvc/beanvalidation/ValidationErrorTemplateExceptionMapper.java b/ext/mvc-bean-validation/src/main/java/org/glassfish/jersey/server/mvc/beanvalidation/ValidationErrorTemplateExceptionMapper.java
new file mode 100644
index 0000000..caba9cc
--- /dev/null
+++ b/ext/mvc-bean-validation/src/main/java/org/glassfish/jersey/server/mvc/beanvalidation/ValidationErrorTemplateExceptionMapper.java
@@ -0,0 +1,47 @@
+/*
+ * 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.server.mvc.beanvalidation;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.Provider;
+
+import javax.inject.Singleton;
+import javax.validation.ConstraintViolationException;
+
+import org.glassfish.jersey.server.mvc.spi.AbstractErrorTemplateMapper;
+import org.glassfish.jersey.server.validation.internal.ValidationHelper;
+
+/**
+ * {@link org.glassfish.jersey.spi.ExtendedExceptionMapper Exception mapper} providing validation errors that are passed to a
+ * viewable in case a Bean Validation exception has been thrown during processing an request.
+ *
+ * @author Michal Gajdos
+ * @since 2.3
+ */
+@Singleton
+final class ValidationErrorTemplateExceptionMapper extends AbstractErrorTemplateMapper<ConstraintViolationException> {
+
+    @Override
+    protected Response.Status getErrorStatus(final ConstraintViolationException cve) {
+        return ValidationHelper.getResponseStatus(cve);
+    }
+
+    @Override
+    protected Object getErrorModel(final ConstraintViolationException cve) {
+        return ValidationHelper.constraintViolationToValidationErrors(cve);
+    }
+}
diff --git a/ext/mvc-bean-validation/src/main/java/org/glassfish/jersey/server/mvc/beanvalidation/package-info.java b/ext/mvc-bean-validation/src/main/java/org/glassfish/jersey/server/mvc/beanvalidation/package-info.java
new file mode 100644
index 0000000..8288c8e
--- /dev/null
+++ b/ext/mvc-bean-validation/src/main/java/org/glassfish/jersey/server/mvc/beanvalidation/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 server-side classes adding support of bean validation to MVC (Model View Controller).
+ */
+package org.glassfish.jersey.server.mvc.beanvalidation;
diff --git a/ext/mvc-freemarker/pom.xml b/ext/mvc-freemarker/pom.xml
new file mode 100644
index 0000000..73b152d
--- /dev/null
+++ b/ext/mvc-freemarker/pom.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0"?>
+<!--
+
+    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.ext</groupId>
+        <artifactId>project</artifactId>
+        <version>2.28-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>jersey-mvc-freemarker</artifactId>
+    <name>jersey-ext-mvc-freemarker</name>
+
+    <description>
+        Jersey extension module providing support for Freemarker templates.
+    </description>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <inherited>true</inherited>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Export-Package>org.glassfish.jersey.server.mvc.freemarker.*;version=${project.version}</Export-Package>
+                    </instructions>
+                    <unpackBundle>true</unpackBundle>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.glassfish.jersey.ext</groupId>
+            <artifactId>jersey-mvc</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.freemarker</groupId>
+            <artifactId>freemarker</artifactId>
+            <version>${freemarker.version}</version>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/ext/mvc-freemarker/src/main/java/org/glassfish/jersey/server/mvc/freemarker/FreemarkerConfigurationFactory.java b/ext/mvc-freemarker/src/main/java/org/glassfish/jersey/server/mvc/freemarker/FreemarkerConfigurationFactory.java
new file mode 100644
index 0000000..d62bf18
--- /dev/null
+++ b/ext/mvc-freemarker/src/main/java/org/glassfish/jersey/server/mvc/freemarker/FreemarkerConfigurationFactory.java
@@ -0,0 +1,39 @@
+/*
+ * 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.server.mvc.freemarker;
+
+import freemarker.template.Configuration;
+
+
+/**
+ * Provides lookup of {@link freemarker.template.Configuration Configuration}
+ * instance for Freemarker templating.
+ * </p>
+ * Instantiation of Configuration objects is relatively heavy-weight, and
+ * Freemarker best-practices dictate that they be reused if possible.
+ * Therefore, most implementations of this interface will only create a
+ * singleton Configuration instance, and return it for every call to
+ * {@link #getConfiguration()}. Although this will usually be the case, it is
+ * not a guarantee of this interface's contract.
+ *
+ * @author Jeff Wilde (jeff.wilde at complicatedrobot.com)
+ */
+public interface FreemarkerConfigurationFactory {
+
+    public Configuration getConfiguration();
+
+}
diff --git a/ext/mvc-freemarker/src/main/java/org/glassfish/jersey/server/mvc/freemarker/FreemarkerDefaultConfigurationFactory.java b/ext/mvc-freemarker/src/main/java/org/glassfish/jersey/server/mvc/freemarker/FreemarkerDefaultConfigurationFactory.java
new file mode 100644
index 0000000..7ec7fed
--- /dev/null
+++ b/ext/mvc-freemarker/src/main/java/org/glassfish/jersey/server/mvc/freemarker/FreemarkerDefaultConfigurationFactory.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.server.mvc.freemarker;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.servlet.ServletContext;
+
+import freemarker.cache.ClassTemplateLoader;
+import freemarker.cache.FileTemplateLoader;
+import freemarker.cache.MultiTemplateLoader;
+import freemarker.cache.TemplateLoader;
+import freemarker.cache.WebappTemplateLoader;
+import freemarker.template.Configuration;
+
+/**
+ * Handy {@link FreemarkerConfigurationFactory} that supplies a minimally
+ * configured {@link freemarker.template.Configuration Configuration} able to
+ * create {@link freemarker.template.Template Freemarker templates}.
+ * The recommended method to provide custom Freemarker configuration is to
+ * sub-class this class, further customize the
+ * {@link freemarker.template.Configuration configuration} as desired in that
+ * class, and then register the sub-class with the {@link FreemarkerMvcFeature}
+ * TEMPLATE_OBJECT_FACTORY property.
+ *
+ * @author Jeff Wilde (jeff.wilde at complicatedrobot.com)
+ */
+public class FreemarkerDefaultConfigurationFactory implements FreemarkerConfigurationFactory {
+
+    protected final Configuration configuration;
+
+    public FreemarkerDefaultConfigurationFactory(ServletContext servletContext) {
+        super();
+
+        // Create different loaders.
+        final List<TemplateLoader> loaders = new ArrayList<>();
+        if (servletContext != null) {
+            loaders.add(new WebappTemplateLoader(servletContext));
+        }
+        loaders.add(new ClassTemplateLoader(FreemarkerDefaultConfigurationFactory.class, "/"));
+        try {
+            loaders.add(new FileTemplateLoader(new File("/")));
+        } catch (IOException e) {
+            // NOOP
+        }
+
+        // Create Base configuration.
+        configuration = new Configuration();
+        configuration.setTemplateLoader(new MultiTemplateLoader(loaders.toArray(new TemplateLoader[loaders.size()])));
+
+    }
+
+    @Override
+    public Configuration getConfiguration() {
+        return configuration;
+    }
+
+}
diff --git a/ext/mvc-freemarker/src/main/java/org/glassfish/jersey/server/mvc/freemarker/FreemarkerMvcFeature.java b/ext/mvc-freemarker/src/main/java/org/glassfish/jersey/server/mvc/freemarker/FreemarkerMvcFeature.java
new file mode 100644
index 0000000..2dab451
--- /dev/null
+++ b/ext/mvc-freemarker/src/main/java/org/glassfish/jersey/server/mvc/freemarker/FreemarkerMvcFeature.java
@@ -0,0 +1,123 @@
+/*
+ * 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.server.mvc.freemarker;
+
+import javax.ws.rs.ConstrainedTo;
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.Feature;
+import javax.ws.rs.core.FeatureContext;
+
+import org.glassfish.jersey.server.mvc.MvcFeature;
+
+/**
+ * {@link Feature} used to add support for {@link MvcFeature MVC} and Freemarker templates.
+ * <p/>
+ * Note: This feature also registers {@link MvcFeature}.
+ *
+ * @author Michal Gajdos
+ * @author Jeff Wilde (jeff.wilde at complicatedrobot.com)
+ */
+@ConstrainedTo(RuntimeType.SERVER)
+public final class FreemarkerMvcFeature implements Feature {
+
+    private static final String SUFFIX = ".freemarker";
+
+    /**
+     * {@link String} property defining the base path to Freemarker templates. If set, the value of the property is added in front
+     * of the template name defined in:
+     * <ul>
+     * <li>{@link org.glassfish.jersey.server.mvc.Viewable Viewable}</li>
+     * <li>{@link org.glassfish.jersey.server.mvc.Template Template}, or</li>
+     * <li>{@link org.glassfish.jersey.server.mvc.ErrorTemplate ErrorTemplate}</li>
+     * </ul>
+     * <p/>
+     * Value can be absolute providing a full path to a system directory with templates or relative to current
+     * {@link javax.servlet.ServletContext servlet context}.
+     * <p/>
+     * There is no default value.
+     * <p/>
+     * The name of the configuration property is <tt>{@value}</tt>.
+     */
+    public static final String TEMPLATE_BASE_PATH = MvcFeature.TEMPLATE_BASE_PATH + SUFFIX;
+
+    /**
+     * If {@code true} then enable caching of Freemarker templates to avoid multiple compilation.
+     * <p/>
+     * The default value is {@code false}.
+     * <p/>
+     * The name of the configuration property is <tt>{@value}</tt>.
+     *
+     * @since 2.5
+     */
+    public static final String CACHE_TEMPLATES = MvcFeature.CACHE_TEMPLATES + SUFFIX;
+
+    /**
+     * Property used to pass user-configured {@link org.glassfish.jersey.server.mvc.freemarker.FreemarkerConfigurationFactory}.
+     * <p/>
+     * The default value is not set.
+     * <p/>
+     * The name of the configuration property is <tt>{@value}</tt>.
+     * <p/>
+     * This property will also accept an instance of {@link freemarker.template.Configuration Configuration} directly, to
+     * support backwards compatibility. If you want to set custom {@link freemarker.template.Configuration configuration} then set
+     * {@link freemarker.cache.TemplateLoader template loader} to multi loader of:
+     * {@link freemarker.cache.WebappTemplateLoader} (if applicable), {@link freemarker.cache.ClassTemplateLoader} and
+     * {@link freemarker.cache.FileTemplateLoader} keep functionality of resolving templates.
+     * <p/>
+     * If no value is set, a {@link org.glassfish.jersey.server.mvc.freemarker.FreemarkerDefaultConfigurationFactory factory}
+     * with the above behaviour is used by default in the
+     * {@link org.glassfish.jersey.server.mvc.freemarker.FreemarkerViewProcessor} class.
+     * <p/>
+     *
+     * @since 2.5
+     */
+    public static final String TEMPLATE_OBJECT_FACTORY = MvcFeature.TEMPLATE_OBJECT_FACTORY + SUFFIX;
+
+    /**
+     * Property defines output encoding produced by {@link org.glassfish.jersey.server.mvc.spi.TemplateProcessor}.
+     * The value must be a valid encoding defined that can be passed
+     * to the {@link java.nio.charset.Charset#forName(String)} method.
+     *
+     * <p/>
+     * The default value is {@code UTF-8}.
+     * <p/>
+     * The name of the configuration property is <tt>{@value}</tt>.
+     * <p/>
+     *
+     * @since 2.7
+     */
+    public static final String ENCODING = MvcFeature.ENCODING + SUFFIX;
+
+    @Override
+    public boolean configure(final FeatureContext context) {
+        final Configuration config = context.getConfiguration();
+
+        if (!config.isRegistered(FreemarkerViewProcessor.class)) {
+            // Template Processor.
+            context.register(FreemarkerViewProcessor.class);
+
+            // MvcFeature.
+            if (!config.isRegistered(MvcFeature.class)) {
+                context.register(MvcFeature.class);
+            }
+
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/ext/mvc-freemarker/src/main/java/org/glassfish/jersey/server/mvc/freemarker/FreemarkerSuppliedConfigurationFactory.java b/ext/mvc-freemarker/src/main/java/org/glassfish/jersey/server/mvc/freemarker/FreemarkerSuppliedConfigurationFactory.java
new file mode 100644
index 0000000..35a2dcb
--- /dev/null
+++ b/ext/mvc-freemarker/src/main/java/org/glassfish/jersey/server/mvc/freemarker/FreemarkerSuppliedConfigurationFactory.java
@@ -0,0 +1,47 @@
+/*
+ * 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.server.mvc.freemarker;
+
+
+import freemarker.template.Configuration;
+
+/**
+ * {@link FreemarkerConfigurationFactory} that supplies an unchanged
+ * {@link freemarker.template.Configuration Configuration} as passed-in to
+ * the constructor.
+ * <p/>
+ * Used to support backwards-compatibility in {@link FreemarkerViewProcessor}
+ * to wrap directly-configured {@link freemarker.template.Configuration Configuration}
+ * objects instead of the recommended {@link FreemarkerDefaultConfigurationFactory}
+ * or a sub-class thereof.
+ *
+ * @author Jeff Wilde (jeff.wilde at complicatedrobot.com)
+ */
+final class FreemarkerSuppliedConfigurationFactory implements FreemarkerConfigurationFactory {
+
+    private final Configuration configuration;
+
+    public FreemarkerSuppliedConfigurationFactory(Configuration configuration) {
+        this.configuration = configuration;
+    }
+
+    @Override
+    public Configuration getConfiguration() {
+        return configuration;
+    }
+
+}
diff --git a/ext/mvc-freemarker/src/main/java/org/glassfish/jersey/server/mvc/freemarker/FreemarkerViewProcessor.java b/ext/mvc-freemarker/src/main/java/org/glassfish/jersey/server/mvc/freemarker/FreemarkerViewProcessor.java
new file mode 100644
index 0000000..164f4ab
--- /dev/null
+++ b/ext/mvc-freemarker/src/main/java/org/glassfish/jersey/server/mvc/freemarker/FreemarkerViewProcessor.java
@@ -0,0 +1,99 @@
+/*
+ * 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.server.mvc.freemarker;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.nio.charset.Charset;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+
+import javax.inject.Inject;
+import javax.servlet.ServletContext;
+
+import org.glassfish.jersey.internal.inject.InjectionManager;
+import org.glassfish.jersey.internal.util.collection.Values;
+import org.glassfish.jersey.server.ContainerException;
+import org.glassfish.jersey.server.mvc.Viewable;
+import org.glassfish.jersey.server.mvc.spi.AbstractTemplateProcessor;
+
+import freemarker.template.Configuration;
+import freemarker.template.Template;
+import freemarker.template.TemplateException;
+
+/**
+ * {@link org.glassfish.jersey.server.mvc.spi.TemplateProcessor Template processor} providing support for Freemarker templates.
+ *
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ * @author Michal Gajdos
+ * @author Jeff Wilde (jeff.wilde at complicatedrobot.com)
+ */
+final class FreemarkerViewProcessor extends AbstractTemplateProcessor<Template> {
+
+    private final FreemarkerConfigurationFactory factory;
+
+    /**
+     * Create an instance of this processor with injected {@link javax.ws.rs.core.Configuration config} and
+     * (optional) {@link javax.servlet.ServletContext servlet context}.
+     *
+     * @param config           config to configure this processor from.
+     * @param injectionManager injection manager.
+     */
+    @Inject
+    public FreemarkerViewProcessor(javax.ws.rs.core.Configuration config, InjectionManager injectionManager) {
+        super(config, injectionManager.getInstance(ServletContext.class), "freemarker", "ftl");
+
+        this.factory = getTemplateObjectFactory(injectionManager::createAndInitialize, FreemarkerConfigurationFactory.class,
+                () -> {
+                    Configuration configuration =
+                            getTemplateObjectFactory(injectionManager::createAndInitialize, Configuration.class, Values.empty());
+                    if (configuration == null) {
+                        return new FreemarkerDefaultConfigurationFactory(injectionManager.getInstance(ServletContext.class));
+                    } else {
+                        return new FreemarkerSuppliedConfigurationFactory(configuration);
+                    }
+                });
+    }
+
+    @Override
+    protected Template resolve(final String templateReference, final Reader reader) throws Exception {
+        return factory.getConfiguration().getTemplate(templateReference);
+    }
+
+    @Override
+    public void writeTo(final Template template, final Viewable viewable, final MediaType mediaType,
+                        final MultivaluedMap<String, Object> httpHeaders, final OutputStream out) throws IOException {
+        try {
+            Object model = viewable.getModel();
+            if (!(model instanceof Map)) {
+                model = new HashMap<String, Object>() {{
+                    put("model", viewable.getModel());
+                }};
+            }
+            Charset encoding = setContentType(mediaType, httpHeaders);
+
+            template.process(model, new OutputStreamWriter(out, encoding));
+        } catch (TemplateException te) {
+            throw new ContainerException(te);
+        }
+    }
+}
diff --git a/ext/mvc-freemarker/src/main/java/org/glassfish/jersey/server/mvc/freemarker/package-info.java b/ext/mvc-freemarker/src/main/java/org/glassfish/jersey/server/mvc/freemarker/package-info.java
new file mode 100644
index 0000000..26762f3
--- /dev/null
+++ b/ext/mvc-freemarker/src/main/java/org/glassfish/jersey/server/mvc/freemarker/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 server-side classes adding support of FreeMarker to Jersey MVC (Model View Controller).
+ */
+package org.glassfish.jersey.server.mvc.freemarker;
diff --git a/ext/mvc-jsp/pom.xml b/ext/mvc-jsp/pom.xml
new file mode 100644
index 0000000..e76f2f5
--- /dev/null
+++ b/ext/mvc-jsp/pom.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0"?>
+<!--
+
+    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.ext</groupId>
+        <artifactId>project</artifactId>
+        <version>2.28-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>jersey-mvc-jsp</artifactId>
+    <name>jersey-ext-mvc-jsp</name>
+
+    <description>
+        Jersey extension module providing support for JSP templates.
+    </description>
+
+    <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>
+                        <Import-Package>
+                            javax.servlet.jsp.*;version="${range;[==,+);${jsp.version}}",
+                            *
+                        </Import-Package>
+                        <Export-Package>org.glassfish.jersey.server.mvc.jsp.*;version=${project.version}</Export-Package>
+                    </instructions>
+                    <unpackBundle>true</unpackBundle>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.glassfish.hk2.external</groupId>
+            <artifactId>javax.inject</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.jersey.containers</groupId>
+            <artifactId>jersey-container-servlet-core</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.jersey.ext</groupId>
+            <artifactId>jersey-mvc</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>servlet-api</artifactId>
+            <version>${servlet2.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet.jsp</groupId>
+            <artifactId>jsp-api</artifactId>
+            <version>${jsp.version}</version>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/ext/mvc-jsp/src/main/java/org/glassfish/jersey/server/mvc/jsp/Include.java b/ext/mvc-jsp/src/main/java/org/glassfish/jersey/server/mvc/jsp/Include.java
new file mode 100644
index 0000000..90b6e1b
--- /dev/null
+++ b/ext/mvc-jsp/src/main/java/org/glassfish/jersey/server/mvc/jsp/Include.java
@@ -0,0 +1,131 @@
+/*
+ * 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.server.mvc.jsp;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+import javax.servlet.jsp.JspContext;
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.PageContext;
+import javax.servlet.jsp.tagext.SimpleTagSupport;
+
+import org.glassfish.jersey.server.mvc.internal.TemplateHelper;
+import org.glassfish.jersey.server.mvc.jsp.internal.LocalizationMessages;
+
+/**
+ * Includes a side JSP file for the {@code resolvingClass} class.
+ * <p/>
+ * This tag looks for a side JSP file of the given name
+ * from the inheritance hierarchy of the "resolvingClass" class,
+ * and includes the contents of it, just like &lt;jsp:include>.
+ * <p/>
+ * For example, if the {@code resolvingClass} class is the {@code Foo} class,
+ * which looks like the following:
+ * <pre>
+ * class Foo extends Bar { ... }
+ * class Bar extends Zot { ... }
+ * </pre>
+ * <p/>
+ * And if you write:
+ * <pre>
+ * &lt;st:include page="abc.jsp"/&gt;
+ * </pre>
+ * then, it looks for the following files in this order,
+ * and includes the first one found.
+ * <ol>
+ * <li>a side-file of the {@code Foo} class named {@code abc.jsp} ({@code /WEB-INF/Foo/abc.jsp})
+ * <li>a side-file of the {@code Bar} class named {@code abc.jsp} ({@code /WEB-INF/Bar/abc.jsp})
+ * <li>a side-file of the {@code Zot} class named {@code abc.jsp} ({@code /WEB-INF/Zot/abc.jsp})
+ * </ol>
+ *
+ * @author Kohsuke Kawaguchi
+ * @author Paul Sandoz
+ */
+public class Include extends SimpleTagSupport {
+
+    private String page;
+
+    /**
+     * Specifies the name of the JSP to be included.
+     *
+     * @param page page to be included.
+     */
+    public void setPage(String page) {
+        this.page = page;
+    }
+
+    private Object getPageObject(String name) {
+        return getJspContext().getAttribute(name, PageContext.PAGE_SCOPE);
+    }
+
+    public void doTag() throws JspException, IOException {
+        final JspContext jspContext = getJspContext();
+        final Class<?> resolvingClass = (Class<?>) jspContext
+                .getAttribute(RequestDispatcherWrapper.RESOLVING_CLASS_ATTRIBUTE_NAME, PageContext.REQUEST_SCOPE);
+        final String basePath = (String) jspContext.getAttribute(RequestDispatcherWrapper.BASE_PATH_ATTRIBUTE_NAME,
+                PageContext.REQUEST_SCOPE);
+
+        final ServletConfig servletConfig = (ServletConfig) getPageObject(PageContext.CONFIG);
+        final ServletContext servletContext = servletConfig.getServletContext();
+
+        for (Class<?> clazz = resolvingClass; clazz != Object.class; clazz = clazz.getSuperclass()) {
+            final String template = basePath + TemplateHelper.getAbsolutePath(clazz, page, '/');
+
+            if (servletContext.getResource(template) != null) {
+                // Tomcat returns a RequestDispatcher even if the JSP file doesn't exist so check if the resource exists first.
+                final RequestDispatcher dispatcher = servletContext.getRequestDispatcher(template);
+
+                if (dispatcher != null) {
+                    try {
+                        final HttpServletRequest request = (HttpServletRequest) getPageObject(PageContext.REQUEST);
+                        final HttpServletResponse response = (HttpServletResponse) getPageObject(PageContext.RESPONSE);
+
+                        dispatcher.include(request,
+                                new Wrapper(response, new PrintWriter(jspContext.getOut())));
+                    } catch (ServletException e) {
+                        throw new JspException(e);
+                    }
+                    return;
+                }
+            }
+        }
+
+        throw new JspException(LocalizationMessages.UNABLE_TO_FIND_PAGE_FOR_RESOLVING_CLASS(page, resolvingClass));
+    }
+
+    class Wrapper extends HttpServletResponseWrapper {
+
+        private final PrintWriter writer;
+
+        Wrapper(HttpServletResponse httpServletResponse, PrintWriter w) {
+            super(httpServletResponse);
+            this.writer = w;
+        }
+
+        public PrintWriter getWriter() throws IOException {
+            return writer;
+        }
+    }
+}
diff --git a/ext/mvc-jsp/src/main/java/org/glassfish/jersey/server/mvc/jsp/JspMvcFeature.java b/ext/mvc-jsp/src/main/java/org/glassfish/jersey/server/mvc/jsp/JspMvcFeature.java
new file mode 100644
index 0000000..3b70480
--- /dev/null
+++ b/ext/mvc-jsp/src/main/java/org/glassfish/jersey/server/mvc/jsp/JspMvcFeature.java
@@ -0,0 +1,73 @@
+/*
+ * 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.server.mvc.jsp;
+
+import javax.ws.rs.ConstrainedTo;
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.Feature;
+import javax.ws.rs.core.FeatureContext;
+
+import org.glassfish.jersey.server.mvc.MvcFeature;
+
+/**
+ * {@link Feature} used to add support for {@link MvcFeature MVC} and JSP templates.
+ * <p/>
+ * Note: This feature also registers {@link MvcFeature}.
+ *
+ * @author Michal Gajdos
+ */
+@ConstrainedTo(RuntimeType.SERVER)
+public final class JspMvcFeature implements Feature {
+
+    private static final String SUFFIX = ".jsp";
+
+    /**
+     * {@link String} property defining the base path to JSP templates. If set, the value of the property is added in front
+     * of the template name defined in:
+     * <ul>
+     * <li>{@link org.glassfish.jersey.server.mvc.Viewable Viewable}</li>
+     * <li>{@link org.glassfish.jersey.server.mvc.Template Template}, or</li>
+     * <li>{@link org.glassfish.jersey.server.mvc.ErrorTemplate ErrorTemplate}</li>
+     * </ul>
+     * <p/>
+     * Value can be absolute or relative to current {@link javax.servlet.ServletContext servlet context}.
+     * <p/>
+     * There is no default value.
+     * <p/>
+     * The name of the configuration property is <tt>{@value}</tt>.
+     */
+    public static final String TEMPLATE_BASE_PATH = MvcFeature.TEMPLATE_BASE_PATH + SUFFIX;
+
+    @Override
+    public boolean configure(final FeatureContext context) {
+        final Configuration config = context.getConfiguration();
+
+        if (!config.isRegistered(JspTemplateProcessor.class)) {
+            // Template Processor.
+            context.register(JspTemplateProcessor.class);
+
+            // MvcFeature.
+            if (!config.isRegistered(MvcFeature.class)) {
+                context.register(MvcFeature.class);
+            }
+
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/ext/mvc-jsp/src/main/java/org/glassfish/jersey/server/mvc/jsp/JspTemplateProcessor.java b/ext/mvc-jsp/src/main/java/org/glassfish/jersey/server/mvc/jsp/JspTemplateProcessor.java
new file mode 100644
index 0000000..755762f
--- /dev/null
+++ b/ext/mvc-jsp/src/main/java/org/glassfish/jersey/server/mvc/jsp/JspTemplateProcessor.java
@@ -0,0 +1,167 @@
+/*
+ * 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.server.mvc.jsp;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.Reader;
+
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+
+import javax.inject.Inject;
+import javax.inject.Provider;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+
+import org.glassfish.jersey.internal.util.collection.Ref;
+import org.glassfish.jersey.message.internal.TracingLogger;
+import org.glassfish.jersey.server.ContainerException;
+import org.glassfish.jersey.server.ContainerRequest;
+import org.glassfish.jersey.server.mvc.Viewable;
+import org.glassfish.jersey.server.mvc.jsp.internal.LocalizationMessages;
+import org.glassfish.jersey.server.mvc.spi.AbstractTemplateProcessor;
+import org.glassfish.jersey.server.mvc.spi.ResolvedViewable;
+
+/**
+ * A JSP template processor able to process resources obtained through {@link ServletContext servlet context}. This template
+ * processor does not support caching of template reference objects (in Jersey) or passing custom template object factory.
+ *
+ * @author Paul Sandoz
+ * @author Michal Gajdos
+ */
+final class JspTemplateProcessor extends AbstractTemplateProcessor<String> {
+
+    @Inject
+    private Provider<Ref<HttpServletRequest>> requestProviderRef;
+    @Inject
+    private Provider<Ref<HttpServletResponse>> responseProviderRef;
+    @Inject
+    private Provider<ContainerRequest> containerRequestProvider;
+
+    /**
+     * Create an instance of this processor with injected {@link Configuration config} and
+     * (optional) {@link ServletContext servlet context}.
+     *
+     * @param config configuration to configure this processor from.
+     * @param servletContext (optional) servlet context to obtain template resources from.
+     */
+    @Inject
+    public JspTemplateProcessor(final Configuration config, final ServletContext servletContext) {
+        super(config, servletContext, "jsp", "jsp");
+    }
+
+    @Override
+    protected String resolve(final String templatePath, final Reader reader) throws Exception {
+        return templatePath;
+    }
+
+    @Override
+    public void writeTo(final String templateReference, final Viewable viewable, final MediaType mediaType,
+                        final MultivaluedMap<String, Object> httpHeaders, final OutputStream out) throws IOException {
+
+        if (!(viewable instanceof ResolvedViewable)) {
+            // This should not happen with default MVC message body writer implementation
+            throw new IllegalArgumentException(LocalizationMessages.ERROR_VIEWABLE_INCORRECT_INSTANCE());
+        }
+
+        // SPI could supply instance of ResolvedViewable but we would like to keep the backward
+        // compatibility, so the cast is here.
+        final ResolvedViewable resolvedViewable = (ResolvedViewable) viewable;
+
+
+        final TracingLogger tracingLogger = TracingLogger.getInstance(containerRequestProvider.get().getPropertiesDelegate());
+        if (tracingLogger.isLogEnabled(MvcJspEvent.JSP_FORWARD)) {
+            tracingLogger.log(MvcJspEvent.JSP_FORWARD, templateReference, resolvedViewable.getModel());
+        }
+
+        final RequestDispatcher dispatcher = getServletContext().getRequestDispatcher(templateReference);
+        if (dispatcher == null) {
+            throw new ContainerException(LocalizationMessages.NO_REQUEST_DISPATCHER_FOR_RESOLVED_PATH(templateReference));
+        }
+
+        final RequestDispatcher wrapper = new RequestDispatcherWrapper(dispatcher, getBasePath(), resolvedViewable);
+
+        // OutputStream and Writer for HttpServletResponseWrapper.
+        final ServletOutputStream responseStream = new ServletOutputStream() {
+            @Override
+            public void write(final int b) throws IOException {
+                out.write(b);
+            }
+        };
+        final PrintWriter responseWriter = new PrintWriter(new OutputStreamWriter(responseStream, getEncoding()));
+
+        try {
+            wrapper.forward(requestProviderRef.get().get(), new HttpServletResponseWrapper(responseProviderRef.get().get()) {
+
+                @Override
+                public ServletOutputStream getOutputStream() throws IOException {
+                    return responseStream;
+                }
+
+                @Override
+                public PrintWriter getWriter() throws IOException {
+                    return responseWriter;
+                }
+            });
+        } catch (final Exception e) {
+            throw new ContainerException(e);
+        } finally {
+            responseWriter.flush();
+        }
+    }
+
+    /**
+     * MVC-JSP side tracing events.
+     */
+    private static enum MvcJspEvent implements TracingLogger.Event {
+        JSP_FORWARD(TracingLogger.Level.SUMMARY, "MVC", "Forwarding view to JSP page [%s], model %s");
+
+        private final TracingLogger.Level level;
+        private final String category;
+        private final String messageFormat;
+
+        private MvcJspEvent(final TracingLogger.Level level, final String category, final String messageFormat) {
+            this.level = level;
+            this.category = category;
+            this.messageFormat = messageFormat;
+        }
+
+        @Override
+        public String category() {
+            return category;
+        }
+
+        @Override
+        public TracingLogger.Level level() {
+            return level;
+        }
+
+        @Override
+        public String messageFormat() {
+            return messageFormat;
+        }
+    }
+
+}
diff --git a/ext/mvc-jsp/src/main/java/org/glassfish/jersey/server/mvc/jsp/RequestDispatcherWrapper.java b/ext/mvc-jsp/src/main/java/org/glassfish/jersey/server/mvc/jsp/RequestDispatcherWrapper.java
new file mode 100644
index 0000000..761ba90
--- /dev/null
+++ b/ext/mvc-jsp/src/main/java/org/glassfish/jersey/server/mvc/jsp/RequestDispatcherWrapper.java
@@ -0,0 +1,89 @@
+/*
+ * 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.server.mvc.jsp;
+
+import java.io.IOException;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+import org.glassfish.jersey.server.mvc.spi.ResolvedViewable;
+
+/**
+ * {@link RequestDispatcher Request dispatcher wrapper} for setting attributes (e.g. {@code it}).
+ *
+ * @author Paul Sandoz
+ * @author Michal Gajdos
+ */
+final class RequestDispatcherWrapper implements RequestDispatcher {
+
+    static final String BASE_PATH_ATTRIBUTE_NAME = "_basePath";
+    static final String OLD_MODEL_ATTRIBUTE_NAME = "it";
+    static final String MODEL_ATTRIBUTE_NAME = "model";
+    static final String RESOLVING_CLASS_ATTRIBUTE_NAME = "resolvingClass";
+    static final String REQUEST_ATTRIBUTE_NAME = "_request";
+    static final String RESPONSE_ATTRIBUTE_NAME = "_response";
+
+    private final RequestDispatcher dispatcher;
+
+    private final String basePath;
+
+    private final ResolvedViewable viewable;
+
+    /**
+     * Creates new {@code RequestDispatcherWrapper} responsible for setting request attributes and forwarding the processing to
+     * the given dispatcher.
+     *
+     * @param dispatcher dispatcher processing the request after all the request attributes were set.
+     * @param basePath base path of all JSP set to {@value #BASE_PATH_ATTRIBUTE_NAME} request attribute.
+     * @param viewable viewable to obtain model and resolving class from.
+     */
+    public RequestDispatcherWrapper(
+            final RequestDispatcher dispatcher, final String basePath, final ResolvedViewable viewable) {
+        this.dispatcher = dispatcher;
+        this.basePath = basePath;
+        this.viewable = viewable;
+    }
+
+    @Override
+    public void forward(final ServletRequest request, final ServletResponse response) throws ServletException, IOException {
+        final Object oldIt = request.getAttribute(MODEL_ATTRIBUTE_NAME);
+        final Object oldResolvingClass = request.getAttribute(RESOLVING_CLASS_ATTRIBUTE_NAME);
+
+        request.setAttribute(RESOLVING_CLASS_ATTRIBUTE_NAME, viewable.getResolvingClass());
+
+        request.setAttribute(OLD_MODEL_ATTRIBUTE_NAME, viewable.getModel());
+        request.setAttribute(MODEL_ATTRIBUTE_NAME, viewable.getModel());
+
+        request.setAttribute(BASE_PATH_ATTRIBUTE_NAME, basePath);
+        request.setAttribute(REQUEST_ATTRIBUTE_NAME, request);
+        request.setAttribute(RESPONSE_ATTRIBUTE_NAME, response);
+
+        dispatcher.forward(request, response);
+
+        request.setAttribute(RESOLVING_CLASS_ATTRIBUTE_NAME, oldResolvingClass);
+        request.setAttribute(MODEL_ATTRIBUTE_NAME, oldIt);
+    }
+
+    @Override
+    public void include(final ServletRequest request, final ServletResponse response) throws ServletException, IOException {
+        throw new UnsupportedOperationException();
+    }
+
+}
diff --git a/ext/mvc-jsp/src/main/java/org/glassfish/jersey/server/mvc/jsp/package-info.java b/ext/mvc-jsp/src/main/java/org/glassfish/jersey/server/mvc/jsp/package-info.java
new file mode 100644
index 0000000..23c4326
--- /dev/null
+++ b/ext/mvc-jsp/src/main/java/org/glassfish/jersey/server/mvc/jsp/package-info.java
@@ -0,0 +1,26 @@
+/*
+ * 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 server-side MVC support for servlet containers.
+ * <p/>
+ * Note: Jersey applications that want to use MVC features should be registered as filters instead of servlets in web.xml (to
+ * fully take advantage of {@link org.glassfish.jersey.server.mvc.jsp.JspMvcFeature}). Web.xml-less deployment of an application
+ * using MVC is not supported at the moment.
+ *
+ * @see org.glassfish.jersey.server.mvc.jsp.JspTemplateProcessor
+ */
+package org.glassfish.jersey.server.mvc.jsp;
diff --git a/ext/mvc-jsp/src/main/resources/META-INF/taglib.tld b/ext/mvc-jsp/src/main/resources/META-INF/taglib.tld
new file mode 100644
index 0000000..95e6585
--- /dev/null
+++ b/ext/mvc-jsp/src/main/resources/META-INF/taglib.tld
@@ -0,0 +1,61 @@
+<?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
+
+-->
+
+<taglib xmlns="http://java.sun.com/xml/ns/javaee"
+        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd"
+        version="2.1">
+
+    <description>The tag library contains tags that takes advantage of the request dispatching mechanism.</description>
+    <tlib-version>1.0</tlib-version>
+    <short-name>jersey-mvc-jsp</short-name>
+    <uri>urn:org:glassfish:jersey:servlet:mvc</uri>
+
+    <tag>
+        <name>include</name>
+        <tag-class>org.glassfish.jersey.server.mvc.jsp.Include</tag-class>
+        <body-content>empty</body-content>
+        <attribute>
+            <name>page</name>
+            <required>yes</required>
+            <rtexprvalue>yes</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>resource</name>
+            <required>no</required>
+            <rtexprvalue>yes</rtexprvalue>
+        </attribute>
+    </tag>
+</taglib>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ext/mvc-jsp/src/main/resources/org.glassfish.jersey.server.mvc.jsp.internal/localization.properties b/ext/mvc-jsp/src/main/resources/org.glassfish.jersey.server.mvc.jsp.internal/localization.properties
new file mode 100644
index 0000000..b81cd4c
--- /dev/null
+++ b/ext/mvc-jsp/src/main/resources/org.glassfish.jersey.server.mvc.jsp.internal/localization.properties
@@ -0,0 +1,22 @@
+#
+# 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} - path
+error.viewable.incorrect.instance=viewable argument must be an instance of ResolvedViewable.
+no.request.dispatcher.for.resolved.path=No request dispatcher for: {0}
+# {0} - path
+resource.path.not.in.correct.form=Resource path "{0}" is not in correct form.
+unable.to.find.page.for.resolving.class=Unable to find "{0}" for {1}.
diff --git a/ext/mvc-mustache/pom.xml b/ext/mvc-mustache/pom.xml
new file mode 100644
index 0000000..b3c747c
--- /dev/null
+++ b/ext/mvc-mustache/pom.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0"?>
+<!--
+
+    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>
+        <groupId>org.glassfish.jersey.ext</groupId>
+        <artifactId>project</artifactId>
+        <version>2.28-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>jersey-mvc-mustache</artifactId>
+    <name>jersey-ext-mvc-mustache</name>
+
+    <description>
+        Jersey extension module providing support for Mustache templates.
+    </description>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <inherited>true</inherited>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Export-Package>org.glassfish.jersey.server.mvc.mustache.*;version=${project.version}</Export-Package>
+                    </instructions>
+                    <unpackBundle>true</unpackBundle>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.glassfish.jersey.ext</groupId>
+            <artifactId>jersey-mvc</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.spullara.mustache.java</groupId>
+            <artifactId>compiler</artifactId>
+            <version>${mustache.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>servlet-api</artifactId>
+            <version>${servlet2.version}</version>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/ext/mvc-mustache/src/main/java/org/glassfish/jersey/server/mvc/mustache/MustacheMvcFeature.java b/ext/mvc-mustache/src/main/java/org/glassfish/jersey/server/mvc/mustache/MustacheMvcFeature.java
new file mode 100644
index 0000000..9677253
--- /dev/null
+++ b/ext/mvc-mustache/src/main/java/org/glassfish/jersey/server/mvc/mustache/MustacheMvcFeature.java
@@ -0,0 +1,113 @@
+/*
+ * 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.server.mvc.mustache;
+
+import javax.ws.rs.ConstrainedTo;
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.Feature;
+import javax.ws.rs.core.FeatureContext;
+
+import org.glassfish.jersey.server.mvc.MvcFeature;
+
+/**
+ * {@link Feature} used to add support for {@link MvcFeature MVC} and Mustache templates.
+ * <p/>
+ * Note: This feature also registers {@link MvcFeature}.
+ *
+ * @author Michal Gajdos
+ * @since 2.3
+ */
+@ConstrainedTo(RuntimeType.SERVER)
+public class MustacheMvcFeature implements Feature {
+
+    private static final String SUFFIX = ".mustache";
+
+    /**
+     * {@link String} property defining the base path to Mustache templates. If set, the value of the property is added in front
+     * of the template name defined in:
+     * <ul>
+     * <li>{@link org.glassfish.jersey.server.mvc.Viewable Viewable}</li>
+     * <li>{@link org.glassfish.jersey.server.mvc.Template Template}, or</li>
+     * <li>{@link org.glassfish.jersey.server.mvc.ErrorTemplate ErrorTemplate}</li>
+     * </ul>
+     * <p/>
+     * Value can be absolute providing a full path to a system directory with templates or relative to current
+     * {@link javax.servlet.ServletContext servlet context}.
+     * <p/>
+     * There is no default value.
+     * <p/>
+     * The name of the configuration property is <tt>{@value}</tt>.
+     */
+    public static final String TEMPLATE_BASE_PATH = MvcFeature.TEMPLATE_BASE_PATH + SUFFIX;
+
+    /**
+     * If {@code true} then enable caching of Mustache templates to avoid multiple compilation.
+     * <p/>
+     * The default value is {@code false}.
+     * <p/>
+     * The name of the configuration property is <tt>{@value}</tt>.
+     *
+     * @since 2.5
+     */
+    public static final String CACHE_TEMPLATES = MvcFeature.CACHE_TEMPLATES + SUFFIX;
+
+    /**
+     * Property used to pass user-configured {@link com.github.mustachejava.MustacheFactory factory} able to create
+     * {@link com.github.mustachejava.Mustache Mustache templates}.
+     * <p/>
+     * The default value is not set.
+     * <p/>
+     * The name of the configuration property is <tt>{@value}</tt>.
+     *
+     * @since 2.5
+     */
+    public static final String TEMPLATE_OBJECT_FACTORY = MvcFeature.TEMPLATE_OBJECT_FACTORY + SUFFIX;
+
+    /**
+     * Property defines output encoding produced by {@link org.glassfish.jersey.server.mvc.spi.TemplateProcessor}.
+     * The value must be a valid encoding defined that can be passed
+     * to the {@link java.nio.charset.Charset#forName(String)} method.
+     *
+     * <p/>
+     * The default value is {@code UTF-8}.
+     * <p/>
+     * The name of the configuration property is <tt>{@value}</tt>.
+     * <p/>
+     *
+     * @since 2.7
+     */
+    public static final String ENCODING = MvcFeature.ENCODING + SUFFIX;
+
+    @Override
+    public boolean configure(final FeatureContext context) {
+        final Configuration config = context.getConfiguration();
+
+        if (!config.isRegistered(MustacheTemplateProcessor.class)) {
+            // Template Processor.
+            context.register(MustacheTemplateProcessor.class);
+
+            // MvcFeature.
+            if (!config.isRegistered(MvcFeature.class)) {
+                context.register(MvcFeature.class);
+            }
+
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/ext/mvc-mustache/src/main/java/org/glassfish/jersey/server/mvc/mustache/MustacheTemplateProcessor.java b/ext/mvc-mustache/src/main/java/org/glassfish/jersey/server/mvc/mustache/MustacheTemplateProcessor.java
new file mode 100644
index 0000000..a39c50b
--- /dev/null
+++ b/ext/mvc-mustache/src/main/java/org/glassfish/jersey/server/mvc/mustache/MustacheTemplateProcessor.java
@@ -0,0 +1,80 @@
+/*
+ * 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.server.mvc.mustache;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.nio.charset.Charset;
+
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import javax.servlet.ServletContext;
+
+import org.glassfish.jersey.internal.inject.InjectionManager;
+import org.glassfish.jersey.server.mvc.Viewable;
+import org.glassfish.jersey.server.mvc.spi.AbstractTemplateProcessor;
+import org.glassfish.jersey.server.mvc.spi.TemplateProcessor;
+
+import com.github.mustachejava.DefaultMustacheFactory;
+import com.github.mustachejava.Mustache;
+import com.github.mustachejava.MustacheFactory;
+
+/**
+ * {@link TemplateProcessor Template processor} providing support for Mustache templates.
+ *
+ * @author Michal Gajdos
+ * @see MustacheMvcFeature
+ * @since 2.3
+ */
+@Singleton
+final class MustacheTemplateProcessor extends AbstractTemplateProcessor<Mustache> {
+
+    private final MustacheFactory factory;
+
+    /**
+     * Create an instance of this processor with injected {@link Configuration config} and (nullable)
+     * {@link ServletContext servlet context}.
+     *
+     * @param config           configuration to configure this processor from.
+     * @param injectionManager injection manager.
+     */
+    @Inject
+    public MustacheTemplateProcessor(Configuration config, InjectionManager injectionManager) {
+        super(config, injectionManager.getInstance(ServletContext.class), "mustache", "mustache");
+
+        this.factory = getTemplateObjectFactory(injectionManager::createAndInitialize, MustacheFactory.class,
+                DefaultMustacheFactory::new);
+    }
+
+    @Override
+    protected Mustache resolve(final String templatePath, final Reader reader) {
+        return factory.compile(reader, templatePath);
+    }
+
+    @Override
+    public void writeTo(final Mustache mustache, final Viewable viewable, final MediaType mediaType,
+                        final MultivaluedMap<String, Object> httpHeaders, final OutputStream out) throws IOException {
+        Charset encoding = setContentType(mediaType, httpHeaders);
+        mustache.execute(new OutputStreamWriter(out, encoding), viewable.getModel()).flush();
+    }
+}
diff --git a/ext/mvc-mustache/src/main/java/org/glassfish/jersey/server/mvc/mustache/package-info.java b/ext/mvc-mustache/src/main/java/org/glassfish/jersey/server/mvc/mustache/package-info.java
new file mode 100644
index 0000000..297ab09
--- /dev/null
+++ b/ext/mvc-mustache/src/main/java/org/glassfish/jersey/server/mvc/mustache/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 server-side classes adding support of Mustache to Jersey MVC (Model View Controller).
+ */
+package org.glassfish.jersey.server.mvc.mustache;
diff --git a/ext/mvc/pom.xml b/ext/mvc/pom.xml
new file mode 100644
index 0000000..9a0ff5e
--- /dev/null
+++ b/ext/mvc/pom.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0"?>
+<!--
+
+    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>
+        <groupId>org.glassfish.jersey.ext</groupId>
+        <artifactId>project</artifactId>
+        <version>2.28-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>jersey-mvc</artifactId>
+    <name>jersey-ext-mvc</name>
+
+    <description>
+        Jersey extension module providing support for MVC.
+    </description>
+
+    <dependencies>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>servlet-api</artifactId>
+            <version>${servlet2.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.glassfish.jersey.core</groupId>
+            <artifactId>jersey-server</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>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Export-Package>org.glassfish.jersey.server.mvc.*;version=${project.version}</Export-Package>
+                    </instructions>
+                    <unpackBundle>true</unpackBundle>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/ErrorTemplate.java b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/ErrorTemplate.java
new file mode 100644
index 0000000..4400c93
--- /dev/null
+++ b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/ErrorTemplate.java
@@ -0,0 +1,52 @@
+/*
+ * 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.server.mvc;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Used to annotate JAX-RS resources and resource methods to provide reference to an error template if an exception has been
+ * raised during processing a request (resource method invocation).
+ * <p/>
+ * The processing of this annotation is similar to the processing of {@link Template} annotation with the difference that the
+ * thrown exception (or an object derived from the exception) is used as a model for the defined template.
+ * <p/>
+ * By default every {@link Exception exception} is mapped to a viewable and passed to the MVC runtime for further processing.
+ * <p/>
+ * Note: The {@link ErrorTemplate} annotation can be used even in case when neither {@link Viewable viewable} is used as return
+ * value of a resource method nor {@link Template} annotation is used to annotate the resource method or resource class.
+ *
+ * @author Michal Gajdos
+ * @see Template
+ * @see org.glassfish.jersey.server.mvc.spi.AbstractErrorTemplateMapper
+ * @since 2.3
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE, ElementType.METHOD})
+@Documented
+public @interface ErrorTemplate {
+
+    /**
+     * The template name that should be used to display an error raised during processing a request. The template name may be
+     * declared as absolute template name if the name begins with a '/', otherwise the template name is recognized to be relative.
+     */
+    String name() default "";
+}
diff --git a/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/MvcFeature.java b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/MvcFeature.java
new file mode 100644
index 0000000..d83376c
--- /dev/null
+++ b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/MvcFeature.java
@@ -0,0 +1,114 @@
+/*
+ * 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.server.mvc;
+
+import javax.ws.rs.ConstrainedTo;
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.Feature;
+import javax.ws.rs.core.FeatureContext;
+
+import org.glassfish.jersey.server.mvc.internal.ErrorTemplateExceptionMapper;
+import org.glassfish.jersey.server.mvc.internal.MvcBinder;
+
+/**
+ * {@code MvcFeature} used to add MVC support to the server.
+ *
+ * @author Michal Gajdos
+ */
+@ConstrainedTo(RuntimeType.SERVER)
+public final class MvcFeature implements Feature {
+
+    /**
+     * {@link String} property defining the base path to MVC templates. If set, the value of the property is added in front
+     * of the template name defined in:
+     * <ul>
+     * <li>{@link org.glassfish.jersey.server.mvc.Viewable Viewable}</li>
+     * <li>{@link org.glassfish.jersey.server.mvc.Template Template}, or</li>
+     * <li>{@link org.glassfish.jersey.server.mvc.ErrorTemplate ErrorTemplate}</li>
+     * </ul>
+     * <p/>
+     * Value can be absolute providing a full path to a system directory with templates or relative to current
+     * {@link javax.servlet.ServletContext servlet context}.
+     * <p/>
+     * There is no default value.
+     * <p/>
+     * The name of the configuration property is <tt>{@value}</tt>.
+     */
+    public static final String TEMPLATE_BASE_PATH = "jersey.config.server.mvc.templateBasePath";
+
+    /**
+     * If {@code true} then enable caching of template objects, i.e. to avoid multiple compilations of a template.
+     * <p/>
+     * The default value is {@code false}.
+     * <p/>
+     * The name of the configuration property is <tt>{@value}</tt>.
+     * <p/>
+     * Note: This property is used as common prefix for specific
+     * {@link org.glassfish.jersey.server.mvc.spi.TemplateProcessor template processors} properties and might not be supported by
+     * all template processors.
+     *
+     * @since 2.5
+     */
+    public static final String CACHE_TEMPLATES = "jersey.config.server.mvc.caching";
+
+    /**
+     * Property used to pass user-configured factory able to create template objects. Value of the property is supposed to be an
+     * instance of "templating engine"-specific factory, a class of the factory or class-name of the factory.
+     * <p/>
+     * The default value is not set.
+     * <p/>
+     * The name of the configuration property is <tt>{@value}</tt>.
+     * <p/>
+     * Note: This property is used as common prefix for specific
+     * {@link org.glassfish.jersey.server.mvc.spi.TemplateProcessor template processors} properties and might not be supported by
+     * all template processors.
+     *
+     * @since 2.5
+     */
+    public static final String TEMPLATE_OBJECT_FACTORY = "jersey.config.server.mvc.factory";
+
+    /**
+     * Property defines output encoding produced by {@link org.glassfish.jersey.server.mvc.spi.TemplateProcessor}. The value
+     * must be a valid encoding defined that can be passed to the {@link java.nio.charset.Charset#forName(String)} method.
+     * <p/>
+     * The default value is {@code UTF-8}.
+     * <p/>
+     * The name of the configuration property is <tt>{@value}</tt>.
+     * <p/>
+     * Note: This property is used as common prefix for specific
+     * {@link org.glassfish.jersey.server.mvc.spi.TemplateProcessor template processors} properties and might not be supported by
+     * all template processors.
+     *
+     * @since 2.7
+     */
+    public static final String ENCODING = "jersey.config.server.mvc.encoding";
+
+    @Override
+    public boolean configure(final FeatureContext context) {
+        final Configuration config = context.getConfiguration();
+
+        if (!config.isRegistered(ErrorTemplateExceptionMapper.class)) {
+            context.register(ErrorTemplateExceptionMapper.class);
+            context.register(new MvcBinder());
+
+            return true;
+        }
+
+        return false;
+    }
+}
diff --git a/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/Template.java b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/Template.java
new file mode 100644
index 0000000..b0b5bfa
--- /dev/null
+++ b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/Template.java
@@ -0,0 +1,52 @@
+/*
+ * 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.server.mvc;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Used to annotate JAX-RS resources and resource methods to provide reference to a template for MVC support.
+ * <p/>
+ * In case a resource class is annotated with {@link Template} annotation then an instance of this class is considered to be
+ * the model. Producible {@link javax.ws.rs.core.MediaType media types} are determined from the resource classes
+ * {@link javax.ws.rs.Produces} annotation.
+ * <p/>
+ * In case a resource method is annotated with {@link Template} annotation then the return value of the method is the model.
+ * Otherwise the processing of such a method is the same as if the  return type of the method was {@link Viewable} class.
+ * Producible {@link javax.ws.rs.core.MediaType media types} are determined from the method's {@link javax.ws.rs.Produces}
+ * annotation.
+ * <p/>
+ * To see how templates are being resolved, see {@link Viewable viewable}.
+ *
+ * @author Michal Gajdos
+ * @see Viewable
+ */
+@Inherited
+@Target({ElementType.TYPE, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Template {
+
+    /**
+     * The template name that should be used to output the entity. The template name may be declared as absolute template name
+     * if the name begins with a '/', otherwise the template name is declared as a relative template name.
+     */
+    String name() default "";
+}
diff --git a/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/Viewable.java b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/Viewable.java
new file mode 100644
index 0000000..56703a4
--- /dev/null
+++ b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/Viewable.java
@@ -0,0 +1,105 @@
+/*
+ * 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.server.mvc;
+
+import org.glassfish.jersey.server.mvc.internal.LocalizationMessages;
+
+/**
+ * A viewable type referencing a template by name and a model to be passed
+ * to the template. Such a type may be returned by a resource method of a
+ * resource class. In this respect the template is the view and the controller
+ * is the resource class in the Model View Controller pattern.
+ * <p/>
+ * The template name may be declared as absolute template name if the name
+ * begins with a '/', otherwise the template name is declared as a relative
+ * template name. If the template name is relative then the class of the
+ * last matching resource is utilized to create an absolute path by default. However,
+ * the responsibility of resolving the absolute template name is up to
+ * {@link org.glassfish.jersey.server.mvc.spi.ViewableContext} which can override the
+ * default resolving behaviour.
+ * <p/>
+ *
+ * @author Paul Sandoz
+ * @author Michal Gajdos
+ *
+ * @see Template
+ * @see org.glassfish.jersey.server.mvc.spi.ViewableContext
+ * @see org.glassfish.jersey.server.mvc.internal.ResolvingViewableContext
+ */
+public class Viewable {
+
+    private final String templateName;
+
+    private final Object model;
+
+    /**
+     * Construct a new viewable type with a template name.
+     * <p/>
+     * The model will be set to {@code null}.
+     *
+     * @param templateName the template name, shall not be {@code null}.
+     * @throws IllegalArgumentException if the template name is {@code null}.
+     */
+    public Viewable(String templateName) throws IllegalArgumentException {
+        this(templateName, null);
+    }
+
+    /**
+     * Construct a new viewable type with a template name and a model.
+     *
+     * @param templateName the template name, shall not be {@code null}.
+     * @param model the model, may be {@code null}.
+     * @throws IllegalArgumentException if the template name is {@code null}.
+     */
+    public Viewable(String templateName, Object model) throws IllegalArgumentException {
+        if (templateName == null) {
+            throw new IllegalArgumentException(LocalizationMessages.TEMPLATE_NAME_MUST_NOT_BE_NULL());
+        }
+
+        this.templateName = templateName;
+        this.model = model;
+    }
+
+    /**
+     * Get the template name.
+     *
+     * @return the template name.
+     */
+    public String getTemplateName() {
+        return templateName;
+    }
+
+    /**
+     * Get the model.
+     *
+     * @return the model.
+     */
+    public Object getModel() {
+        return model;
+    }
+
+
+    /**
+     * Determines whether the template name is represented by an absolute path.
+     *
+     * @return {@code true} if the template name is absolute, and starts with a
+     *         '/' character, {@code false} otherwise.
+     */
+    public boolean isTemplateNameAbsolute() {
+        return templateName.length() > 0 && templateName.charAt(0) == '/';
+    }
+}
diff --git a/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/ErrorTemplateExceptionMapper.java b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/ErrorTemplateExceptionMapper.java
new file mode 100644
index 0000000..8f3c1ff
--- /dev/null
+++ b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/ErrorTemplateExceptionMapper.java
@@ -0,0 +1,28 @@
+/*
+ * 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.server.mvc.internal;
+
+import org.glassfish.jersey.server.mvc.spi.AbstractErrorTemplateMapper;
+
+/**
+ * Implementation of {@link AbstractErrorTemplateMapper} for every thrown {@link Exception}.
+ *
+ * @author Michal Gajdos
+ * @since 2.3
+ */
+public class ErrorTemplateExceptionMapper extends AbstractErrorTemplateMapper<Exception> {
+}
diff --git a/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/ImplicitViewable.java b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/ImplicitViewable.java
new file mode 100644
index 0000000..ebe93ed
--- /dev/null
+++ b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/ImplicitViewable.java
@@ -0,0 +1,71 @@
+/*
+ * 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.server.mvc.internal;
+
+import java.util.List;
+
+import org.glassfish.jersey.server.mvc.Viewable;
+
+/**
+ * {@link Viewable} implementation representing return value of enhancing methods added to
+ * {@link org.glassfish.jersey.server.model.Resource resources} annotated with {@link org.glassfish.jersey.server.mvc.Template}.
+ *
+ * @author Michal Gajdos
+ * @see TemplateModelProcessor
+ * @see org.glassfish.jersey.server.mvc.Template
+ */
+final class ImplicitViewable extends Viewable {
+
+    private final List<String> templateNames;
+
+    private final Class<?> resolvingClass;
+
+    /**
+     * Create a {@code ImplicitViewable}.
+     *
+     * @param templateNames allowed template names for which a {@link Viewable viewable} can be resolved.
+     * @param model the model, may be {@code null}.
+     * @param resolvingClass the class to use to resolve the template name if the template is not absolute,
+     * if {@code null} then the resolving class will be obtained from the last matching resource.
+     * @throws IllegalArgumentException if the template name is {@code null}.
+     */
+    ImplicitViewable(final List<String> templateNames, final Object model, final Class<?> resolvingClass)
+            throws IllegalArgumentException {
+        super("", model);
+
+        this.templateNames = templateNames;
+        this.resolvingClass = resolvingClass;
+    }
+
+    /**
+     * Get allowed template names for which a {@link Viewable viewable} can be resolved.
+     *
+     * @return allowed template names.
+     */
+    public List<String> getTemplateNames() {
+        return templateNames;
+    }
+
+    /**
+     * Get the resolving class.
+     *
+     * @return Resolving class.
+     */
+    public Class<?> getResolvingClass() {
+        return resolvingClass;
+    }
+}
diff --git a/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/MvcBinder.java b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/MvcBinder.java
new file mode 100644
index 0000000..8203178
--- /dev/null
+++ b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/MvcBinder.java
@@ -0,0 +1,45 @@
+/*
+ * 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.server.mvc.internal;
+
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.WriterInterceptor;
+
+import javax.inject.Singleton;
+
+import org.glassfish.jersey.internal.inject.AbstractBinder;
+import org.glassfish.jersey.server.model.ModelProcessor;
+import org.glassfish.jersey.server.mvc.spi.ViewableContext;
+
+/**
+ * Provides MVC functionality.
+ *
+ * @author Michal Gajdos
+ */
+public class MvcBinder extends AbstractBinder {
+
+    @Override
+    protected void configure() {
+        bind(TemplateMethodInterceptor.class).to(WriterInterceptor.class).in(Singleton.class);
+        //noinspection unchecked
+        bind(ViewableMessageBodyWriter.class).to(MessageBodyWriter.class).in(Singleton.class);
+
+        bind(TemplateModelProcessor.class).to(ModelProcessor.class).in(Singleton.class);
+        bindAsContract(ResolvingViewableContext.class).in(Singleton.class);
+        bind(ResolvingViewableContext.class).to(ViewableContext.class).in(Singleton.class).ranked(Integer.MIN_VALUE);
+    }
+}
diff --git a/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/ResolvingViewableContext.java b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/ResolvingViewableContext.java
new file mode 100644
index 0000000..bfe4059
--- /dev/null
+++ b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/ResolvingViewableContext.java
@@ -0,0 +1,131 @@
+/*
+ * 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.server.mvc.internal;
+
+import javax.ws.rs.core.MediaType;
+
+import org.glassfish.jersey.server.mvc.Viewable;
+import org.glassfish.jersey.server.mvc.spi.ResolvedViewable;
+import org.glassfish.jersey.server.mvc.spi.TemplateProcessor;
+import org.glassfish.jersey.server.mvc.spi.ViewableContext;
+import org.glassfish.jersey.server.mvc.spi.ViewableContextException;
+
+/**
+ * Default implementation of {@link org.glassfish.jersey.server.mvc.spi.ViewableContext viewable context}.
+ * <p/>
+ * If the template name of given {@link Viewable} is represented as a relative path then the resolving class,
+ * and super classes in the inheritance hierarchy, are utilized to generate the absolute template name as follows.
+ * <br/>
+ * The base path starts with '/' character, followed by the fully qualified class name of the resolving class,
+ * with any '.' and '$' characters replaced with a '/' character, followed by a '/' character,
+ * followed by the relative template name.
+ * <br/>
+ * If the absolute template name cannot be resolved into a template reference (see {@link org.glassfish.jersey.server.mvc.spi
+ * .TemplateProcessor} and {@link org.glassfish.jersey.server.mvc.spi.ViewableContext}) then the super class of the resolving
+ * class is utilized, and is set as the resolving class. Traversal up the inheritance hierarchy proceeds until an absolute
+ * template name can be resolved into a template reference, or the Object class is reached,
+ * which means the absolute template name could not be resolved and an error will result.
+ *
+ * @author Michal Gajdos
+ */
+class ResolvingViewableContext implements ViewableContext {
+
+    /**
+     * Resolve given {@link Viewable viewable} using {@link MediaType media type}, {@code resolving class} and
+     * {@link TemplateProcessor template processor}.
+     *
+     * @param viewable viewable to be resolved.
+     * @param mediaType media type of te output.
+     * @param resourceClass resolving class.
+     * @param templateProcessor template processor to be used.
+     * @return resolved viewable or {@code null} if the viewable cannot be resolved.
+     */
+    public ResolvedViewable resolveViewable(final Viewable viewable, final MediaType mediaType,
+                                            final Class<?> resourceClass, final TemplateProcessor templateProcessor) {
+        if (viewable.isTemplateNameAbsolute()) {
+            return resolveAbsoluteViewable(viewable, resourceClass, mediaType, templateProcessor);
+        } else {
+            if (resourceClass == null) {
+                throw new ViewableContextException(LocalizationMessages.TEMPLATE_RESOLVING_CLASS_CANNOT_BE_NULL());
+            }
+
+            return resolveRelativeViewable(viewable, resourceClass, mediaType, templateProcessor);
+        }
+    }
+
+    /**
+     * Resolve given {@link Viewable viewable} with absolute template name using {@link MediaType media type} and
+     * {@link TemplateProcessor template processor}.
+     *
+     * @param viewable viewable to be resolved.
+     * @param mediaType media type of te output.
+     * @param resourceClass resource class.
+     * @param templateProcessor template processor to be used.
+     * @return resolved viewable or {@code null} if the viewable cannot be resolved.
+     */
+    @SuppressWarnings("unchecked")
+    private ResolvedViewable resolveAbsoluteViewable(final Viewable viewable, Class<?> resourceClass,
+                                                     final MediaType mediaType,
+                                                     final TemplateProcessor templateProcessor) {
+        final Object resolvedTemplateObject = templateProcessor.resolve(viewable.getTemplateName(), mediaType);
+
+        if (resolvedTemplateObject != null) {
+            return new ResolvedViewable(templateProcessor, resolvedTemplateObject, viewable, resourceClass, mediaType);
+        }
+
+        return null;
+    }
+
+    /**
+     * Resolve given {@link Viewable viewable} with relative template name using {@link MediaType media type},
+     * {@code resolving class} and {@link TemplateProcessor template processor}.
+     *
+     * @param viewable viewable to be resolved.
+     * @param mediaType media type of te output.
+     * @param resolvingClass resolving class.
+     * @param templateProcessor template processor to be used.
+     * @return resolved viewable or {@code null} if the viewable cannot be resolved.
+     */
+    @SuppressWarnings("unchecked")
+    private ResolvedViewable resolveRelativeViewable(final Viewable viewable, final Class<?> resolvingClass,
+                                                     final MediaType mediaType, final TemplateProcessor templateProcessor) {
+        final String path = TemplateHelper.getTemplateName(viewable);
+
+        // Find in directories.
+        for (Class c = resolvingClass; c != Object.class; c = c.getSuperclass()) {
+            final String absolutePath = TemplateHelper.getAbsolutePath(c, path, '/');
+            final Object resolvedTemplateObject = templateProcessor.resolve(absolutePath, mediaType);
+
+            if (resolvedTemplateObject != null) {
+                return new ResolvedViewable(templateProcessor, resolvedTemplateObject, viewable, c, mediaType);
+            }
+        }
+
+        // Find in flat files.
+        for (Class c = resolvingClass; c != Object.class; c = c.getSuperclass()) {
+            final String absolutePath = TemplateHelper.getAbsolutePath(c, path, '.');
+            final Object resolvedTemplateObject = templateProcessor.resolve(absolutePath, mediaType);
+
+            if (resolvedTemplateObject != null) {
+                return new ResolvedViewable(templateProcessor, resolvedTemplateObject, viewable, c, mediaType);
+            }
+        }
+
+        return null;
+    }
+
+}
diff --git a/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/TemplateHelper.java b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/TemplateHelper.java
new file mode 100644
index 0000000..50190b0
--- /dev/null
+++ b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/TemplateHelper.java
@@ -0,0 +1,152 @@
+/*
+ * 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.server.mvc.internal;
+
+import java.lang.annotation.Annotation;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Variant;
+
+import org.glassfish.jersey.internal.util.PropertiesHelper;
+import org.glassfish.jersey.internal.util.collection.Ref;
+import org.glassfish.jersey.internal.util.collection.Refs;
+import org.glassfish.jersey.message.internal.MediaTypes;
+import org.glassfish.jersey.message.internal.VariantSelector;
+import org.glassfish.jersey.server.ContainerRequest;
+import org.glassfish.jersey.server.ExtendedUriInfo;
+import org.glassfish.jersey.server.mvc.MvcFeature;
+import org.glassfish.jersey.server.mvc.Template;
+import org.glassfish.jersey.server.mvc.Viewable;
+
+/**
+ * Helper class to provide some common functionality related to MVC.
+ *
+ * @author Michal Gajdos
+ */
+public final class TemplateHelper {
+
+    private static final Charset DEFAULT_ENCODING = Charset.forName("UTF-8");
+
+    /**
+     * Return an absolute path to the given class where segments are separated using {@code delim} character and {@code path}
+     * is appended to this path.
+     *
+     * @param resourceClass class for which an absolute path should be obtained.
+     * @param path segment to be appended to the resulting path.
+     * @param delim character used for separating path segments.
+     * @return an absolute path to the resource class.
+     */
+    public static String getAbsolutePath(Class<?> resourceClass, String path, char delim) {
+        return '/' + resourceClass.getName().replace('.', '/').replace('$', delim) + delim + path;
+    }
+
+    /**
+     * Get media types for which the {@link org.glassfish.jersey.server.mvc.spi.ResolvedViewable resolved viewable} could be
+     * produced.
+     *
+     * @param containerRequest request to obtain acceptable media types.
+     * @param extendedUriInfo uri info to obtain resource method from and its producible media types.
+     * @param varyHeaderValue Vary header reference.
+     * @return list of producible media types.
+     */
+    public static List<MediaType> getProducibleMediaTypes(final ContainerRequest containerRequest,
+                                                          final ExtendedUriInfo extendedUriInfo,
+                                                          final Ref<String> varyHeaderValue) {
+        final List<MediaType> producedTypes = getResourceMethodProducibleTypes(extendedUriInfo);
+        final MediaType[] mediaTypes = producedTypes.toArray(new MediaType[producedTypes.size()]);
+
+        final List<Variant> variants = VariantSelector.selectVariants(
+                containerRequest, Variant.mediaTypes(mediaTypes).build(),
+                varyHeaderValue == null ? Refs.<String>emptyRef() : varyHeaderValue);
+
+        return variants.stream()
+                       .map(variant -> MediaTypes.stripQualityParams(variant.getMediaType()))
+                       .collect(Collectors.toList());
+    }
+
+    /**
+     * Get template name from given {@link org.glassfish.jersey.server.mvc.Viewable viewable} or return {@code index} if the given
+     * viewable doesn't contain a valid template name.
+     *
+     * @param viewable viewable to obtain template name from.
+     * @return {@code non-null}, {@code non-empty} template name.
+     */
+    public static String getTemplateName(final Viewable viewable) {
+        return viewable.getTemplateName() == null || viewable.getTemplateName().isEmpty() ? "index" : viewable.getTemplateName();
+    }
+
+    /**
+     * Return a list of producible media types of the last matched resource method.
+     *
+     * @param extendedUriInfo uri info to obtain resource method from.
+     * @return list of producible media types of the last matched resource method.
+     */
+    private static List<MediaType> getResourceMethodProducibleTypes(final ExtendedUriInfo extendedUriInfo) {
+        if (extendedUriInfo.getMatchedResourceMethod() != null
+                && !extendedUriInfo.getMatchedResourceMethod().getProducedTypes().isEmpty()) {
+            return extendedUriInfo.getMatchedResourceMethod().getProducedTypes();
+        }
+        return Arrays.asList(MediaType.WILDCARD_TYPE);
+    }
+
+    /**
+     * Extract {@link org.glassfish.jersey.server.mvc.Template template} annotation from given list.
+     *
+     * @param annotations list of annotations.
+     * @return {@link org.glassfish.jersey.server.mvc.Template template} annotation or {@code null} if this annotation is not present.
+     */
+    public static Template getTemplateAnnotation(final Annotation[] annotations) {
+        if (annotations != null && annotations.length > 0) {
+            for (Annotation annotation : annotations) {
+                if (annotation instanceof Template) {
+                    return (Template) annotation;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Get output encoding from configuration.
+     * @param configuration Configuration.
+     * @param suffix Template processor suffix of the
+     *               to configuration property {@link org.glassfish.jersey.server.mvc.MvcFeature#ENCODING}.
+     *
+     * @return Encoding read from configuration properties or a default encoding if no encoding is configured.
+     */
+    public static Charset getTemplateOutputEncoding(Configuration configuration, String suffix) {
+        final String enc = PropertiesHelper.getValue(configuration.getProperties(), MvcFeature.ENCODING + suffix,
+                String.class, null);
+        if (enc == null) {
+            return DEFAULT_ENCODING;
+        } else {
+            return Charset.forName(enc);
+        }
+    }
+
+    /**
+     * Prevents instantiation.
+     */
+    private TemplateHelper() {
+    }
+}
diff --git a/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/TemplateInflector.java b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/TemplateInflector.java
new file mode 100644
index 0000000..3673dae
--- /dev/null
+++ b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/TemplateInflector.java
@@ -0,0 +1,33 @@
+/*
+ * 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.server.mvc.internal;
+
+/**
+ * Interface providing information about implicit viewable resource classes.
+ *
+ * @author Michal Gajdos
+ * @since 2.3
+ */
+public interface TemplateInflector {
+
+    /**
+     * Get a class of the model used in template of implicit viewable.
+     *
+     * @return class of model used in template of implicit viewable.
+     */
+    public Class<?> getModelClass();
+}
diff --git a/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/TemplateMethodInterceptor.java b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/TemplateMethodInterceptor.java
new file mode 100644
index 0000000..0fdfacb
--- /dev/null
+++ b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/TemplateMethodInterceptor.java
@@ -0,0 +1,55 @@
+/*
+ * 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.server.mvc.internal;
+
+import java.io.IOException;
+
+import javax.ws.rs.Priorities;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.ext.WriterInterceptor;
+import javax.ws.rs.ext.WriterInterceptorContext;
+
+import javax.annotation.Priority;
+
+import org.glassfish.jersey.server.mvc.Template;
+import org.glassfish.jersey.server.mvc.Viewable;
+
+/**
+ * Intercepts resource methods that are annotated by {@link Template template annotation} and does not return {@link Viewable}
+ * instances.
+ *
+ * @see org.glassfish.jersey.server.mvc.spi.ResolvedViewable
+ * @see ViewableMessageBodyWriter
+ *
+ * @author Michal Gajdos
+ */
+@Priority(Priorities.ENTITY_CODER)
+class TemplateMethodInterceptor implements WriterInterceptor {
+
+    @Override
+    public void aroundWriteTo(final WriterInterceptorContext context) throws IOException, WebApplicationException {
+        final Template template = TemplateHelper.getTemplateAnnotation(context.getAnnotations());
+        final Object entity = context.getEntity();
+
+        if (template != null && !(entity instanceof Viewable)) {
+            context.setType(Viewable.class);
+            context.setEntity(new Viewable(template.name(), entity));
+        }
+
+        context.proceed();
+    }
+}
diff --git a/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/TemplateModelProcessor.java b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/TemplateModelProcessor.java
new file mode 100644
index 0000000..8f96009
--- /dev/null
+++ b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/TemplateModelProcessor.java
@@ -0,0 +1,362 @@
+/*
+ * 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.server.mvc.internal;
+
+import java.security.AccessController;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.HttpMethod;
+import javax.ws.rs.Produces;
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ResourceContext;
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import javax.inject.Inject;
+import javax.inject.Provider;
+
+import org.glassfish.jersey.internal.Errors;
+import org.glassfish.jersey.internal.util.Producer;
+import org.glassfish.jersey.internal.util.ReflectionHelper;
+import org.glassfish.jersey.message.internal.MediaTypes;
+import org.glassfish.jersey.process.Inflector;
+import org.glassfish.jersey.server.ExtendedUriInfo;
+import org.glassfish.jersey.server.internal.inject.ConfiguredValidator;
+import org.glassfish.jersey.server.model.Invocable;
+import org.glassfish.jersey.server.model.ModelProcessor;
+import org.glassfish.jersey.server.model.Resource;
+import org.glassfish.jersey.server.model.ResourceMethod;
+import org.glassfish.jersey.server.model.ResourceModel;
+import org.glassfish.jersey.server.model.RuntimeResource;
+import org.glassfish.jersey.server.model.internal.ModelHelper;
+import org.glassfish.jersey.server.model.internal.ModelProcessorUtil;
+import org.glassfish.jersey.server.mvc.Template;
+import org.glassfish.jersey.server.mvc.Viewable;
+
+/**
+ * {@link ModelProcessor Model processor} enhancing (sub-)resources with {@value HttpMethod#GET} methods responsible of producing
+ * implicit {@link org.glassfish.jersey.server.mvc.Viewable viewables}.
+ * <p/>
+ * Note: Resource classes has to be annotated with {@link Template} annotation in order to be enhanced by this model processor.
+ *
+ * @author Michal Gajdos
+ * @see Template
+ */
+class TemplateModelProcessor implements ModelProcessor {
+
+    /**
+     * Path parameter representing implicit template name.
+     */
+    private static final String IMPLICIT_VIEW_PATH_PARAMETER = "implicit-view-path-parameter";
+    private static final String IMPLICIT_VIEW_PATH_PARAMETER_TEMPLATE = "{" + IMPLICIT_VIEW_PATH_PARAMETER + "}";
+
+    private final Provider<ResourceContext> resourceContextProvider;
+    private final Provider<ExtendedUriInfo> extendedUriInfoProvider;
+    private final Provider<ConfiguredValidator> validatorProvider;
+
+    /**
+     * Inflector producing response with {@link org.glassfish.jersey.server.mvc.spi.ResolvedViewable resolved viewable} where
+     * model is the resource class annotated with {@link Template} or 404 as its status code.
+     */
+    private class TemplateInflectorImpl implements TemplateInflector, Inflector<ContainerRequestContext, Response> {
+
+        private final String templateName;
+
+        private final Class<?> resourceClass;
+        private final Object resourceInstance;
+
+        private Class<?> modelClass;
+
+        /**
+         * Create enhancing template {@link Inflector inflector} method.
+         *
+         * @param templateName     template name for the produced {@link org.glassfish.jersey.server.mvc.Viewable viewable}.
+         * @param resourceClass    model class for the produced {@link org.glassfish.jersey.server.mvc.Viewable viewable}.
+         *                         Should not be {@code null}.
+         * @param resourceInstance model for the produced {@link org.glassfish.jersey.server.mvc.Viewable viewable}. May be
+         *                         {@code null}.
+         */
+        private TemplateInflectorImpl(final String templateName, final Class<?> resourceClass,
+                                      final Object resourceInstance) {
+            this.templateName = templateName;
+
+            this.resourceClass = resourceClass;
+            this.resourceInstance = resourceInstance;
+        }
+
+        @Override
+        public Response apply(ContainerRequestContext requestContext) {
+            final List<String> templateNames = getTemplateNames(requestContext);
+            final Object model = getModel(extendedUriInfoProvider.get());
+
+            // Validate resource class.
+            final ConfiguredValidator validator = validatorProvider.get();
+            if (validator != null) {
+                validator.validateResourceAndInputParams(model, null, null);
+            }
+
+            return Response.ok().entity(new ImplicitViewable(templateNames, model, resourceClass)).build();
+        }
+
+        @Override
+        public Class<?> getModelClass() {
+            return modelClass;
+        }
+
+        private Object setModelClass(final Object model) {
+            if (modelClass == null) {
+                modelClass = model.getClass();
+            }
+            return model;
+        }
+
+        /**
+         * Obtains a model object for a viewable.
+         *
+         * @param extendedUriInfo uri info to obtain last matched resource from.
+         * @return a model object.
+         */
+        private Object getModel(final ExtendedUriInfo extendedUriInfo) {
+            final List<Object> matchedResources = extendedUriInfo.getMatchedResources();
+
+            if (resourceInstance != null) {
+                return setModelClass(resourceInstance);
+            } else if (matchedResources.size() > 1) {
+                return setModelClass(matchedResources.get(1));
+            } else {
+                return setModelClass(resourceContextProvider.get().getResource(resourceClass));
+            }
+        }
+
+        /**
+         * Returns a list of template names to be considered as candidates for resolving
+         * {@link org.glassfish.jersey.server.mvc.Viewable viewable} into
+         * {@link org.glassfish.jersey.server.mvc.spi.ResolvedViewable resolved viewable}.
+         * <p/>
+         * Order of template names to be resolved is as follows:
+         * <ul>
+         * <li>{{@value #IMPLICIT_VIEW_PATH_PARAMETER}} value</li>
+         * <li>{@link org.glassfish.jersey.server.mvc.Template#name()}</li>
+         * <li>last sub-resource locator path</li>
+         * <li>index</li>
+         * </ul>
+         *
+         * @param requestContext request context to obtain {@link #IMPLICIT_VIEW_PATH_PARAMETER} value from.
+         * @return a non-empty list of template names.
+         */
+        private List<String> getTemplateNames(final ContainerRequestContext requestContext) {
+            final List<String> templateNames = new ArrayList<>();
+
+            // Template name extracted from path param.
+            final String pathTemplate = requestContext.getUriInfo().getPathParameters().getFirst(IMPLICIT_VIEW_PATH_PARAMETER);
+            if (pathTemplate != null) {
+                templateNames.add(pathTemplate);
+            }
+
+            // Annotation.
+            if (this.templateName != null && !"".equals(this.templateName)) {
+                templateNames.add(this.templateName);
+            }
+
+            // Sub-resource path.
+            final ExtendedUriInfo uriInfo = extendedUriInfoProvider.get();
+            final List<RuntimeResource> matchedRuntimeResources = uriInfo.getMatchedRuntimeResources();
+            if (matchedRuntimeResources.size() > 1) {
+                // > 1 to check that we matched sub-resource
+                final RuntimeResource lastMatchedRuntimeResource = matchedRuntimeResources.get(0);
+                final Resource lastMatchedResource = lastMatchedRuntimeResource.getResources().get(0);
+
+                String path = lastMatchedResource.getPath();
+
+                if (path != null && !IMPLICIT_VIEW_PATH_PARAMETER_TEMPLATE.equals(path)) {
+                    path = path.charAt(0) == '/' ? path.substring(1, path.length()) : path;
+                    templateNames.add(path);
+                }
+            }
+
+            // Index.
+            if (templateNames.isEmpty()) {
+                templateNames.add("index");
+            }
+
+            return templateNames;
+        }
+    }
+
+    /**
+     * Create a {@code TemplateModelProcessor} instance.
+     *
+     * @param resourceContextProvider (injected) resource context provider.
+     * @param validatorProvider       Jersey extension of BeanValidation Validator.
+     * @param extendedUriInfoProvider (injected) extended uri info provider.
+     */
+    @Inject
+    TemplateModelProcessor(final Provider<ResourceContext> resourceContextProvider,
+                           final Provider<ConfiguredValidator> validatorProvider,
+                           final Provider<ExtendedUriInfo> extendedUriInfoProvider) {
+        this.resourceContextProvider = resourceContextProvider;
+        this.validatorProvider = validatorProvider;
+        this.extendedUriInfoProvider = extendedUriInfoProvider;
+    }
+
+    @Override
+    public ResourceModel processResourceModel(final ResourceModel resourceModel, final Configuration configuration) {
+        return processModel(resourceModel, false);
+    }
+
+    @Override
+    public ResourceModel processSubResource(final ResourceModel subResourceModel, final Configuration configuration) {
+        return processModel(subResourceModel, true);
+    }
+
+    /**
+     * Enhance {@link org.glassfish.jersey.server.model.RuntimeResource runtime resources} from the given
+     * {@link org.glassfish.jersey.server.model.ResourceModel resource model} with methods obtained via
+     * {@link #getEnhancingMethods(org.glassfish.jersey.server.model.RuntimeResource)}.
+     *
+     * @param resourceModel    resource model with runtime resources to enhance.
+     * @param subResourceModel determines whether the resource model represents sub-resource.
+     * @return enhanced resource model.
+     */
+    private ResourceModel processModel(final ResourceModel resourceModel, final boolean subResourceModel) {
+        ResourceModel.Builder newModelBuilder = processTemplateAnnotatedInvocables(resourceModel, subResourceModel);
+
+        for (RuntimeResource resource : resourceModel.getRuntimeResourceModel().getRuntimeResources()) {
+            ModelProcessorUtil.enhanceResource(resource, newModelBuilder, getEnhancingMethods(resource), false);
+        }
+
+        return newModelBuilder.build();
+    }
+
+    /**
+     * Process all {@link Invocable invocables} and defines
+     * {@link org.glassfish.jersey.server.model.Invocable#getRoutingResponseType() routing response types}
+     * as {@link Viewable} for all methods annotated with {@link Template}.
+     *
+     * @param resourceModel    resource model to process.
+     * @param subResourceModel determines whether the resource model represents sub-resource.
+     * @return Modified resource model.
+     */
+    private ResourceModel.Builder processTemplateAnnotatedInvocables(ResourceModel resourceModel,
+                                                                     final boolean subResourceModel) {
+        ResourceModel.Builder modelBuilder = new ResourceModel.Builder(subResourceModel);
+        for (Resource resource : resourceModel.getResources()) {
+            Resource newResource = processResource(resource);
+            modelBuilder.addResource(newResource);
+        }
+        return modelBuilder;
+    }
+
+    private Resource processResource(Resource resource) {
+        Resource.Builder resourceBuilder = Resource.builder(resource.getPath());
+        for (ResourceMethod resourceMethod : resource.getResourceMethods()) {
+            ResourceMethod.Builder builder = resourceBuilder.addMethod(resourceMethod);
+            if (resourceMethod.getInvocable().getHandlingMethod().isAnnotationPresent(Template.class)) {
+                builder.routingResponseType(Viewable.class);
+            }
+        }
+        if (resource.getResourceLocator() != null) {
+            resourceBuilder.addMethod(resource.getResourceLocator());
+        }
+
+        for (Resource child : resource.getChildResources()) {
+            resourceBuilder.addChildResource(processResource(child));
+        }
+        return resourceBuilder.build();
+
+    }
+
+    /**
+     * Returns a list of enhancing methods for a given {@link org.glassfish.jersey.server.model.RuntimeResource runtime
+     * resource}.
+     *
+     * @param runtimeResource runtime resource to create enhancing methods for.
+     * @return list of enhancing methods.
+     */
+    private List<ModelProcessorUtil.Method> getEnhancingMethods(final RuntimeResource runtimeResource) {
+        final List<ModelProcessorUtil.Method> newMethods = new ArrayList<>();
+
+        for (final Resource resource : runtimeResource.getResources()) {
+            // Handler classes.
+            for (final Class<?> handlerClass : resource.getHandlerClasses()) {
+                createEnhancingMethods(handlerClass, null, newMethods);
+            }
+
+            // Names - if there are no handler classes / instances.
+            if (resource.getHandlerClasses().isEmpty() && resource.getHandlerInstances().isEmpty()) {
+                for (String resourceName : resource.getNames()) {
+                    final Class<Object> resourceClass = AccessController
+                            .doPrivileged(ReflectionHelper.classForNamePA(resourceName));
+                    if (resourceClass != null) {
+                        createEnhancingMethods(resourceClass, null, newMethods);
+                    }
+                }
+            }
+
+            // Handler instances.
+            Errors.process(new Producer<Void>() {
+                @Override
+                public Void call() {
+                    for (final Object handlerInstance : resource.getHandlerInstances()) {
+                        final Class<?> handlerInstanceClass = handlerInstance.getClass();
+
+                        if (!resource.getHandlerClasses().contains(handlerInstanceClass)) {
+                            createEnhancingMethods(handlerInstanceClass, handlerInstance, newMethods);
+                        } else {
+                            Errors.warning(resource,
+                                    LocalizationMessages.TEMPLATE_HANDLER_ALREADY_ENHANCED(handlerInstanceClass));
+                        }
+                    }
+
+                    return null;
+                }
+            });
+        }
+
+        return newMethods;
+    }
+
+    /**
+     * Creates enhancing methods for given resource.
+     *
+     * @param resourceClass    resource class for which enhancing methods should be created.
+     * @param resourceInstance resource instance for which enhancing methods should be created. May be {@code null}.
+     * @param newMethods       list to store new methods into.
+     */
+    private void createEnhancingMethods(final Class<?> resourceClass, final Object resourceInstance,
+                                        final List<ModelProcessorUtil.Method> newMethods) {
+        final Template template = resourceClass.getAnnotation(Template.class);
+
+        if (template != null) {
+            final Class<?> annotatedResourceClass = ModelHelper.getAnnotatedResourceClass(resourceClass);
+
+            final List<MediaType> produces = MediaTypes
+                    .createQualitySourceMediaTypes(annotatedResourceClass.getAnnotation(Produces.class));
+            final List<MediaType> consumes = MediaTypes.createFrom(annotatedResourceClass.getAnnotation(Consumes.class));
+
+            final TemplateInflectorImpl inflector = new TemplateInflectorImpl(template.name(),
+                    resourceClass, resourceInstance);
+
+            newMethods.add(new ModelProcessorUtil.Method(HttpMethod.GET, consumes, produces, inflector));
+            newMethods.add(new ModelProcessorUtil.Method(IMPLICIT_VIEW_PATH_PARAMETER_TEMPLATE, HttpMethod.GET,
+                    consumes, produces, inflector));
+        }
+    }
+}
diff --git a/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/ViewableMessageBodyWriter.java b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/ViewableMessageBodyWriter.java
new file mode 100644
index 0000000..61820ef
--- /dev/null
+++ b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/ViewableMessageBodyWriter.java
@@ -0,0 +1,206 @@
+/*
+ * 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.server.mvc.internal;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.logging.Logger;
+
+import javax.inject.Inject;
+import javax.ws.rs.ConstrainedTo;
+import javax.ws.rs.NotFoundException;
+import javax.ws.rs.ProcessingException;
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.container.ResourceInfo;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.Provider;
+
+import org.glassfish.jersey.internal.inject.InjectionManager;
+import org.glassfish.jersey.internal.inject.Providers;
+import org.glassfish.jersey.server.ContainerRequest;
+import org.glassfish.jersey.server.ExtendedUriInfo;
+import org.glassfish.jersey.server.mvc.Viewable;
+import org.glassfish.jersey.server.mvc.spi.ResolvedViewable;
+import org.glassfish.jersey.server.mvc.spi.TemplateProcessor;
+import org.glassfish.jersey.server.mvc.spi.ViewableContext;
+import org.glassfish.jersey.server.mvc.spi.ViewableContextException;
+
+/**
+ * {@link javax.ws.rs.ext.MessageBodyWriter Message body writer} for {@link org.glassfish.jersey.server.mvc.Viewable viewable}
+ * entities.
+ *
+ * @author Paul Sandoz
+ * @author Michal Gajdos
+ */
+@Provider
+@ConstrainedTo(RuntimeType.SERVER)
+final class ViewableMessageBodyWriter implements MessageBodyWriter<Viewable> {
+
+    @Inject
+    private InjectionManager injectionManager;
+
+    @Context
+    private javax.inject.Provider<ExtendedUriInfo> extendedUriInfoProvider;
+    @Context
+    private javax.inject.Provider<ContainerRequest> requestProvider;
+    @Context
+    private javax.inject.Provider<ResourceInfo> resourceInfoProvider;
+
+    private static final Logger LOGGER = Logger.getLogger(ViewableMessageBodyWriter.class.getName());
+
+
+    @Override
+    public boolean isWriteable(final Class<?> type, final Type genericType, final Annotation[] annotations,
+                               final MediaType mediaType) {
+        return Viewable.class.isAssignableFrom(type);
+    }
+
+    @Override
+    public long getSize(final Viewable viewable, final Class<?> type, final Type genericType,
+                        final Annotation[] annotations, final MediaType mediaType) {
+        return -1;
+    }
+
+    @Override
+    public void writeTo(final Viewable viewable,
+                        final Class<?> type,
+                        final Type genericType,
+                        final Annotation[] annotations,
+                        final MediaType mediaType,
+                        final MultivaluedMap<String, Object> httpHeaders,
+                        final OutputStream entityStream) throws IOException, WebApplicationException {
+
+        try {
+            final ResolvedViewable resolvedViewable = resolve(viewable);
+            if (resolvedViewable == null) {
+                final String message = LocalizationMessages.TEMPLATE_NAME_COULD_NOT_BE_RESOLVED(viewable.getTemplateName());
+                throw new WebApplicationException(new ProcessingException(message), Response.Status.NOT_FOUND);
+            }
+
+            httpHeaders.putSingle(HttpHeaders.CONTENT_TYPE, resolvedViewable.getMediaType());
+            resolvedViewable.writeTo(entityStream, httpHeaders);
+        } catch (ViewableContextException vce) {
+            throw new NotFoundException(vce);
+        }
+    }
+
+    /**
+     * Resolve the given {@link org.glassfish.jersey.server.mvc.Viewable viewable} using
+     * {@link org.glassfish.jersey.server.mvc.spi.ViewableContext}.
+     *
+     * @param viewable viewable to be resolved.
+     * @return resolved viewable or {@code null}, if the viewable cannot be resolved.
+     */
+    private ResolvedViewable resolve(final Viewable viewable) {
+        if (viewable instanceof ResolvedViewable) {
+            return (ResolvedViewable) viewable;
+        } else {
+            final ViewableContext viewableContext = getViewableContext();
+            final Set<TemplateProcessor> templateProcessors = getTemplateProcessors();
+
+            final List<MediaType> producibleMediaTypes = TemplateHelper
+                    .getProducibleMediaTypes(requestProvider.get(), extendedUriInfoProvider.get(), null);
+
+            final Class<?> resourceClass = resourceInfoProvider.get().getResourceClass();
+            if (viewable instanceof ImplicitViewable) {
+                // Template Names.
+                final ImplicitViewable implicitViewable = (ImplicitViewable) viewable;
+
+                for (final String templateName : implicitViewable.getTemplateNames()) {
+                    final Viewable simpleViewable = new Viewable(templateName, viewable.getModel());
+
+                    final ResolvedViewable resolvedViewable = resolve(simpleViewable, producibleMediaTypes,
+                            implicitViewable.getResolvingClass(), viewableContext, templateProcessors);
+
+                    if (resolvedViewable != null) {
+                        return resolvedViewable;
+                    }
+                }
+            } else {
+                return resolve(viewable, producibleMediaTypes, resourceClass, viewableContext, templateProcessors);
+            }
+
+            return null;
+        }
+    }
+
+    /**
+     * Resolve given {@link org.glassfish.jersey.server.mvc.Viewable viewable} for a list of {@link javax.ws.rs.core.MediaType mediaTypes} and a {@link Class resolvingClass}
+     * using given {@link org.glassfish.jersey.server.mvc.spi.ViewableContext viewableContext} and a set of {@link org.glassfish.jersey.server.mvc.spi.TemplateProcessor templateProcessors}
+     *
+     * @param viewable viewable to be resolved.
+     * @param mediaTypes producible media types.
+     * @param resolvingClass non-null resolving class.
+     * @param viewableContext viewable context.
+     * @param templateProcessors collection of available template processors.
+     * @return resolved viewable or {@code null}, if the viewable cannot be resolved.
+     */
+    private ResolvedViewable resolve(final Viewable viewable, final List<MediaType> mediaTypes, final Class<?> resolvingClass,
+                                     final ViewableContext viewableContext, final Set<TemplateProcessor> templateProcessors) {
+        for (TemplateProcessor templateProcessor : templateProcessors) {
+            for (final MediaType mediaType : mediaTypes) {
+                final ResolvedViewable resolvedViewable = viewableContext
+                        .resolveViewable(viewable, mediaType, resolvingClass, templateProcessor);
+
+                if (resolvedViewable != null) {
+                    return resolvedViewable;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Get a {@link java.util.LinkedHashSet collection} of available template processors.
+     *
+     * @return set of template processors.
+     */
+    private Set<TemplateProcessor> getTemplateProcessors() {
+        final Set<TemplateProcessor> templateProcessors = new LinkedHashSet<>();
+
+        templateProcessors.addAll(Providers.getCustomProviders(injectionManager, TemplateProcessor.class));
+        templateProcessors.addAll(Providers.getProviders(injectionManager, TemplateProcessor.class));
+
+        return templateProcessors;
+    }
+
+    /**
+     * Get {@link org.glassfish.jersey.server.mvc.spi.ViewableContext viewable context}. User defined (custom) contexts have higher priority than the default ones
+     * (i.e. {@link ResolvingViewableContext}).
+     *
+     * @return {@code non-null} viewable context.
+     */
+    private ViewableContext getViewableContext() {
+        final Set<ViewableContext> customProviders = Providers.getCustomProviders(injectionManager, ViewableContext.class);
+        if (!customProviders.isEmpty()) {
+            return customProviders.iterator().next();
+        }
+        return Providers.getProviders(injectionManager, ViewableContext.class).iterator().next();
+    }
+}
diff --git a/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/package-info.java b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/internal/package-info.java
new file mode 100644
index 0000000..52d1952
--- /dev/null
+++ b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/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 server-side internal MVC (Model View Controller) classes.
+ */
+package org.glassfish.jersey.server.mvc.internal;
diff --git a/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/package-info.java b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/package-info.java
new file mode 100644
index 0000000..eec0145
--- /dev/null
+++ b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/package-info.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
+ */
+
+/**
+ * Provides support for Model, View and Controller (MVC).
+ * <p/>
+ * Given the MVC pattern the Controller corresponds to a resource class, the View to a template referenced by a template name,
+ * and the Model to a Java object (or a Java bean).
+ * <p/>
+ * A resource method of a resource class may return an instance of {@link org.glassfish.jersey.server.mvc.Viewable} that
+ * encapsulates the template name and the model. In this respect the instance of{@link org.glassfish.jersey.server.mvc.Viewable}
+ * is the response entity. Such a viewable response entity may be set in contexts other than a resource method but for the
+ * purposes of this section the focus is on resource methods.
+ * <p/>
+ * The {@link org.glassfish.jersey.server.mvc.Viewable}, returned by a resource method,
+ * will be processed such that the template name is resolved to a template reference that identifies a template capable of
+ * being processed by an appropriate view processor.
+ * <br/>
+ * The view processor then processes template given the model to produce a response entity that is returned to the client.
+ * <p/>
+ * For example, the template name could reference a Java Server Page (JSP) and the model will be accessible to that JSP. The
+ * JSP view processor will process the JSP resulting in an HTML document that is returned as the response entity. (See later
+ * for more details.)
+ * <p/>
+ * Two forms of returning {@link org.glassfish.jersey.server.mvc.Viewable} instances are supported: explicit; and implicit.
+ */
+package org.glassfish.jersey.server.mvc;
diff --git a/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/spi/AbstractErrorTemplateMapper.java b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/spi/AbstractErrorTemplateMapper.java
new file mode 100644
index 0000000..c513445
--- /dev/null
+++ b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/spi/AbstractErrorTemplateMapper.java
@@ -0,0 +1,118 @@
+/*
+ * 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.server.mvc.spi;
+
+import javax.ws.rs.core.Response;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.glassfish.jersey.server.ExtendedUriInfo;
+import org.glassfish.jersey.server.model.Invocable;
+import org.glassfish.jersey.server.model.ResourceMethod;
+import org.glassfish.jersey.server.mvc.ErrorTemplate;
+import org.glassfish.jersey.server.mvc.Viewable;
+import org.glassfish.jersey.server.mvc.internal.TemplateInflector;
+import org.glassfish.jersey.spi.ExtendedExceptionMapper;
+
+/**
+ * Default implementation of {@link ExtendedExceptionMapper} used to declare special handling for exception types that should be
+ * processed by MVC.
+ * <p/>
+ * Extensions should override {@link #getErrorStatus(Throwable)} and {@link #getErrorModel(Throwable)} to provide a response
+ * status and model derived from a raised throwable.
+ * <p/>
+ * By default every {@link Exception exception} is mapped and used as a model in a viewable and passed to the MVC runtime for
+ * further processing.
+ *
+ * @param <T> A type of the exception processed by the exception mapper.
+ * @author Michal Gajdos
+ * @since 2.3
+ */
+@Singleton
+public abstract class AbstractErrorTemplateMapper<T extends Throwable> implements ExtendedExceptionMapper<T> {
+
+    @Inject
+    private javax.inject.Provider<ExtendedUriInfo> uriInfoProvider;
+
+    @Override
+    public final boolean isMappable(final T throwable) {
+        return getErrorTemplate() != null;
+    }
+
+    /**
+     * Get an {@link ErrorTemplate} annotation from resource method / class the throwable was raised from.
+     *
+     * @return an error template annotation or {@code null} if the method is not annotated.
+     */
+    private ErrorTemplate getErrorTemplate() {
+        final ExtendedUriInfo uriInfo = uriInfoProvider.get();
+        final ResourceMethod matchedResourceMethod = uriInfo.getMatchedResourceMethod();
+
+        if (matchedResourceMethod != null) {
+            final Invocable invocable = matchedResourceMethod.getInvocable();
+
+            ErrorTemplate errorTemplate = invocable.getHandlingMethod().getAnnotation(ErrorTemplate.class);
+            if (errorTemplate == null) {
+                Class<?> handlerClass = invocable.getHandler().getHandlerClass();
+
+                if (invocable.isInflector() && TemplateInflector.class
+                        .isAssignableFrom(invocable.getHandler().getHandlerClass())) {
+
+                    handlerClass = ((TemplateInflector) invocable.getHandler().getInstance(null)).getModelClass();
+                }
+
+                errorTemplate = handlerClass.getAnnotation(ErrorTemplate.class);
+            }
+
+            return errorTemplate;
+        }
+
+        return null;
+    }
+
+    @Override
+    public final Response toResponse(final T throwable) {
+        final ErrorTemplate error = getErrorTemplate();
+        final String templateName = "".equals(error.name()) ? "index" : error.name();
+
+        return Response
+                .status(getErrorStatus(throwable))
+                .entity(new Viewable(templateName, getErrorModel(throwable)))
+                .build();
+    }
+
+    /**
+     * Get a model for error template. Default value is the {@code throwable} itself.
+     *
+     * @param throwable throwable raised during processing a resource method.
+     * @return a model for error template.
+     */
+    protected Object getErrorModel(final T throwable) {
+        return throwable;
+    }
+
+    /**
+     * Get a response status of to-be-processed error template. Default value is {@link Response.Status#OK}.
+     *
+     * @param throwable throwable raised during processing a resource method.
+     * @return response status of error response.
+     */
+    protected Response.Status getErrorStatus(final T throwable) {
+        return Response.Status.OK;
+    }
+}
diff --git a/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/spi/AbstractTemplateProcessor.java b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/spi/AbstractTemplateProcessor.java
new file mode 100644
index 0000000..049ea9a
--- /dev/null
+++ b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/spi/AbstractTemplateProcessor.java
@@ -0,0 +1,313 @@
+/*
+ * 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.server.mvc.spi;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentMap;
+import java.util.function.Function;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+
+import javax.servlet.ServletContext;
+
+import org.glassfish.jersey.internal.util.PropertiesHelper;
+import org.glassfish.jersey.internal.util.ReflectionHelper;
+import org.glassfish.jersey.internal.util.collection.DataStructures;
+import org.glassfish.jersey.internal.util.collection.Value;
+import org.glassfish.jersey.server.mvc.MvcFeature;
+import org.glassfish.jersey.server.mvc.internal.LocalizationMessages;
+import org.glassfish.jersey.server.mvc.internal.TemplateHelper;
+
+/**
+ * Default implementation of {@link org.glassfish.jersey.server.mvc.spi.TemplateProcessor template processor} that can be used to
+ * implement support for custom templating engines. The class currently recognizes following properties:
+ * <ul>
+ * <li>{@link org.glassfish.jersey.server.mvc.MvcFeature#TEMPLATE_BASE_PATH}</li>
+ * <li>{@link org.glassfish.jersey.server.mvc.MvcFeature#CACHE_TEMPLATES}</li>
+ * <li>{@link org.glassfish.jersey.server.mvc.MvcFeature#TEMPLATE_OBJECT_FACTORY}</li>
+ * </ul>
+ * If any of the properties are not supported by particular template processor then this fact should be mentioned in documentation
+ * of the template processor.
+ *
+ * @author Michal Gajdos
+ */
+public abstract class AbstractTemplateProcessor<T> implements TemplateProcessor<T> {
+
+    private static final Logger LOGGER = Logger.getLogger(AbstractTemplateProcessor.class.getName());
+
+    private final ConcurrentMap<String, T> cache;
+
+    private final String suffix;
+    private final Configuration config;
+    private final ServletContext servletContext;
+
+    private final String basePath;
+    private final Set<String> supportedExtensions;
+    private final Charset encoding;
+
+    /**
+     * Create an instance of the processor with injected {@link javax.ws.rs.core.Configuration config} and
+     * (optional) {@link ServletContext servlet context}.
+     *
+     * @param config configuration to configure this processor from.
+     * @param servletContext (optional) servlet context to obtain template resources from.
+     * @param propertySuffix suffix to distinguish properties for current template processor.
+     * @param supportedExtensions supported template file extensions.
+     */
+    public AbstractTemplateProcessor(final Configuration config, final ServletContext servletContext,
+                                     final String propertySuffix, final String... supportedExtensions) {
+        this.config = config;
+        this.suffix = '.' + propertySuffix;
+
+        this.servletContext = servletContext;
+        this.supportedExtensions =
+                Arrays.stream(supportedExtensions)
+                      .map(input -> {
+                          input = input.toLowerCase();
+                          return input.startsWith(".") ? input : "." + input;
+                      })
+                      .collect(Collectors.toSet());
+
+        // Resolve property values.
+        final Map<String, Object> properties = config.getProperties();
+
+        // Base Path.
+        String basePath = PropertiesHelper.getValue(properties, MvcFeature.TEMPLATE_BASE_PATH + suffix, String.class, null);
+        if (basePath == null) {
+            basePath = PropertiesHelper.getValue(properties, MvcFeature.TEMPLATE_BASE_PATH, "", null);
+        }
+        this.basePath = basePath;
+
+        // Cache.
+        Boolean cacheEnabled = PropertiesHelper.getValue(properties, MvcFeature.CACHE_TEMPLATES + suffix, Boolean.class, null);
+        if (cacheEnabled == null) {
+            cacheEnabled = PropertiesHelper.getValue(properties, MvcFeature.CACHE_TEMPLATES, false, null);
+        }
+        this.cache = cacheEnabled ? DataStructures.<String, T>createConcurrentMap() : null;
+        this.encoding = TemplateHelper.getTemplateOutputEncoding(config, suffix);
+    }
+
+    /**
+     * Return base path for current template processor.
+     *
+     * @return base path or an empty string.
+     */
+    protected String getBasePath() {
+        return basePath;
+    }
+
+    /**
+     * Return current servlet context, if present.
+     *
+     * @return servlet context instance or {@code null}.
+     */
+    protected ServletContext getServletContext() {
+        return servletContext;
+    }
+
+    @Override
+    public T resolve(final String name, final MediaType mediaType) {
+        // Look into the cache if enabled.
+        if (cache != null) {
+            if (!cache.containsKey(name)) {
+                cache.putIfAbsent(name, resolve(name));
+            }
+            return cache.get(name);
+        }
+
+        return resolve(name);
+    }
+
+    /**
+     * Resolve a template name to a template reference.
+     *
+     * @param name the template name.
+     * @return the template reference, otherwise {@code null} if the template name cannot be resolved.
+     */
+    private T resolve(final String name) {
+        for (final String template : getTemplatePaths(name)) {
+            Reader reader = null;
+
+            // ServletContext.
+            if (servletContext != null) {
+                //"The path must begin with a "/"".
+                final String path = template.startsWith("/") ? template : "/" + template;
+                final InputStream stream = servletContext.getResourceAsStream(path);
+                reader = stream != null ? new InputStreamReader(stream) : null;
+            }
+
+            // Classloader.
+            if (reader == null) {
+                InputStream stream = getClass().getResourceAsStream(template);
+                if (stream == null) {
+                    stream = getClass().getClassLoader().getResourceAsStream(template);
+                }
+                reader = stream != null ? new InputStreamReader(stream) : null;
+            }
+
+            // File-system path.
+            if (reader == null) {
+                try {
+                    reader = new InputStreamReader(new FileInputStream(template), encoding);
+                } catch (final FileNotFoundException fnfe) {
+                    // NOOP.
+                }
+            }
+
+            if (reader != null) {
+                try {
+                    return resolve(template, reader);
+                } catch (final Exception e) {
+                    LOGGER.log(Level.WARNING, LocalizationMessages.TEMPLATE_RESOLVE_ERROR(template), e);
+                } finally {
+                    try {
+                        reader.close();
+                    } catch (final IOException e) {
+                        LOGGER.log(Level.WARNING, LocalizationMessages.TEMPLATE_ERROR_CLOSING_READER(), e);
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Resolve given template path and/or reader to a template reference object.
+     *
+     * @param templatePath resolved template path (incl. base path and suffix).
+     * @param reader reader containing template character stream.
+     * @return non-{@code null} template reference object.
+     * @throws Exception if an exception occurred during resolving.
+     */
+    protected abstract T resolve(final String templatePath, final Reader reader) throws Exception;
+
+    /**
+     * Return collection of possible template paths (included basePath and suffix).
+     *
+     * @param name the template name.
+     * @return collection of possible template paths.
+     */
+    private Collection<String> getTemplatePaths(final String name) {
+        final String lowerName = name.toLowerCase();
+        final String templatePath = basePath.endsWith("/") ? basePath + name.substring(1) : basePath + name;
+
+        // Check whether the given name ends with supported suffix.
+        for (final String extension : supportedExtensions) {
+            if (lowerName.endsWith(extension)) {
+                return Collections.singleton(templatePath);
+            }
+        }
+
+        return supportedExtensions.stream().map(input -> templatePath + input).collect(Collectors.toSet());
+    }
+
+    /**
+     * Retrieve a template object factory. The factory is, at first, looked for in
+     * {@link javax.ws.rs.core.Configuration configuration} and if not found, given default value is used.
+     *
+     * @param createInstance function that delegates a creation and an initialization to injection manager.
+     * @param type           type of requested template object factory.
+     * @param defaultValue   default value to be used if no factory reference is present in configuration.
+     * @param <F> type of requested template object factory.
+     * @return non-{@code null} template object factory.
+     */
+    protected <F> F getTemplateObjectFactory(Function<Class<?>, ?> createInstance, Class<F> type, Value<F> defaultValue) {
+        final Object objectFactoryProperty = config.getProperty(MvcFeature.TEMPLATE_OBJECT_FACTORY + suffix);
+
+        if (objectFactoryProperty != null) {
+            if (type.isAssignableFrom(objectFactoryProperty.getClass())) {
+                return type.cast(objectFactoryProperty);
+            } else {
+                Class<?> factoryClass = null;
+
+                if (objectFactoryProperty instanceof String) {
+                    factoryClass = ReflectionHelper.classForNamePA((String) objectFactoryProperty).run();
+                } else if (objectFactoryProperty instanceof Class<?>) {
+                    factoryClass = (Class<?>) objectFactoryProperty;
+                }
+
+                if (factoryClass != null) {
+                    if (type.isAssignableFrom(factoryClass)) {
+                        return type.cast(createInstance.apply(factoryClass));
+                    } else {
+                        LOGGER.log(Level.CONFIG, LocalizationMessages.WRONG_TEMPLATE_OBJECT_FACTORY(factoryClass, type));
+                    }
+                }
+            }
+        }
+
+        return defaultValue.get();
+    }
+
+    /**
+     * Set the {@link HttpHeaders#CONTENT_TYPE} header to the {@code httpHeaders} based on {@code mediaType} and
+     * {@link #getEncoding() default encoding} defined in this processor. If {@code mediaType} defines encoding
+     * then this encoding will be used otherwise the default processor encoding is used. The chosen encoding
+     * is returned from the method.
+     *
+     * @param mediaType Media type of the entity.
+     * @param httpHeaders Http headers.
+     * @return Selected encoding.
+     */
+    protected Charset setContentType(final MediaType mediaType, final MultivaluedMap<String, Object> httpHeaders) {
+        final Charset encoding;
+
+        final String charset = mediaType.getParameters().get(MediaType.CHARSET_PARAMETER);
+        final MediaType finalMediaType;
+        if (charset == null) {
+            encoding = getEncoding();
+            final HashMap<String, String> params = new HashMap<>(mediaType.getParameters());
+            params.put(MediaType.CHARSET_PARAMETER, encoding.name());
+            finalMediaType = new MediaType(mediaType.getType(), mediaType.getSubtype(), params);
+        } else {
+            encoding = Charset.forName(charset);
+            finalMediaType = mediaType;
+        }
+        final ArrayList<Object> typeList = new ArrayList<>(1);
+        typeList.add(finalMediaType.toString());
+        httpHeaders.put(HttpHeaders.CONTENT_TYPE, typeList);
+        return encoding;
+    }
+
+    /**
+     * Get the output encoding.
+     *
+     * @return Not-{@code null} encoding.
+     */
+    protected Charset getEncoding() {
+        return encoding;
+    }
+}
diff --git a/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/spi/ResolvedViewable.java b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/spi/ResolvedViewable.java
new file mode 100644
index 0000000..069b822
--- /dev/null
+++ b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/spi/ResolvedViewable.java
@@ -0,0 +1,113 @@
+/*
+ * 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.server.mvc.spi;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+
+import org.glassfish.jersey.server.mvc.Viewable;
+
+/**
+ * A resolved {@link org.glassfish.jersey.server.mvc.Viewable viewable}.
+ * <p/>
+ * A resolved viewable is obtained from the resolving methods on {@link org.glassfish.jersey.server.mvc.spi.ViewableContext}
+ * and has associated with it a {@link TemplateProcessor} that is capable of processing a template identified by a template
+ * reference.
+ *
+ * @param <T> the type of the resolved template object.
+ * @author Paul Sandoz
+ * @author Michal Gajdos
+ */
+public final class ResolvedViewable<T> extends Viewable {
+
+    private final TemplateProcessor<T> viewProcessor;
+
+    private final T templateReference;
+
+    private final MediaType mediaType;
+
+    private final Class<?> resolvingClass;
+
+
+    /**
+     * Create a resolved viewable.
+     *
+     * @param viewProcessor the view processor that resolved a template name to a template reference.
+     * @param templateReference the template reference.
+     * @param viewable the viewable that is resolved.
+     * @param mediaType media type the {@code templateReference} should be transformed into.
+     */
+    public ResolvedViewable(TemplateProcessor<T> viewProcessor, T templateReference, Viewable viewable, MediaType mediaType) {
+        this(viewProcessor, templateReference, viewable, null, mediaType);
+    }
+
+    /**
+     * Create a resolved viewable.
+     *
+     * @param viewProcessor the view processor that resolved a template name to a template reference.
+     * @param templateReference the template reference.
+     * @param viewable the viewable that is resolved.
+     * @param resolvingClass the resolving class that was used to resolve a relative template name into an absolute template name.
+     * @param mediaType media type the {@code templateReference} should be transformed into.
+     */
+    public ResolvedViewable(TemplateProcessor<T> viewProcessor, T templateReference, Viewable viewable,
+                            Class<?> resolvingClass, MediaType mediaType) {
+        super(viewable.getTemplateName(), viewable.getModel());
+
+        this.viewProcessor = viewProcessor;
+        this.templateReference = templateReference;
+        this.mediaType = mediaType;
+        this.resolvingClass = resolvingClass;
+    }
+
+    /**
+     * Write the resolved viewable.
+     * <p/>
+     * This method defers to
+     * {@link TemplateProcessor#writeTo(Object, org.glassfish.jersey.server.mvc.Viewable, javax.ws.rs.core.MediaType,
+     * javax.ws.rs.core.MultivaluedMap, java.io.OutputStream)}
+     * to write the viewable utilizing the template reference.
+     *
+     * @param out the output stream that the view processor writes to.
+     * @throws java.io.IOException if there was an error processing the template.
+     */
+    public void writeTo(OutputStream out, final MultivaluedMap<String, Object> httpHeaders) throws IOException {
+        viewProcessor.writeTo(templateReference, this, mediaType, httpHeaders, out);
+    }
+
+    /**
+     * Get the media type for which the {@link TemplateProcessor view processor} resolved the template reference.
+     *
+     * @return final {@link javax.ws.rs.core.MediaType media type} of the resolved viewable.
+     */
+    public MediaType getMediaType() {
+        return mediaType;
+    }
+
+
+    /**
+     * Get resolving class.
+     *
+     * @return Resolving class.
+     */
+    public Class<?> getResolvingClass() {
+        return resolvingClass;
+    }
+}
diff --git a/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/spi/TemplateProcessor.java b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/spi/TemplateProcessor.java
new file mode 100644
index 0000000..9d31792
--- /dev/null
+++ b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/spi/TemplateProcessor.java
@@ -0,0 +1,84 @@
+/*
+ * 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.server.mvc.spi;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import javax.ws.rs.ConstrainedTo;
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+
+import org.glassfish.jersey.server.mvc.Viewable;
+import org.glassfish.jersey.spi.Contract;
+
+/**
+ * A view processor.
+ * <p/>
+ * Implementations of this interface shall be capable of resolving a
+ * template name (+ media type) to a template reference that identifies a template supported
+ * by the implementation. And, processing the template, identified by template
+ * reference and media type, the results of which are written to an output stream.
+ * <p/>
+ * Implementations can register a view processor as a provider, for
+ * example, annotating the implementation class with {@link javax.ws.rs.ext.Provider}
+ * or registering an implementing class or instance as a singleton with
+ * {@link org.glassfish.jersey.server.ResourceConfig} or {@link javax.ws.rs.core.Application}.
+ * <p/>
+ * Such view processors could be JSP view processors (supported by the
+ * Jersey servlet and filter implementations) or say Freemarker or Velocity
+ * view processors (not implemented).
+ *
+ * @param <T> the type of the template object.
+ * @author Paul Sandoz
+ * @author Michal Gajdos
+ */
+@Contract
+@ConstrainedTo(RuntimeType.SERVER)
+public interface TemplateProcessor<T> {
+
+    /**
+     * Resolve a template name to a template reference.
+     *
+     * @param name the template name.
+     * @param mediaType requested media type of the template.
+     * @return the template reference, otherwise {@code null} if the template name cannot be resolved.
+     */
+    public T resolve(String name, MediaType mediaType);
+
+    /**
+     * Process a template and write the result to an output stream.
+     *
+     * @param templateReference the template reference. This is obtained by calling the {@link #resolve(String,
+     * javax.ws.rs.core.MediaType)} method with a template name and media type.
+     * @param viewable the viewable that contains the model to be passed to the template.
+     * @param mediaType media type the {@code templateReference} should be transformed into.
+     * @param httpHeaders http headers that will be send in the response. Headers can be modified to
+     *                    influence response headers before the the first byte is written
+     *                    to the {@code out}. After the response buffer is committed the headers modification
+     *                    has no effect. Template processor can for example set the content type of
+     *                    the response.
+     * @param out the output stream to write the result of processing the template.
+     * @throws java.io.IOException if there was an error processing the template.
+     *
+     * @since 2.7
+     */
+    public void writeTo(T templateReference, Viewable viewable, MediaType mediaType,
+                        final MultivaluedMap<String, Object> httpHeaders, OutputStream out) throws IOException;
+
+}
diff --git a/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/spi/ViewableContext.java b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/spi/ViewableContext.java
new file mode 100644
index 0000000..a6fc2fb
--- /dev/null
+++ b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/spi/ViewableContext.java
@@ -0,0 +1,61 @@
+/*
+ * 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.server.mvc.spi;
+
+import javax.ws.rs.ConstrainedTo;
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.core.MediaType;
+
+import org.glassfish.jersey.server.mvc.Viewable;
+import org.glassfish.jersey.spi.Contract;
+
+/**
+ * The context for resolving an instance of {@link org.glassfish.jersey.server.mvc.Viewable} to an instance of {@link
+ * ResolvedViewable}.
+ * <p/>
+ * Note:
+ * {@link ViewableContext#resolveViewable(org.glassfish.jersey.server.mvc.Viewable, javax.ws.rs.core.MediaType, Class, TemplateProcessor)}
+ * method may be called multiple times (combination of all the calculated possible media types of the response with all found
+ * {@link TemplateProcessor template processors}).
+ *
+ * @author Paul Sandoz
+ * @author Michal Gajdos
+ */
+@Contract
+@ConstrainedTo(RuntimeType.SERVER)
+public interface ViewableContext {
+
+    /**
+     * Resolve given {@link org.glassfish.jersey.server.mvc.Viewable viewable} using {@link javax.ws.rs.core.MediaType mediaType},
+     * {@code resourceClass} and {@link TemplateProcessor templateProcessor}.
+     * <p/>
+     * If the template name of the viewable is not absolute then the given {@code resourceClass} may be utilized to resolve
+     * the relative template name into an absolute template name.
+     * <br/>
+     * <ul>
+     * {@code resourceClass} contains class of the matched resource.
+     * </ul>
+     *
+     * @param viewable viewable to be resolved.
+     * @param mediaType media type the viewable may be transformed into.
+     * @param resourceClass resource class.
+     * @param templateProcessor template processor to be used.
+     * @return resolved viewable or {@code null} if the viewable cannot be resolved.
+     */
+    public ResolvedViewable resolveViewable(final Viewable viewable, final MediaType mediaType, final Class<?> resourceClass,
+                                            final TemplateProcessor templateProcessor);
+}
diff --git a/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/spi/ViewableContextException.java b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/spi/ViewableContextException.java
new file mode 100644
index 0000000..cf8c093
--- /dev/null
+++ b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/spi/ViewableContextException.java
@@ -0,0 +1,57 @@
+/*
+ * 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.server.mvc.spi;
+
+import org.glassfish.jersey.server.ContainerException;
+
+/**
+ * A runtime exception associated with errors when resolving a {@link org.glassfish.jersey.server.mvc.Viewable} to a {@link
+ * ResolvedViewable} by methods on {@link ViewableContext}.
+ *
+ * @author Paul Sandoz
+ */
+@SuppressWarnings("UnusedDeclaration")
+public class ViewableContextException extends ContainerException {
+
+    /**
+     * Construct a new instance with the supplied message.
+     *
+     * @param message the message.
+     */
+    public ViewableContextException(String message) {
+        super(message);
+    }
+
+    /**
+     * Construct a new instance with the supplied message and cause.
+     *
+     * @param message the message.
+     * @param cause the Throwable that caused the exception to be thrown.
+     */
+    public ViewableContextException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    /**
+     * Construct a new instance with the supplied cause.
+     *
+     * @param cause the Throwable that caused the exception to be thrown.
+     */
+    public ViewableContextException(Throwable cause) {
+        super(cause);
+    }
+}
diff --git a/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/spi/package-info.java b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/spi/package-info.java
new file mode 100644
index 0000000..ae1d3a7
--- /dev/null
+++ b/ext/mvc/src/main/java/org/glassfish/jersey/server/mvc/spi/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * 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
+ */
+
+/**
+ * Provides support for view aspect of model view controller and templates that
+ * produce views.
+ */
+package org.glassfish.jersey.server.mvc.spi;
diff --git a/ext/mvc/src/main/resources/org/glassfish/jersey/server/mvc/internal/localization.properties b/ext/mvc/src/main/resources/org/glassfish/jersey/server/mvc/internal/localization.properties
new file mode 100644
index 0000000..5b0eaa2
--- /dev/null
+++ b/ext/mvc/src/main/resources/org/glassfish/jersey/server/mvc/internal/localization.properties
@@ -0,0 +1,28 @@
+#
+# 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
+#
+
+template.handler.already.enhanced=Handler class "{0}" has been already enhanced. Skipping enhancing handler instance.
+template.model.of.view.cannot.be.null=The model of the view MUST not be null.
+template.name.could.not.be.resolved=The template name "{0}" could not be resolved to a fully qualified template name.
+template.name.must.not.be.null=The template name MUST not be null.
+# {0} - list of template names, e.g. ["sub", "index"], {1} - list of paths, e.g. ["/views", "/org/glassfish/jersey/server/mvc/TemplateResource"], {2} - list of media types, e.g. ["text/plain"]
+template.names.could.not.be.resolved=Neither of the template names ({0}) could be resolved to a fully qualified template name. (paths: {1}, media-types: {2})
+template.no.matching.resource.available=There is no last matching resource available.
+template.resolve.error=An exception occurred during resolving the {0} template.
+template.resolving.class.cannot.be.null=Resolving class MUST not be null.
+template.error.closing.reader=File reader was not closed properly.
+wrong.template.object.factory=Provided template object factory class, {0}, is not assignable from required type {1}.
+
diff --git a/ext/pom.xml b/ext/pom.xml
new file mode 100644
index 0000000..0e56abf
--- /dev/null
+++ b/ext/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.ext</groupId>
+    <artifactId>project</artifactId>
+    <packaging>pom</packaging>
+    <name>jersey-extensions</name>
+
+    <description>
+        Jersey extension modules providing utilities, filters as well as integrations
+        with external frameworks (Guice, Spring, Freemarker, etc.) and languages
+        (Scala, Groovy, etc.).
+
+        NOTE: Jersey security extensions have their own dedicated security umbrella
+        project.
+    </description>
+
+    <modules>
+        <module>bean-validation</module>
+        <module>cdi</module>
+        <module>entity-filtering</module>
+        <module>metainf-services</module>
+        <module>mvc</module>
+        <module>mvc-bean-validation</module>
+        <module>mvc-freemarker</module>
+        <module>mvc-jsp</module>
+        <module>mvc-mustache</module>
+        <module>proxy-client</module>
+        <module>rx</module>
+        <module>servlet-portability</module>
+        <module>spring4</module>
+        <module>wadl-doclet</module>
+    </modules>
+
+    <dependencies>
+        <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/ext/proxy-client/pom.xml b/ext/proxy-client/pom.xml
new file mode 100644
index 0000000..4477b0f
--- /dev/null
+++ b/ext/proxy-client/pom.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0"?>
+<!--
+
+    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.ext</groupId>
+        <artifactId>project</artifactId>
+        <version>2.28-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>jersey-proxy-client</artifactId>
+    <name>jersey-ext-proxy-client</name>
+    <description>
+        Jersey extension module providing support for (proxy-based) high-level client API.
+    </description>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <inherited>true</inherited>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Export-Package>org.glassfish.jersey.client.proxy.*;version=${project.version}</Export-Package>
+                    </instructions>
+                    <unpackBundle>true</unpackBundle>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.glassfish.jersey.core</groupId>
+            <artifactId>jersey-common</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+            <artifactId>jersey-test-framework-provider-bundle</artifactId>
+            <version>${project.version}</version>
+            <type>pom</type>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/ext/proxy-client/src/main/java/org/glassfish/jersey/client/proxy/WebResourceFactory.java b/ext/proxy-client/src/main/java/org/glassfish/jersey/client/proxy/WebResourceFactory.java
new file mode 100644
index 0000000..d457c0b
--- /dev/null
+++ b/ext/proxy-client/src/main/java/org/glassfish/jersey/client/proxy/WebResourceFactory.java
@@ -0,0 +1,367 @@
+/*
+ * 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.client.proxy;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Proxy;
+import java.lang.reflect.Type;
+import java.security.AccessController;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.CookieParam;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.FormParam;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.HttpMethod;
+import javax.ws.rs.MatrixParam;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.Invocation;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Cookie;
+import javax.ws.rs.core.Form;
+import javax.ws.rs.core.GenericEntity;
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedHashMap;
+import javax.ws.rs.core.MultivaluedMap;
+
+import org.glassfish.jersey.internal.util.ReflectionHelper;
+
+/**
+ * Factory for client-side representation of a resource.
+ * See the <a href="package-summary.html">package overview</a>
+ * for an example on how to use this class.
+ *
+ * @author Martin Matula
+ */
+public final class WebResourceFactory implements InvocationHandler {
+
+    private static final String[] EMPTY = {};
+
+    private final WebTarget target;
+    private final MultivaluedMap<String, Object> headers;
+    private final List<Cookie> cookies;
+    private final Form form;
+
+    private static final MultivaluedMap<String, Object> EMPTY_HEADERS = new MultivaluedHashMap<>();
+    private static final Form EMPTY_FORM = new Form();
+    private static final List<Class> PARAM_ANNOTATION_CLASSES = Arrays.<Class>asList(PathParam.class, QueryParam.class,
+            HeaderParam.class, CookieParam.class, MatrixParam.class, FormParam.class);
+
+    /**
+     * Creates a new client-side representation of a resource described by
+     * the interface passed in the first argument.
+     * <p/>
+     * Calling this method has the same effect as calling {@code WebResourceFactory.newResource(resourceInterface, rootTarget,
+     *false)}.
+     *
+     * @param <C> Type of the resource to be created.
+     * @param resourceInterface Interface describing the resource to be created.
+     * @param target WebTarget pointing to the resource or the parent of the resource.
+     * @return Instance of a class implementing the resource interface that can
+     * be used for making requests to the server.
+     */
+    public static <C> C newResource(final Class<C> resourceInterface, final WebTarget target) {
+        return newResource(resourceInterface, target, false, EMPTY_HEADERS, Collections.<Cookie>emptyList(), EMPTY_FORM);
+    }
+
+    /**
+     * Creates a new client-side representation of a resource described by
+     * the interface passed in the first argument.
+     *
+     * @param <C> Type of the resource to be created.
+     * @param resourceInterface Interface describing the resource to be created.
+     * @param target WebTarget pointing to the resource or the parent of the resource.
+     * @param ignoreResourcePath If set to true, ignores path annotation on the resource interface (this is used when creating
+     * sub-resources)
+     * @param headers Header params collected from parent resources (used when creating a sub-resource)
+     * @param cookies Cookie params collected from parent resources (used when creating a sub-resource)
+     * @param form Form params collected from parent resources (used when creating a sub-resource)
+     * @return Instance of a class implementing the resource interface that can
+     * be used for making requests to the server.
+     */
+    @SuppressWarnings("unchecked")
+    public static <C> C newResource(final Class<C> resourceInterface,
+                                    final WebTarget target,
+                                    final boolean ignoreResourcePath,
+                                    final MultivaluedMap<String, Object> headers,
+                                    final List<Cookie> cookies,
+                                    final Form form) {
+
+        return (C) Proxy.newProxyInstance(AccessController.doPrivileged(ReflectionHelper.getClassLoaderPA(resourceInterface)),
+                new Class[] {resourceInterface},
+                new WebResourceFactory(ignoreResourcePath ? target : addPathFromAnnotation(resourceInterface, target),
+                        headers, cookies, form));
+    }
+
+    private WebResourceFactory(final WebTarget target, final MultivaluedMap<String, Object> headers,
+                               final List<Cookie> cookies, final Form form) {
+        this.target = target;
+        this.headers = headers;
+        this.cookies = cookies;
+        this.form = form;
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
+        if (args == null && method.getName().equals("toString")) {
+            return toString();
+        }
+
+        if (args == null && method.getName().equals("hashCode")) {
+            //unique instance in the JVM, and no need to override
+            return hashCode();
+        }
+
+        if (args != null && args.length == 1 && method.getName().equals("equals")) {
+            //unique instance in the JVM, and no need to override
+            return equals(args[0]);
+        }
+
+        // get the interface describing the resource
+        final Class<?> proxyIfc = proxy.getClass().getInterfaces()[0];
+
+        // response type
+        final Class<?> responseType = method.getReturnType();
+
+        // determine method name
+        String httpMethod = getHttpMethodName(method);
+        if (httpMethod == null) {
+            for (final Annotation ann : method.getAnnotations()) {
+                httpMethod = getHttpMethodName(ann.annotationType());
+                if (httpMethod != null) {
+                    break;
+                }
+            }
+        }
+
+        // create a new UriBuilder appending the @Path attached to the method
+        WebTarget newTarget = addPathFromAnnotation(method, target);
+
+        if (httpMethod == null) {
+            if (newTarget == target) {
+                // no path annotation on the method -> fail
+                throw new UnsupportedOperationException("Not a resource method.");
+            } else if (!responseType.isInterface()) {
+                // the method is a subresource locator, but returns class,
+                // not interface - can't help here
+                throw new UnsupportedOperationException("Return type not an interface");
+            }
+        }
+
+        // process method params (build maps of (Path|Form|Cookie|Matrix|Header..)Params
+        // and extract entity type
+        final MultivaluedHashMap<String, Object> headers = new MultivaluedHashMap<String, Object>(this.headers);
+        final LinkedList<Cookie> cookies = new LinkedList<>(this.cookies);
+        final Form form = new Form();
+        form.asMap().putAll(this.form.asMap());
+        final Annotation[][] paramAnns = method.getParameterAnnotations();
+        Object entity = null;
+        Type entityType = null;
+        for (int i = 0; i < paramAnns.length; i++) {
+            final Map<Class, Annotation> anns = new HashMap<>();
+            for (final Annotation ann : paramAnns[i]) {
+                anns.put(ann.annotationType(), ann);
+            }
+            Annotation ann;
+            Object value = args[i];
+            if (!hasAnyParamAnnotation(anns)) {
+                entityType = method.getGenericParameterTypes()[i];
+                entity = value;
+            } else {
+                if (value == null && (ann = anns.get(DefaultValue.class)) != null) {
+                    value = ((DefaultValue) ann).value();
+                }
+
+                if (value != null) {
+                    if ((ann = anns.get(PathParam.class)) != null) {
+                        newTarget = newTarget.resolveTemplate(((PathParam) ann).value(), value);
+                    } else if ((ann = anns.get((QueryParam.class))) != null) {
+                        if (value instanceof Collection) {
+                            newTarget = newTarget.queryParam(((QueryParam) ann).value(), convert((Collection) value));
+                        } else {
+                            newTarget = newTarget.queryParam(((QueryParam) ann).value(), value);
+                        }
+                    } else if ((ann = anns.get((HeaderParam.class))) != null) {
+                        if (value instanceof Collection) {
+                            headers.addAll(((HeaderParam) ann).value(), convert((Collection) value));
+                        } else {
+                            headers.addAll(((HeaderParam) ann).value(), value);
+                        }
+
+                    } else if ((ann = anns.get((CookieParam.class))) != null) {
+                        final String name = ((CookieParam) ann).value();
+                        Cookie c;
+                        if (value instanceof Collection) {
+                            for (final Object v : ((Collection) value)) {
+                                if (!(v instanceof Cookie)) {
+                                    c = new Cookie(name, v.toString());
+                                } else {
+                                    c = (Cookie) v;
+                                    if (!name.equals(((Cookie) v).getName())) {
+                                        // is this the right thing to do? or should I fail? or ignore the difference?
+                                        c = new Cookie(name, c.getValue(), c.getPath(), c.getDomain(), c.getVersion());
+                                    }
+                                }
+                                cookies.add(c);
+                            }
+                        } else {
+                            if (!(value instanceof Cookie)) {
+                                cookies.add(new Cookie(name, value.toString()));
+                            } else {
+                                c = (Cookie) value;
+                                if (!name.equals(((Cookie) value).getName())) {
+                                    // is this the right thing to do? or should I fail? or ignore the difference?
+                                    cookies.add(new Cookie(name, c.getValue(), c.getPath(), c.getDomain(), c.getVersion()));
+                                }
+                            }
+                        }
+                    } else if ((ann = anns.get((MatrixParam.class))) != null) {
+                        if (value instanceof Collection) {
+                            newTarget = newTarget.matrixParam(((MatrixParam) ann).value(), convert((Collection) value));
+                        } else {
+                            newTarget = newTarget.matrixParam(((MatrixParam) ann).value(), value);
+                        }
+                    } else if ((ann = anns.get((FormParam.class))) != null) {
+                        if (value instanceof Collection) {
+                            for (final Object v : ((Collection) value)) {
+                                form.param(((FormParam) ann).value(), v.toString());
+                            }
+                        } else {
+                            form.param(((FormParam) ann).value(), value.toString());
+                        }
+                    }
+                }
+            }
+        }
+
+        if (httpMethod == null) {
+            // the method is a subresource locator
+            return WebResourceFactory.newResource(responseType, newTarget, true, headers, cookies, form);
+        }
+
+        // accepted media types
+        Produces produces = method.getAnnotation(Produces.class);
+        if (produces == null) {
+            produces = proxyIfc.getAnnotation(Produces.class);
+        }
+        final String[] accepts = (produces == null) ? EMPTY : produces.value();
+
+        // determine content type
+        String contentType = null;
+        if (entity != null) {
+            final List<Object> contentTypeEntries = headers.get(HttpHeaders.CONTENT_TYPE);
+            if ((contentTypeEntries != null) && (!contentTypeEntries.isEmpty())) {
+                contentType = contentTypeEntries.get(0).toString();
+            } else {
+                Consumes consumes = method.getAnnotation(Consumes.class);
+                if (consumes == null) {
+                    consumes = proxyIfc.getAnnotation(Consumes.class);
+                }
+                if (consumes != null && consumes.value().length > 0) {
+                    contentType = consumes.value()[0];
+                }
+            }
+        }
+
+        Invocation.Builder builder = newTarget.request()
+                .headers(headers) // this resets all headers so do this first
+                .accept(accepts); // if @Produces is defined, propagate values into Accept header; empty array is NO-OP
+
+        for (final Cookie c : cookies) {
+            builder = builder.cookie(c);
+        }
+
+        final Object result;
+
+        if (entity == null && !form.asMap().isEmpty()) {
+            entity = form;
+            contentType = MediaType.APPLICATION_FORM_URLENCODED;
+        } else {
+            if (contentType == null) {
+                contentType = MediaType.APPLICATION_OCTET_STREAM;
+            }
+            if (!form.asMap().isEmpty()) {
+                if (entity instanceof Form) {
+                    ((Form) entity).asMap().putAll(form.asMap());
+                } else {
+                    // TODO: should at least log some warning here
+                }
+            }
+        }
+
+        final GenericType responseGenericType = new GenericType(method.getGenericReturnType());
+        if (entity != null) {
+            if (entityType instanceof ParameterizedType) {
+                entity = new GenericEntity(entity, entityType);
+            }
+            result = builder.method(httpMethod, Entity.entity(entity, contentType), responseGenericType);
+        } else {
+            result = builder.method(httpMethod, responseGenericType);
+        }
+
+        return result;
+    }
+
+    private boolean hasAnyParamAnnotation(final Map<Class, Annotation> anns) {
+        for (final Class paramAnnotationClass : PARAM_ANNOTATION_CLASSES) {
+            if (anns.containsKey(paramAnnotationClass)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private Object[] convert(final Collection value) {
+        return value.toArray();
+    }
+
+    private static WebTarget addPathFromAnnotation(final AnnotatedElement ae, WebTarget target) {
+        final Path p = ae.getAnnotation(Path.class);
+        if (p != null) {
+            target = target.path(p.value());
+        }
+        return target;
+    }
+
+    @Override
+    public String toString() {
+        return target.toString();
+    }
+
+    private static String getHttpMethodName(final AnnotatedElement ae) {
+        final HttpMethod a = ae.getAnnotation(HttpMethod.class);
+        return a == null ? null : a.value();
+    }
+}
diff --git a/ext/proxy-client/src/main/java/org/glassfish/jersey/client/proxy/package-info.java b/ext/proxy-client/src/main/java/org/glassfish/jersey/client/proxy/package-info.java
new file mode 100644
index 0000000..4a957dc
--- /dev/null
+++ b/ext/proxy-client/src/main/java/org/glassfish/jersey/client/proxy/package-info.java
@@ -0,0 +1,62 @@
+/*
+ * 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
+ */
+
+/**
+ * This package defines a high-level (proxy-based) client API.
+ * The API enables utilization of the server-side JAX-RS annotations
+ * to describe the server-side resources and dynamically generate client-side
+ * proxy objects for them.
+ * <p>
+ * Consider a server which exposes a resource at http://localhost:8080. The resource
+ * can be described by the following interface:
+ * </p>
+ *
+ * <pre>
+ * &#064;Path("myresource")
+ * public interface MyResourceIfc {
+ *     &#064;GET
+ *     &#064;Produces("text/plain")
+ *     String get();
+ *
+ *     &#064;POST
+ *     &#064;Consumes("application/xml")
+ *     &#064;Produces("application/xml")
+ *     MyBean postEcho(MyBean bean);
+ *
+ *     &#064;Path("{id}")
+ *     &#064;GET
+ *     &#064;Produces("text/plain")
+ *     String getById(&#064;PathParam("id") String id);
+ * }
+ * </pre>
+ *
+ * <p>
+ * You can use <a href="WebResourceFactory.html">WebResourceFactory</a> class defined
+ * in this package to access the server-side resource using this interface.
+ * Here is an example:
+ * </p>
+ *
+ * <pre>
+ * Client client = ClientBuilder.newClient();
+ * WebTarget target = client.target("http://localhost:8080/");
+ * MyResourceIfc resource = WebResourceFactory.newResource(MyResourceIfc.class, target);
+ *
+ * String responseFromGet = resource.get();
+ * MyBean responseFromPost = resource.postEcho(myBeanInstance);
+ * String responseFromGetById = resource.getById("abc");
+ * </pre>
+ */
+package org.glassfish.jersey.client.proxy;
diff --git a/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/MyBean.java b/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/MyBean.java
new file mode 100644
index 0000000..099aebf
--- /dev/null
+++ b/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/MyBean.java
@@ -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
+ */
+
+package org.glassfish.jersey.client.proxy;
+
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement
+public class MyBean {
+    public String name;
+}
diff --git a/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/MyResource.java b/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/MyResource.java
new file mode 100644
index 0000000..00f3922
--- /dev/null
+++ b/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/MyResource.java
@@ -0,0 +1,166 @@
+/*
+ * 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.client.proxy;
+
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+
+import java.util.List;
+import java.util.Set;
+import java.util.SortedSet;
+
+public class MyResource implements MyResourceIfc {
+
+    @Context HttpHeaders headers;
+
+    @Override
+    public String getIt() {
+        return "Got it!";
+    }
+
+    @Override
+    public List<MyBean> postIt(List<MyBean> entity) {
+        return entity;
+    }
+
+    @Override
+    public MyBean postValid(@Valid MyBean entity) {
+        return entity;
+    }
+
+    @Override
+    public String getId(String id) {
+        return id;
+    }
+
+    @Override
+    public String getByName(String name) {
+        return name;
+    }
+
+    @Override
+    public String getByNameCookie(String name) {
+        return name;
+    }
+
+    @Override
+    public String getByNameHeader(String name) {
+        return name;
+    }
+
+    @Override
+    public String getByNameMatrix(String name) {
+        return name;
+    }
+
+    @Override
+    public String postByNameFormParam(String name) {
+        return name;
+    }
+
+    @Override
+    public String getByNameList(List<String> name) {
+        return name.size() + ":" + name;
+    }
+
+    @Override
+    public String getByNameSet(Set<String> name) {
+        return name.size() + ":" + name;
+    }
+
+    @Override
+    public String getByNameSortedSet(SortedSet<String> name) {
+        return name.size() + ":" + name;
+    }
+
+    @Override
+    public String getByNameCookieList(List<String> name) {
+        return name.size() + ":" + name;
+    }
+
+    @Override
+    public String getByNameCookieSet(Set<String> name) {
+        return name.size() + ":" + name;
+    }
+
+    @Override
+    public String getByNameCookieSortedSet(SortedSet<String> name) {
+        return name.size() + ":" + name;
+    }
+
+    @Override
+    public String getByNameHeaderList(List<String> name) {
+        return name.size() + ":" + name;
+    }
+
+    @Override
+    public String getByNameHeaderSet(Set<String> name) {
+        return name.size() + ":" + name;
+    }
+
+    @Override
+    public String getByNameHeaderSortedSet(SortedSet<String> name) {
+        return name.size() + ":" + name;
+    }
+
+    @Override
+    public String getByNameMatrixList(List<String> name) {
+        return name.size() + ":" + name;
+    }
+
+    @Override
+    public String getByNameMatrixSet(Set<String> name) {
+        return name.size() + ":" + name;
+    }
+
+    @Override
+    public String getByNameMatrixSortedSet(SortedSet<String> name) {
+        return name.size() + ":" + name;
+    }
+
+    @Override
+    public String postByNameFormList(List<String> name) {
+        return name.size() + ":" + name;
+    }
+
+    @Override
+    public String postByNameFormSet(Set<String> name) {
+        return name.size() + ":" + name;
+    }
+
+    @Override
+    public String postByNameFormSortedSet(SortedSet<String> name) {
+        return name.size() + ":" + name;
+    }
+
+    @Override
+    public MySubResourceIfc getSubResource() {
+        return new MySubResource();
+    }
+
+    @Override
+    public boolean isAcceptHeaderValid(HttpHeaders headers) {
+        List<MediaType> accepts = headers.getAcceptableMediaTypes();
+        return accepts.contains(MediaType.TEXT_PLAIN_TYPE) && accepts.contains(MediaType.TEXT_XML_TYPE);
+    }
+
+    @Override
+    public String putIt(MyBean dummyBean) {
+        return headers.getHeaderString(HttpHeaders.CONTENT_TYPE);
+    }
+}
diff --git a/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/MyResourceIfc.java b/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/MyResourceIfc.java
new file mode 100644
index 0000000..2b7d6fa
--- /dev/null
+++ b/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/MyResourceIfc.java
@@ -0,0 +1,174 @@
+/*
+ * 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.client.proxy;
+
+import java.util.List;
+import java.util.Set;
+import java.util.SortedSet;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.CookieParam;
+import javax.ws.rs.FormParam;
+import javax.ws.rs.GET;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.MatrixParam;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+
+@Path("myresource")
+public interface MyResourceIfc {
+    @GET
+    @Produces(MediaType.TEXT_PLAIN)
+    String getIt();
+
+    @POST
+    @Consumes({MediaType.APPLICATION_XML})
+    @Produces({MediaType.APPLICATION_XML})
+    List<MyBean> postIt(List<MyBean> entity);
+
+    @POST
+    @Path("valid")
+    @Consumes({MediaType.APPLICATION_XML})
+    @Produces({MediaType.APPLICATION_XML})
+    MyBean postValid(@Valid MyBean entity);
+
+    @Path("{id}")
+    @GET
+    @Produces(MediaType.TEXT_PLAIN)
+    String getId(@PathParam("id") String id);
+
+    @Path("query")
+    @GET
+    @Produces(MediaType.TEXT_PLAIN)
+    String getByName(@QueryParam("name") String name);
+
+    @Path("cookie")
+    @GET
+    @Produces(MediaType.TEXT_PLAIN)
+    String getByNameCookie(@CookieParam("cookie-name") String name);
+
+    @Path("header")
+    @GET
+    @Produces(MediaType.TEXT_PLAIN)
+    String getByNameHeader(@HeaderParam("header-name") String name);
+
+    @Path("matrix")
+    @GET
+    @Produces(MediaType.TEXT_PLAIN)
+    String getByNameMatrix(@MatrixParam("matrix-name") String name);
+
+    @Path("form")
+    @POST
+    @Produces(MediaType.TEXT_PLAIN)
+    String postByNameFormParam(@FormParam("form-name") String name);
+
+
+    @Path("query-list")
+    @GET
+    @Produces(MediaType.TEXT_PLAIN)
+    String getByNameList(@QueryParam("name-list") List<String> name);
+
+    @Path("query-set")
+    @GET
+    @Produces(MediaType.TEXT_PLAIN)
+    String getByNameSet(@QueryParam("name-set") Set<String> name);
+
+    @Path("query-sortedset")
+    @GET
+    @Produces(MediaType.TEXT_PLAIN)
+    String getByNameSortedSet(@QueryParam("name-sorted") SortedSet<String> name);
+
+    @Path("cookie-list")
+    @GET
+    @Produces(MediaType.TEXT_PLAIN)
+    String getByNameCookieList(@CookieParam("cookie-name-list") List<String> name);
+
+    @Path("cookie-set")
+    @GET
+    @Produces(MediaType.TEXT_PLAIN)
+    String getByNameCookieSet(@CookieParam("cookie-name-set") Set<String> name);
+
+    @Path("cookie-sortedset")
+    @GET
+    @Produces(MediaType.TEXT_PLAIN)
+    String getByNameCookieSortedSet(@CookieParam("cookie-name-sorted") SortedSet<String> name);
+
+    @Path("header-list")
+    @GET
+    @Produces(MediaType.TEXT_PLAIN)
+    String getByNameHeaderList(@HeaderParam("header-name-list") List<String> name);
+
+    @Path("header-set")
+    @GET
+    @Produces(MediaType.TEXT_PLAIN)
+    String getByNameHeaderSet(@HeaderParam("header-name-set") Set<String> name);
+
+    @Path("header-sortedset")
+    @GET
+    @Produces(MediaType.TEXT_PLAIN)
+    String getByNameHeaderSortedSet(@HeaderParam("header-name-sorted") SortedSet<String> name);
+
+    @Path("matrix-list")
+    @GET
+    @Produces(MediaType.TEXT_PLAIN)
+    String getByNameMatrixList(@MatrixParam("matrix-name-list") List<String> name);
+
+    @Path("matrix-set")
+    @GET
+    @Produces(MediaType.TEXT_PLAIN)
+    String getByNameMatrixSet(@MatrixParam("matrix-name-set") Set<String> name);
+
+    @Path("matrix-sortedset")
+    @GET
+    @Produces(MediaType.TEXT_PLAIN)
+    String getByNameMatrixSortedSet(@MatrixParam("matrix-name-sorted") SortedSet<String> name);
+
+    @Path("form-list")
+    @POST
+    @Produces(MediaType.TEXT_PLAIN)
+    String postByNameFormList(@FormParam("form-name-list") List<String> name);
+
+    @Path("form-set")
+    @POST
+    @Produces(MediaType.TEXT_PLAIN)
+    String postByNameFormSet(@FormParam("form-name-set") Set<String> name);
+
+    @Path("form-sortedset")
+    @POST
+    @Produces(MediaType.TEXT_PLAIN)
+    String postByNameFormSortedSet(@FormParam("form-name-sorted") SortedSet<String> name);
+
+    @Path("subresource")
+    MySubResourceIfc getSubResource();
+
+    @Path("isAcceptHeaderValid")
+    @GET
+    @Produces({MediaType.TEXT_PLAIN, MediaType.TEXT_XML})
+    boolean isAcceptHeaderValid(@Context HttpHeaders headers);
+
+    @Path("putIt")
+    @PUT
+    @Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
+    String putIt(MyBean dummyBean);
+}
diff --git a/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/MySubResource.java b/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/MySubResource.java
new file mode 100644
index 0000000..04772e9
--- /dev/null
+++ b/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/MySubResource.java
@@ -0,0 +1,30 @@
+/*
+ * 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.client.proxy;
+
+/**
+ *
+ * @author Martin Matula
+ */
+public class MySubResource implements MySubResourceIfc {
+    @Override
+    public MyBean getMyBean() {
+        MyBean bean = new MyBean();
+        bean.name = "Got it!";
+        return bean;
+    }
+}
diff --git a/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/MySubResourceIfc.java b/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/MySubResourceIfc.java
new file mode 100644
index 0000000..ccaa16b
--- /dev/null
+++ b/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/MySubResourceIfc.java
@@ -0,0 +1,31 @@
+/*
+ * 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.client.proxy;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+/**
+ *
+ * @author Martin Matula
+ */
+public interface MySubResourceIfc {
+    @GET
+    @Produces(MediaType.APPLICATION_XML)
+    public MyBean getMyBean();
+}
diff --git a/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/Valid.java b/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/Valid.java
new file mode 100644
index 0000000..8cc343a
--- /dev/null
+++ b/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/Valid.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
+ */
+
+package org.glassfish.jersey.client.proxy;
+
+public @interface Valid {
+}
diff --git a/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/WebResourceFactoryTest.java b/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/WebResourceFactoryTest.java
new file mode 100644
index 0000000..1f51f17
--- /dev/null
+++ b/ext/proxy-client/src/test/java/org/glassfish/jersey/client/proxy/WebResourceFactoryTest.java
@@ -0,0 +1,338 @@
+/*
+ * 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.client.proxy;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import javax.ws.rs.core.Cookie;
+import javax.ws.rs.core.Form;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedHashMap;
+import javax.ws.rs.core.MultivaluedMap;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.TestProperties;
+
+import org.junit.Ignore;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author Martin Matula
+ */
+public class WebResourceFactoryTest extends JerseyTest {
+
+    private MyResourceIfc resource;
+    private MyResourceIfc resource2;
+    private MyResourceIfc resourceWithXML;
+
+    @Override
+    protected ResourceConfig configure() {
+        // mvn test -Djersey.config.test.container.factory=org.glassfish.jersey.test.inmemory.InMemoryTestContainerFactory
+        // mvn test -Djersey.config.test.container.factory=org.glassfish.jersey.test.grizzly.GrizzlyTestContainerFactory
+        // mvn test -Djersey.config.test.container.factory=org.glassfish.jersey.test.jdkhttp.JdkHttpServerTestContainerFactory
+        // mvn test -Djersey.config.test.container.factory=org.glassfish.jersey.test.simple.SimpleTestContainerFactory
+        enable(TestProperties.LOG_TRAFFIC);
+        //        enable(TestProperties.DUMP_ENTITY);
+        return new ResourceConfig(MyResource.class);
+    }
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        resource = WebResourceFactory.newResource(MyResourceIfc.class, target());
+        resource2 = WebResourceFactory.newResource(MyResourceIfc.class, target());
+
+        final MultivaluedMap<String, Object> headers = new MultivaluedHashMap<>(1);
+        headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML);
+        resourceWithXML = WebResourceFactory
+                .newResource(MyResourceIfc.class, target(), false, headers, Collections.<Cookie>emptyList(), new Form());
+    }
+
+    @Test
+    public void testGetIt() {
+        assertEquals("Got it!", resource.getIt());
+    }
+
+    @Test
+    public void testPostIt() {
+        final MyBean bean = new MyBean();
+        bean.name = "Ahoj";
+        assertEquals("Ahoj", resource.postIt(Collections.singletonList(bean)).get(0).name);
+    }
+
+    @Test
+    public void testPostValid() {
+        final MyBean bean = new MyBean();
+        bean.name = "Ahoj";
+        assertEquals("Ahoj", resource.postValid(bean).name);
+    }
+
+    @Test
+    public void testPathParam() {
+        assertEquals("jouda", resource.getId("jouda"));
+    }
+
+    @Test
+    public void testQueryParam() {
+        assertEquals("jiri", resource.getByName("jiri"));
+    }
+
+    @Test
+    public void testFormParam() {
+        assertEquals("jiri", resource.postByNameFormParam("jiri"));
+    }
+
+    @Test
+    public void testCookieParam() {
+        assertEquals("jiri", resource.getByNameCookie("jiri"));
+    }
+
+    @Test
+    public void testHeaderParam() {
+        assertEquals("jiri", resource.getByNameHeader("jiri"));
+    }
+
+    @Test
+    public void testMatrixParam() {
+        assertEquals("jiri", resource.getByNameMatrix("jiri"));
+    }
+
+    @Test
+    public void testSubResource() {
+        assertEquals("Got it!", resource.getSubResource().getMyBean().name);
+    }
+
+    @Test
+    public void testQueryParamsAsList() {
+        final List<String> list = new ArrayList<>();
+        list.add("a");
+        list.add("bb");
+        list.add("ccc");
+
+        assertEquals("3:[a, bb, ccc]", resource.getByNameList(list));
+    }
+
+    @Test
+    public void testQueryParamsAsSet() {
+        final Set<String> set = new HashSet<>();
+        set.add("a");
+        set.add("bb");
+        set.add("ccc");
+
+        final String result = resource.getByNameSet(set);
+        checkSet(result);
+    }
+
+    @Test
+    public void testQueryParamsAsSortedSet() {
+        final SortedSet<String> set = new TreeSet<>();
+        set.add("a");
+        set.add("bb");
+        set.add("ccc");
+
+        final String result = resource.getByNameSortedSet(set);
+        assertEquals("3:[a, bb, ccc]", result);
+    }
+
+    @Test
+    @Ignore("See issue JERSEY-2441")
+    public void testHeaderCookieAsList() {
+        final List<String> list = new ArrayList<>();
+        list.add("a");
+        list.add("bb");
+        list.add("ccc");
+
+        assertEquals("3:[a, bb, ccc]", resource.getByNameCookieList(list));
+    }
+
+    @Test
+    @Ignore("See issue JERSEY-2441")
+    public void testHeaderCookieAsSet() {
+        final Set<String> set = new HashSet<>();
+        set.add("a");
+        set.add("bb");
+        set.add("ccc");
+
+        final String result = resource.getByNameCookieSet(set);
+        checkSet(result);
+    }
+
+    @Test
+    @Ignore("See issue JERSEY-2441")
+    public void testHeaderCookieAsSortedSet() {
+        final SortedSet<String> set = new TreeSet<>();
+        set.add("a");
+        set.add("bb");
+        set.add("ccc");
+
+        final String result = resource.getByNameCookieSortedSet(set);
+        assertEquals("3:[a, bb, ccc]", result);
+    }
+
+    /**
+     * This cannot work with jersey now. Server side parses header params only if they are send as more
+     * lines in the request. Jersey has currently no possibility to do so. See JERSEY-2263.
+     */
+    @Test
+    @Ignore("See issue JERSEY-2263")
+    public void testHeaderParamsAsList() {
+        final List<String> list = new ArrayList<>();
+        list.add("a");
+        list.add("bb");
+        list.add("ccc");
+
+        assertEquals("3:[a, bb, ccc]", resource.getByNameHeaderList(list));
+    }
+
+    @Test
+    @Ignore("See issue JERSEY-2263")
+    public void testHeaderParamsAsSet() {
+        final Set<String> set = new HashSet<>();
+        set.add("a");
+        set.add("bb");
+        set.add("ccc");
+
+        final String result = resource.getByNameHeaderSet(set);
+        checkSet(result);
+    }
+
+    @Test
+    @Ignore("See issue JERSEY-2263")
+    public void testHeaderParamsAsSortedSet() {
+        final SortedSet<String> set = new TreeSet<>();
+        set.add("a");
+        set.add("bb");
+        set.add("ccc");
+
+        final String result = resource.getByNameHeaderSortedSet(set);
+        assertEquals("3:[a, bb, ccc]", result);
+    }
+
+    @Test
+    public void testMatrixParamsAsList() {
+        final List<String> list = new ArrayList<>();
+        list.add("a");
+        list.add("bb");
+        list.add("ccc");
+
+        assertEquals("3:[a, bb, ccc]", resource.getByNameMatrixList(list));
+    }
+
+    @Test
+    public void testMatrixParamsAsSet() {
+        final Set<String> set = new HashSet<>();
+        set.add("a");
+        set.add("bb");
+        set.add("ccc");
+
+        final String result = resource.getByNameMatrixSet(set);
+        checkSet(result);
+    }
+
+    @Test
+    public void testMatrixParamsAsSortedSet() {
+        final SortedSet<String> set = new TreeSet<>();
+        set.add("a");
+        set.add("bb");
+        set.add("ccc");
+
+        final String result = resource.getByNameMatrixSortedSet(set);
+        assertEquals("3:[a, bb, ccc]", result);
+    }
+
+    private void checkSet(final String result) {
+        assertTrue("Set does not contain 3 items.", result.startsWith("3:["));
+        assertTrue("Set does not contain 'a' item.", result.contains("a"));
+        assertTrue("Set does not contain 'bb' item.", result.contains("bb"));
+        assertTrue("Set does not contain 'ccc' item.", result.contains("ccc"));
+    }
+
+    @Test
+    public void testFormParamsAsList() {
+        final List<String> list = new ArrayList<>();
+        list.add("a");
+        list.add("bb");
+        list.add("ccc");
+
+        assertEquals("3:[a, bb, ccc]", resource.postByNameFormList(list));
+    }
+
+    @Test
+    public void testFormParamsAsSet() {
+        final Set<String> set = new HashSet<>();
+        set.add("a");
+        set.add("bb");
+        set.add("ccc");
+
+        final String result = resource.postByNameFormSet(set);
+        checkSet(result);
+    }
+
+    @Test
+    public void testFormParamsAsSortedSet() {
+        final SortedSet<String> set = new TreeSet<>();
+        set.add("a");
+        set.add("bb");
+        set.add("ccc");
+
+        final String result = resource.postByNameFormSortedSet(set);
+        assertEquals("3:[a, bb, ccc]", result);
+    }
+
+    @Test
+    public void testAcceptHeader() {
+        assertTrue("Accept HTTP header does not match @Produces annotation", resource.isAcceptHeaderValid(null));
+    }
+
+    @Test
+    public void testPutWithExplicitContentType() {
+        assertEquals("Content-Type HTTP header does not match explicitly provided type", resourceWithXML.putIt(new MyBean()),
+                MediaType.APPLICATION_XML);
+    }
+
+    @Test
+    public void testToString() throws Exception {
+        final String actual = resource.toString();
+        final String expected = target().path("myresource").toString();
+
+        assertEquals(expected, actual);
+    }
+
+    @Test
+    public void testHashCode() throws Exception {
+        int h1 = resource.hashCode();
+        int h2 = resource2.hashCode();
+        assertNotEquals("The hash codes should not match", h1, h2);
+    }
+
+    @Test
+    public void testEquals() {
+        assertFalse("The two resource instances should not be considered equals as they are unique", resource.equals(resource2));
+    }
+}
diff --git a/ext/rx/pom.xml b/ext/rx/pom.xml
new file mode 100644
index 0000000..3826cc7
--- /dev/null
+++ b/ext/rx/pom.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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
+
+-->
+
+<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.ext</groupId>
+        <artifactId>project</artifactId>
+        <version>2.28-SNAPSHOT</version>
+    </parent>
+
+    <groupId>org.glassfish.jersey.ext.rx</groupId>
+    <artifactId>project</artifactId>
+    <packaging>pom</packaging>
+    <name>jersey-ext-rx</name>
+
+    <description>
+        Jersey incubator for Reactive (Rx) modules that are in a prototyping or research stage,
+        not ready to be released as part of Jersey (yet).
+    </description>
+
+    <modules>
+        <module>rx-client-guava</module>
+        <module>rx-client-rxjava</module>
+        <module>rx-client-rxjava2</module>
+    </modules>
+
+    <dependencies>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.hamcrest</groupId>
+            <artifactId>hamcrest-library</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/ext/rx/rx-client-guava/pom.xml b/ext/rx/rx-client-guava/pom.xml
new file mode 100644
index 0000000..8061dd1
--- /dev/null
+++ b/ext/rx/rx-client-guava/pom.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0"?>
+<!--
+
+    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
+
+-->
+
+<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.ext.rx</groupId>
+        <artifactId>project</artifactId>
+        <version>2.28-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>jersey-rx-client-guava</artifactId>
+    <name>jersey-ext-rx-client-guava</name>
+
+    <description>Jersey Reactive Client - Guava (ListenableFuture) provider.</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.glassfish.jersey.core</groupId>
+            <artifactId>jersey-client</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/ext/rx/rx-client-guava/src/main/java/org/glassfish/jersey/client/rx/guava/JerseyRxListenableFutureInvoker.java b/ext/rx/rx-client-guava/src/main/java/org/glassfish/jersey/client/rx/guava/JerseyRxListenableFutureInvoker.java
new file mode 100644
index 0000000..bb7a98a
--- /dev/null
+++ b/ext/rx/rx-client-guava/src/main/java/org/glassfish/jersey/client/rx/guava/JerseyRxListenableFutureInvoker.java
@@ -0,0 +1,74 @@
+/*
+ * 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.client.rx.guava;
+
+import java.util.concurrent.ExecutorService;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.SyncInvoker;
+import javax.ws.rs.core.GenericType;
+
+import org.glassfish.jersey.client.AbstractRxInvoker;
+import org.glassfish.jersey.internal.util.collection.LazyValue;
+import org.glassfish.jersey.internal.util.collection.Value;
+import org.glassfish.jersey.internal.util.collection.Values;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+
+/**
+ * Implementation of Reactive Invoker for {@code ListenableFuture}.
+ *
+ * @author Michal Gajdos
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ * @since 2.13
+ */
+final class JerseyRxListenableFutureInvoker extends AbstractRxInvoker<ListenableFuture> implements RxListenableFutureInvoker {
+
+    private static final LazyValue<ListeningExecutorService> DEFAULT_EXECUTOR_SERVICE =
+            Values.lazy(new Value<ListeningExecutorService>() {
+                @Override
+                public ListeningExecutorService get() {
+                    return MoreExecutors.newDirectExecutorService();
+                }
+            });
+
+    private final ListeningExecutorService service;
+
+    JerseyRxListenableFutureInvoker(final SyncInvoker syncInvoker, final ExecutorService executor) {
+        super(syncInvoker, executor);
+
+        if (executor == null) {
+            // TODO: use JAX-RS client scheduler
+            // TODO: https://java.net/jira/browse/JAX_RS_SPEC-523
+            service = DEFAULT_EXECUTOR_SERVICE.get();
+        } else {
+            service = MoreExecutors.listeningDecorator(executor);
+        }
+    }
+
+    @Override
+    public <T> ListenableFuture<T> method(final String name, final Entity<?> entity, final Class<T> responseType) {
+        return service.submit(() -> getSyncInvoker().method(name, entity, responseType));
+    }
+
+    @Override
+    public <T> ListenableFuture<T> method(final String name, final Entity<?> entity, final GenericType<T> responseType) {
+        return service.submit(() -> getSyncInvoker().method(name, entity, responseType));
+    }
+}
diff --git a/ext/rx/rx-client-guava/src/main/java/org/glassfish/jersey/client/rx/guava/RxListenableFutureInvoker.java b/ext/rx/rx-client-guava/src/main/java/org/glassfish/jersey/client/rx/guava/RxListenableFutureInvoker.java
new file mode 100644
index 0000000..d9b20c5
--- /dev/null
+++ b/ext/rx/rx-client-guava/src/main/java/org/glassfish/jersey/client/rx/guava/RxListenableFutureInvoker.java
@@ -0,0 +1,108 @@
+/*
+ * 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.client.rx.guava;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.RxInvoker;
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.Response;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+/**
+ * Reactive invoker providing support for {@link com.google.common.util.concurrent.ListenableFuture ListenableFuture} from Guava.
+ *
+ * @author Michal Gajdos
+ * @since 2.13
+ */
+public interface RxListenableFutureInvoker extends RxInvoker<ListenableFuture> {
+
+    @Override
+    public ListenableFuture<Response> get();
+
+    @Override
+    public <T> ListenableFuture<T> get(Class<T> responseType);
+
+    @Override
+    public <T> ListenableFuture<T> get(GenericType<T> responseType);
+
+    @Override
+    public ListenableFuture<Response> put(Entity<?> entity);
+
+    @Override
+    public <T> ListenableFuture<T> put(Entity<?> entity, Class<T> clazz);
+
+    @Override
+    public <T> ListenableFuture<T> put(Entity<?> entity, GenericType<T> type);
+
+    @Override
+    public ListenableFuture<Response> post(Entity<?> entity);
+
+    @Override
+    public <T> ListenableFuture<T> post(Entity<?> entity, Class<T> clazz);
+
+    @Override
+    public <T> ListenableFuture<T> post(Entity<?> entity, GenericType<T> type);
+
+    @Override
+    public ListenableFuture<Response> delete();
+
+    @Override
+    public <T> ListenableFuture<T> delete(Class<T> responseType);
+
+    @Override
+    public <T> ListenableFuture<T> delete(GenericType<T> responseType);
+
+    @Override
+    public ListenableFuture<Response> head();
+
+    @Override
+    public ListenableFuture<Response> options();
+
+    @Override
+    public <T> ListenableFuture<T> options(Class<T> responseType);
+
+    @Override
+    public <T> ListenableFuture<T> options(GenericType<T> responseType);
+
+    @Override
+    public ListenableFuture<Response> trace();
+
+    @Override
+    public <T> ListenableFuture<T> trace(Class<T> responseType);
+
+    @Override
+    public <T> ListenableFuture<T> trace(GenericType<T> responseType);
+
+    @Override
+    public ListenableFuture<Response> method(String name);
+
+    @Override
+    public <T> ListenableFuture<T> method(String name, Class<T> responseType);
+
+    @Override
+    public <T> ListenableFuture<T> method(String name, GenericType<T> responseType);
+
+    @Override
+    public ListenableFuture<Response> method(String name, Entity<?> entity);
+
+    @Override
+    public <T> ListenableFuture<T> method(String name, Entity<?> entity, Class<T> responseType);
+
+    @Override
+    public <T> ListenableFuture<T> method(String name, Entity<?> entity, GenericType<T> responseType);
+}
diff --git a/ext/rx/rx-client-guava/src/main/java/org/glassfish/jersey/client/rx/guava/RxListenableFutureInvokerProvider.java b/ext/rx/rx-client-guava/src/main/java/org/glassfish/jersey/client/rx/guava/RxListenableFutureInvokerProvider.java
new file mode 100644
index 0000000..ac97dd8
--- /dev/null
+++ b/ext/rx/rx-client-guava/src/main/java/org/glassfish/jersey/client/rx/guava/RxListenableFutureInvokerProvider.java
@@ -0,0 +1,42 @@
+/*
+ * 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.client.rx.guava;
+
+import java.util.concurrent.ExecutorService;
+
+import javax.ws.rs.client.RxInvokerProvider;
+import javax.ws.rs.client.SyncInvoker;
+
+/**
+ * Invoker provider for invokers based on Guava's {@code ListenableFuture}.
+ *
+ * @author Michal Gajdos
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ * @since 2.13
+ */
+public final class RxListenableFutureInvokerProvider implements RxInvokerProvider<RxListenableFutureInvoker> {
+
+    @Override
+    public boolean isProviderFor(Class clazz) {
+        return RxListenableFutureInvoker.class.equals(clazz);
+    }
+
+    @Override
+    public JerseyRxListenableFutureInvoker getRxInvoker(SyncInvoker syncInvoker, ExecutorService executorService) {
+        return new JerseyRxListenableFutureInvoker(syncInvoker, executorService);
+    }
+}
diff --git a/ext/rx/rx-client-guava/src/main/java/org/glassfish/jersey/client/rx/guava/package-info.java b/ext/rx/rx-client-guava/src/main/java/org/glassfish/jersey/client/rx/guava/package-info.java
new file mode 100644
index 0000000..4e5eb1c
--- /dev/null
+++ b/ext/rx/rx-client-guava/src/main/java/org/glassfish/jersey/client/rx/guava/package-info.java
@@ -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
+ */
+
+/**
+ * Jersey Reactive Client - Guava (ListenableFuture) provider.
+ */
+package org.glassfish.jersey.client.rx.guava;
diff --git a/ext/rx/rx-client-guava/src/main/resources/META-INF/services/org.glassfish.jersey.client.rx.spi.RxInvokerProvider b/ext/rx/rx-client-guava/src/main/resources/META-INF/services/org.glassfish.jersey.client.rx.spi.RxInvokerProvider
new file mode 100644
index 0000000..4246699
--- /dev/null
+++ b/ext/rx/rx-client-guava/src/main/resources/META-INF/services/org.glassfish.jersey.client.rx.spi.RxInvokerProvider
@@ -0,0 +1 @@
+org.glassfish.jersey.client.rx.guava.RxListenableFutureInvokerProvider
\ No newline at end of file
diff --git a/ext/rx/rx-client-guava/src/test/java/org/glassfish/jersey/client/rx/guava/RxListenableFutureTest.java b/ext/rx/rx-client-guava/src/test/java/org/glassfish/jersey/client/rx/guava/RxListenableFutureTest.java
new file mode 100644
index 0000000..43a97cf
--- /dev/null
+++ b/ext/rx/rx-client-guava/src/test/java/org/glassfish/jersey/client/rx/guava/RxListenableFutureTest.java
@@ -0,0 +1,163 @@
+/*
+ * 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.client.rx.guava;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+
+import javax.ws.rs.NotFoundException;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.internal.guava.ThreadFactoryBuilder;
+import org.glassfish.jersey.process.JerseyProcessingUncaughtExceptionHandler;
+
+import org.hamcrest.Matcher;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.core.Is.is;
+
+/**
+ * @author Michal Gajdos
+ */
+public class RxListenableFutureTest {
+
+    private Client client;
+    private ExecutorService executor;
+
+    @Before
+    public void setUp() throws Exception {
+        client = ClientBuilder.newClient().register(TerminalClientRequestFilter.class);
+        executor = new ScheduledThreadPoolExecutor(1, new ThreadFactoryBuilder()
+                .setNameFormat("jersey-rx-client-test-%d")
+                .setUncaughtExceptionHandler(new JerseyProcessingUncaughtExceptionHandler())
+                .build());
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        executor.shutdown();
+        client = null;
+    }
+
+    @Test
+    public void testNotFoundResponse() throws Exception {
+        client.register(RxListenableFutureInvokerProvider.class);
+
+        final RxListenableFutureInvoker invoker = client.target("http://jersey.java.net")
+                                                        .request()
+                                                        .header("Response-Status", 404)
+                                                        .rx(RxListenableFutureInvoker.class);
+
+        testInvoker(invoker, 404, false);
+    }
+
+    @Test(expected = NotFoundException.class)
+    public void testNotFoundReadEntityViaClass() throws Throwable {
+        client.register(RxListenableFutureInvokerProvider.class);
+
+        try {
+            client.target("http://jersey.java.net")
+                  .request()
+                  .header("Response-Status", 404)
+                  .rx(RxListenableFutureInvoker.class)
+                  .get(String.class)
+                  .get();
+        } catch (final Exception expected) {
+
+            // java.util.concurrent.ExecutionException
+            throw expected
+                    // javax.ws.rs.NotFoundException
+                    .getCause();
+        }
+    }
+
+    @Test(expected = NotFoundException.class)
+    public void testNotFoundReadEntityViaGenericType() throws Throwable {
+        client.register(RxListenableFutureInvokerProvider.class);
+
+        try {
+            client.target("http://jersey.java.net")
+                  .request()
+                  .header("Response-Status", 404)
+                  .rx(RxListenableFutureInvoker.class)
+                  .get(new GenericType<String>() {
+                  })
+                  .get();
+        } catch (final Exception expected) {
+
+            expected.printStackTrace();
+
+            // java.util.concurrent.ExecutionException
+            throw expected
+                    // javax.ws.rs.NotFoundException
+                    .getCause();
+        }
+    }
+
+    @Test
+    public void testReadEntityViaClass() throws Throwable {
+        client.register(RxListenableFutureInvokerProvider.class);
+
+        final String response = client.target("http://jersey.java.net")
+                                      .request()
+                                      .rx(RxListenableFutureInvoker.class)
+                                      .get(String.class)
+                                      .get();
+
+        assertThat(response, is("NO-ENTITY"));
+    }
+
+    @Test
+    public void testReadEntityViaGenericType() throws Throwable {
+        client.register(RxListenableFutureInvokerProvider.class);
+
+        final String response = client.target("http://jersey.java.net")
+                                      .request()
+                                      .rx(RxListenableFutureInvoker.class)
+                                      .get(new GenericType<String>() {
+                                      })
+                                      .get();
+
+        assertThat(response, is("NO-ENTITY"));
+    }
+
+    private void testInvoker(final RxListenableFutureInvoker rx,
+                             final int expectedStatus,
+                             final boolean testDedicatedThread) throws Exception {
+        testResponse(rx.get().get(), expectedStatus, testDedicatedThread);
+    }
+
+    private static void testResponse(final Response response, final int expectedStatus, final boolean testDedicatedThread) {
+        assertThat(response.getStatus(), is(expectedStatus));
+        assertThat(response.readEntity(String.class), is("NO-ENTITY"));
+
+        // Executor.
+        final Matcher<String> matcher = containsString("jersey-rx-client-test");
+        assertThat(response.getHeaderString("Test-Thread"), testDedicatedThread ? matcher : not(matcher));
+
+        // Properties.
+        assertThat(response.getHeaderString("Test-Uri"), is("http://jersey.java.net"));
+        assertThat(response.getHeaderString("Test-Method"), is("GET"));
+    }
+}
diff --git a/ext/rx/rx-client-guava/src/test/java/org/glassfish/jersey/client/rx/guava/TerminalClientRequestFilter.java b/ext/rx/rx-client-guava/src/test/java/org/glassfish/jersey/client/rx/guava/TerminalClientRequestFilter.java
new file mode 100644
index 0000000..b1adaec
--- /dev/null
+++ b/ext/rx/rx-client-guava/src/test/java/org/glassfish/jersey/client/rx/guava/TerminalClientRequestFilter.java
@@ -0,0 +1,62 @@
+/*
+ * 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.client.rx.guava;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.List;
+
+import javax.ws.rs.client.ClientRequestContext;
+import javax.ws.rs.client.ClientRequestFilter;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+
+/**
+ * @author Michal Gajdos
+ */
+class TerminalClientRequestFilter implements ClientRequestFilter {
+
+    @Override
+    public void filter(final ClientRequestContext requestContext) throws IOException {
+        // Obtain entity - from request or create new.
+        final ByteArrayInputStream entity = new ByteArrayInputStream(
+                requestContext.hasEntity() ? requestContext.getEntity().toString().getBytes() : "NO-ENTITY".getBytes()
+        );
+
+        final int responseStatus = requestContext.getHeaders().getFirst("Response-Status") != null
+                ? (int) requestContext.getHeaders().getFirst("Response-Status") : 200;
+        Response.ResponseBuilder response = Response.status(responseStatus)
+                .entity(entity)
+                .type("application/json")
+                // Test properties.
+                .header("Test-Thread", Thread.currentThread().getName())
+                .header("Test-Uri", requestContext.getUri().toString())
+                .header("Test-Method", requestContext.getMethod());
+
+        // Request headers -> Response headers (<header> -> Test-Header-<header>)
+        for (final MultivaluedMap.Entry<String, List<String>> entry : requestContext.getStringHeaders().entrySet()) {
+            response = response.header("Test-Header-" + entry.getKey(), entry.getValue());
+        }
+
+        // Request properties -> Response headers (<header> -> Test-Property-<header>)
+        for (final String property : requestContext.getPropertyNames()) {
+            response = response.header("Test-Property-" + property, requestContext.getProperty(property));
+        }
+
+        requestContext.abortWith(response.build());
+    }
+}
diff --git a/ext/rx/rx-client-rxjava/pom.xml b/ext/rx/rx-client-rxjava/pom.xml
new file mode 100644
index 0000000..0afec67
--- /dev/null
+++ b/ext/rx/rx-client-rxjava/pom.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0"?>
+<!--
+
+    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
+
+-->
+
+<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.ext.rx</groupId>
+        <artifactId>project</artifactId>
+        <version>2.28-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>jersey-rx-client-rxjava</artifactId>
+    <name>jersey-ext-rx-client-rxjava</name>
+
+    <description>Jersey Reactive Client - RxJava (Observable) provider.</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.glassfish.jersey.core</groupId>
+            <artifactId>jersey-client</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>io.reactivex</groupId>
+            <artifactId>rxjava</artifactId>
+            <version>${rxjava.version}</version>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/ext/rx/rx-client-rxjava/src/main/java/org/glassfish/jersey/client/rx/rxjava/JerseyRxObservableInvoker.java b/ext/rx/rx-client-rxjava/src/main/java/org/glassfish/jersey/client/rx/rxjava/JerseyRxObservableInvoker.java
new file mode 100644
index 0000000..cfc2b80
--- /dev/null
+++ b/ext/rx/rx-client-rxjava/src/main/java/org/glassfish/jersey/client/rx/rxjava/JerseyRxObservableInvoker.java
@@ -0,0 +1,82 @@
+/*
+ * 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.client.rx.rxjava;
+
+import java.util.concurrent.ExecutorService;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.SyncInvoker;
+import javax.ws.rs.core.GenericType;
+
+import org.glassfish.jersey.client.AbstractRxInvoker;
+
+import rx.Observable;
+import rx.Scheduler;
+import rx.schedulers.Schedulers;
+
+/**
+ * Implementation of Reactive Invoker for {@code Observable}. If no executor service is provided the JAX-RS Async client is used
+ * to retrieve data when a subscriber is subscribed. When an executor service is provided a sync call is invoked on a thread
+ * provided on from this service.
+ *
+ * @author Michal Gajdos
+ * @since 2.13
+ */
+final class JerseyRxObservableInvoker extends AbstractRxInvoker<Observable> implements RxObservableInvoker {
+
+    JerseyRxObservableInvoker(final SyncInvoker syncInvoker, final ExecutorService executor) {
+        super(syncInvoker, executor);
+    }
+
+    @Override
+    public <T> Observable<T> method(final String name, final Entity<?> entity, final Class<T> responseType) {
+        return method(name, entity, new GenericType<T>(responseType) { });
+    }
+
+    @Override
+    public <T> Observable<T> method(final String name, final Entity<?> entity, final GenericType<T> responseType) {
+        final Scheduler scheduler;
+
+        if (getExecutorService() != null) {
+            scheduler = Schedulers.from(getExecutorService());
+        } else {
+            // TODO: use JAX-RS client scheduler
+            // TODO: https://java.net/jira/browse/JAX_RS_SPEC-523
+            scheduler = Schedulers.io();
+        }
+
+        // Invoke as sync JAX-RS client request and subscribe/observe on a scheduler initialized with executor service.
+        return Observable.create((Observable.OnSubscribe<T>) subscriber -> {
+            if (!subscriber.isUnsubscribed()) {
+                try {
+                    final T response = getSyncInvoker().method(name, entity, responseType);
+
+                    if (!subscriber.isUnsubscribed()) {
+                        subscriber.onNext(response);
+                    }
+                    if (!subscriber.isUnsubscribed()) {
+                        subscriber.onCompleted();
+                    }
+                } catch (final Throwable throwable) {
+                    if (!subscriber.isUnsubscribed()) {
+                        subscriber.onError(throwable);
+                    }
+                }
+            }
+        }).subscribeOn(scheduler).observeOn(scheduler);
+    }
+}
diff --git a/ext/rx/rx-client-rxjava/src/main/java/org/glassfish/jersey/client/rx/rxjava/RxObservableInvoker.java b/ext/rx/rx-client-rxjava/src/main/java/org/glassfish/jersey/client/rx/rxjava/RxObservableInvoker.java
new file mode 100644
index 0000000..60d6243
--- /dev/null
+++ b/ext/rx/rx-client-rxjava/src/main/java/org/glassfish/jersey/client/rx/rxjava/RxObservableInvoker.java
@@ -0,0 +1,115 @@
+/*
+ * 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.client.rx.rxjava;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.RxInvoker;
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.Response;
+
+import rx.Observable;
+
+/**
+ * Reactive invoker providing support for {@link rx.Observable observable} from RxJava.
+ * <p/>
+ * Requests are by default invoked on a separate thread (as JAX-RS Async client requests). This behavior can be overridden by
+ * providing a {@link java.util.concurrent.ExecutorService executor service} when client extension is being created
+ * (in {@link org.glassfish.jersey.client.rx.rxjava.RxObservable RxObservable}).
+ * <p/>
+ * The observables produced by method calls are cold observables. That means that request to the service is invoked only when a
+ * subscriber is subscribed to the observable.
+ *
+ * @author Michal Gajdos
+ * @since 2.13
+ */
+public interface RxObservableInvoker extends RxInvoker<Observable> {
+
+    @Override
+    public Observable<Response> get();
+
+    @Override
+    public <T> Observable<T> get(Class<T> responseType);
+
+    @Override
+    public <T> Observable<T> get(GenericType<T> responseType);
+
+    @Override
+    public Observable<Response> put(Entity<?> entity);
+
+    @Override
+    public <T> Observable<T> put(Entity<?> entity, Class<T> clazz);
+
+    @Override
+    public <T> Observable<T> put(Entity<?> entity, GenericType<T> type);
+
+    @Override
+    public Observable<Response> post(Entity<?> entity);
+
+    @Override
+    public <T> Observable<T> post(Entity<?> entity, Class<T> clazz);
+
+    @Override
+    public <T> Observable<T> post(Entity<?> entity, GenericType<T> type);
+
+    @Override
+    public Observable<Response> delete();
+
+    @Override
+    public <T> Observable<T> delete(Class<T> responseType);
+
+    @Override
+    public <T> Observable<T> delete(GenericType<T> responseType);
+
+    @Override
+    public Observable<Response> head();
+
+    @Override
+    public Observable<Response> options();
+
+    @Override
+    public <T> Observable<T> options(Class<T> responseType);
+
+    @Override
+    public <T> Observable<T> options(GenericType<T> responseType);
+
+    @Override
+    public Observable<Response> trace();
+
+    @Override
+    public <T> Observable<T> trace(Class<T> responseType);
+
+    @Override
+    public <T> Observable<T> trace(GenericType<T> responseType);
+
+    @Override
+    public Observable<Response> method(String name);
+
+    @Override
+    public <T> Observable<T> method(String name, Class<T> responseType);
+
+    @Override
+    public <T> Observable<T> method(String name, GenericType<T> responseType);
+
+    @Override
+    public Observable<Response> method(String name, Entity<?> entity);
+
+    @Override
+    public <T> Observable<T> method(String name, Entity<?> entity, Class<T> responseType);
+
+    @Override
+    public <T> Observable<T> method(String name, Entity<?> entity, GenericType<T> responseType);
+}
diff --git a/ext/rx/rx-client-rxjava/src/main/java/org/glassfish/jersey/client/rx/rxjava/RxObservableInvokerProvider.java b/ext/rx/rx-client-rxjava/src/main/java/org/glassfish/jersey/client/rx/rxjava/RxObservableInvokerProvider.java
new file mode 100644
index 0000000..05591ec
--- /dev/null
+++ b/ext/rx/rx-client-rxjava/src/main/java/org/glassfish/jersey/client/rx/rxjava/RxObservableInvokerProvider.java
@@ -0,0 +1,42 @@
+/*
+ * 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.client.rx.rxjava;
+
+import java.util.concurrent.ExecutorService;
+
+import javax.ws.rs.client.RxInvokerProvider;
+import javax.ws.rs.client.SyncInvoker;
+
+/**
+ * Invoker provider for invokers based on RxJava's {@code Observable}.
+ *
+ * @author Michal Gajdos
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ * @since 2.13
+ */
+public final class RxObservableInvokerProvider implements RxInvokerProvider<RxObservableInvoker> {
+
+    @Override
+    public boolean isProviderFor(Class clazz) {
+        return RxObservableInvoker.class.equals(clazz);
+    }
+
+    @Override
+    public RxObservableInvoker getRxInvoker(SyncInvoker syncInvoker, ExecutorService executorService) {
+        return new JerseyRxObservableInvoker(syncInvoker, executorService);
+    }
+}
diff --git a/ext/rx/rx-client-rxjava/src/main/java/org/glassfish/jersey/client/rx/rxjava/package-info.java b/ext/rx/rx-client-rxjava/src/main/java/org/glassfish/jersey/client/rx/rxjava/package-info.java
new file mode 100644
index 0000000..1e1c4af
--- /dev/null
+++ b/ext/rx/rx-client-rxjava/src/main/java/org/glassfish/jersey/client/rx/rxjava/package-info.java
@@ -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
+ */
+
+/**
+ * Jersey Reactive Client - RxJava (Observable) provider.
+ */
+package org.glassfish.jersey.client.rx.rxjava;
diff --git a/ext/rx/rx-client-rxjava/src/main/resources/META-INF/services/org.glassfish.jersey.client.rx.spi.RxInvokerProvider b/ext/rx/rx-client-rxjava/src/main/resources/META-INF/services/org.glassfish.jersey.client.rx.spi.RxInvokerProvider
new file mode 100644
index 0000000..2e85ada
--- /dev/null
+++ b/ext/rx/rx-client-rxjava/src/main/resources/META-INF/services/org.glassfish.jersey.client.rx.spi.RxInvokerProvider
@@ -0,0 +1 @@
+org.glassfish.jersey.client.rx.rxjava.RxObservableInvokerProvider
\ No newline at end of file
diff --git a/ext/rx/rx-client-rxjava/src/test/java/org/glassfish/jersey/client/rx/rxjava/RxObservableTest.java b/ext/rx/rx-client-rxjava/src/test/java/org/glassfish/jersey/client/rx/rxjava/RxObservableTest.java
new file mode 100644
index 0000000..3ca121f
--- /dev/null
+++ b/ext/rx/rx-client-rxjava/src/test/java/org/glassfish/jersey/client/rx/rxjava/RxObservableTest.java
@@ -0,0 +1,208 @@
+/*
+ * 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.client.rx.rxjava;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.ws.rs.NotFoundException;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.internal.guava.ThreadFactoryBuilder;
+import org.glassfish.jersey.process.JerseyProcessingUncaughtExceptionHandler;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.core.Is.is;
+
+import rx.Subscriber;
+
+/**
+ * @author Michal Gajdos
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ */
+public class RxObservableTest {
+
+    private Client client;
+    private Client clientWithExecutor;
+    private ExecutorService executor;
+
+    @Before
+    public void setUp() throws Exception {
+        client = ClientBuilder.newClient().register(TerminalClientRequestFilter.class);
+        client.register(RxObservableInvokerProvider.class);
+        executor = new ScheduledThreadPoolExecutor(1, new ThreadFactoryBuilder()
+                .setNameFormat("jersey-rx-client-test-%d")
+                .setUncaughtExceptionHandler(new JerseyProcessingUncaughtExceptionHandler())
+                .build());
+
+        clientWithExecutor = ClientBuilder.newBuilder().executorService(executor).build();
+        clientWithExecutor.register(TerminalClientRequestFilter.class);
+        clientWithExecutor.register(RxObservableInvokerProvider.class);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        executor.shutdown();
+
+        client.close();
+        client = null;
+    }
+
+    @Test
+    public void testNotFoundResponse() throws Exception {
+        final RxObservableInvoker invoker = client.target("http://jersey.java.net")
+                                                  .request()
+                                                  .header("Response-Status", 404)
+                                                  .rx(RxObservableInvoker.class);
+
+        testInvoker(invoker, 404, false);
+    }
+
+    @Test
+    public void testNotFoundWithCustomExecutor() throws Exception {
+        final RxObservableInvoker invoker = clientWithExecutor.target("http://jersey.java.net")
+                                                              .request()
+                                                              .header("Response-Status", 404)
+                                                              .rx(RxObservableInvoker.class);
+
+        testInvoker(invoker, 404, true);
+    }
+
+    @Test(expected = NotFoundException.class)
+    public void testNotFoundReadEntityViaClass() throws Throwable {
+        try {
+            client.target("http://jersey.java.net")
+                  .request()
+                  .header("Response-Status", 404)
+                  .rx(RxObservableInvoker.class)
+                  .get(String.class)
+                  .toBlocking()
+                  .toFuture()
+                  .get();
+        } catch (final Exception expected) {
+            // java.util.concurrent.ExecutionException
+            throw expected
+                    // javax.ws.rs.ProcessingException
+                    // .getCause()
+                    // javax.ws.rs.NotFoundException
+                    .getCause();
+        }
+    }
+
+    @Test(expected = NotFoundException.class)
+    public void testNotFoundReadEntityViaGenericType() throws Throwable {
+        try {
+            client.target("http://jersey.java.net")
+                  .request()
+                  .header("Response-Status", 404)
+                  .rx(RxObservableInvoker.class)
+                  .get(new GenericType<String>() { })
+                  .toBlocking()
+                  .toFuture()
+                  .get();
+        } catch (final Exception expected) {
+
+            expected.printStackTrace();
+
+            // java.util.concurrent.ExecutionException
+            throw expected
+                    // javax.ws.rs.NotFoundException
+                    .getCause();
+        }
+    }
+
+    @Test
+    public void testReadEntityViaClass() throws Throwable {
+        final String response = client.target("http://jersey.java.net")
+                                      .request()
+                                      .rx(RxObservableInvoker.class)
+                                      .get(String.class)
+                                      .toBlocking()
+                                      .toFuture()
+                                      .get();
+
+        assertThat(response, is("NO-ENTITY"));
+    }
+
+    @Test
+    public void testReadEntityViaGenericType() throws Throwable {
+        final String response = client.target("http://jersey.java.net")
+                                      .request()
+                                      .rx(RxObservableInvoker.class)
+                                      .get(new GenericType<String>() { })
+                                      .toBlocking()
+                                      .toFuture()
+                                      .get();
+
+        assertThat(response, is("NO-ENTITY"));
+    }
+
+    private void testInvoker(final RxObservableInvoker rx, final int expectedStatus, final boolean testDedicatedThread)
+            throws Exception {
+        final CountDownLatch latch = new CountDownLatch(1);
+        final AtomicReference<Response> responseRef = new AtomicReference<>();
+        final AtomicReference<Throwable> errorRef = new AtomicReference<>();
+
+        rx.get().subscribe(new Subscriber<Response>() {
+            @Override
+            public void onCompleted() {
+                latch.countDown();
+            }
+
+            @Override
+            public void onError(final Throwable e) {
+                errorRef.set(e);
+                latch.countDown();
+            }
+
+            @Override
+            public void onNext(final Response response) {
+                responseRef.set(response);
+            }
+        });
+
+        latch.await();
+
+        if (errorRef.get() == null) {
+            testResponse(responseRef.get(), expectedStatus, testDedicatedThread);
+        } else {
+            throw (Exception) errorRef.get();
+        }
+    }
+
+    private static void testResponse(final Response response, final int expectedStatus, final boolean testDedicatedThread) {
+        assertThat(response.getStatus(), is(expectedStatus));
+        assertThat(response.readEntity(String.class), is("NO-ENTITY"));
+
+        // Executor.
+        assertThat(response.getHeaderString("Test-Thread"), testDedicatedThread
+                ? containsString("jersey-rx-client-test") : containsString("jersey-client-async-executor"));
+
+        // Properties.
+        assertThat(response.getHeaderString("Test-Uri"), is("http://jersey.java.net"));
+        assertThat(response.getHeaderString("Test-Method"), is("GET"));
+    }
+}
diff --git a/ext/rx/rx-client-rxjava/src/test/java/org/glassfish/jersey/client/rx/rxjava/TerminalClientRequestFilter.java b/ext/rx/rx-client-rxjava/src/test/java/org/glassfish/jersey/client/rx/rxjava/TerminalClientRequestFilter.java
new file mode 100644
index 0000000..113961f
--- /dev/null
+++ b/ext/rx/rx-client-rxjava/src/test/java/org/glassfish/jersey/client/rx/rxjava/TerminalClientRequestFilter.java
@@ -0,0 +1,62 @@
+/*
+ * 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.client.rx.rxjava;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.List;
+
+import javax.ws.rs.client.ClientRequestContext;
+import javax.ws.rs.client.ClientRequestFilter;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+
+/**
+ * @author Michal Gajdos
+ */
+class TerminalClientRequestFilter implements ClientRequestFilter {
+
+    @Override
+    public void filter(final ClientRequestContext requestContext) throws IOException {
+        // Obtain entity - from request or create new.
+        final ByteArrayInputStream entity = new ByteArrayInputStream(
+                requestContext.hasEntity() ? requestContext.getEntity().toString().getBytes() : "NO-ENTITY".getBytes()
+        );
+
+        final int responseStatus = requestContext.getHeaders().getFirst("Response-Status") != null
+                ? (int) requestContext.getHeaders().getFirst("Response-Status") : 200;
+        Response.ResponseBuilder response = Response.status(responseStatus)
+                .entity(entity)
+                .type("text/plain")
+                // Test properties.
+                .header("Test-Thread", Thread.currentThread().getName())
+                .header("Test-Uri", requestContext.getUri().toString())
+                .header("Test-Method", requestContext.getMethod());
+
+        // Request headers -> Response headers (<header> -> Test-Header-<header>)
+        for (final MultivaluedMap.Entry<String, List<String>> entry : requestContext.getStringHeaders().entrySet()) {
+            response = response.header("Test-Header-" + entry.getKey(), entry.getValue());
+        }
+
+        // Request properties -> Response headers (<header> -> Test-Property-<header>)
+        for (final String property : requestContext.getPropertyNames()) {
+            response = response.header("Test-Property-" + property, requestContext.getProperty(property));
+        }
+
+        requestContext.abortWith(response.build());
+    }
+}
diff --git a/ext/rx/rx-client-rxjava2/pom.xml b/ext/rx/rx-client-rxjava2/pom.xml
new file mode 100644
index 0000000..36614ce
--- /dev/null
+++ b/ext/rx/rx-client-rxjava2/pom.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0"?>
+<!--
+
+    Copyright (c) 2017, 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.ext.rx</groupId>
+        <artifactId>project</artifactId>
+        <version>2.28-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>jersey-rx-client-rxjava2</artifactId>
+    <name>jersey-ext-rx-client-rxjava2</name>
+
+    <description>Jersey Reactive Client - RxJava2 (Flowable) provider.</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.glassfish.jersey.core</groupId>
+            <artifactId>jersey-client</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>io.reactivex.rxjava2</groupId>
+            <artifactId>rxjava</artifactId>
+            <version>${rxjava2.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.reactivestreams</groupId>
+            <artifactId>reactive-streams</artifactId>
+            <version>1.0.0</version>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/ext/rx/rx-client-rxjava2/src/main/java/org/glassfish/jersey/client/rx/rxjava2/JerseyRxFlowableInvoker.java b/ext/rx/rx-client-rxjava2/src/main/java/org/glassfish/jersey/client/rx/rxjava2/JerseyRxFlowableInvoker.java
new file mode 100644
index 0000000..e357319
--- /dev/null
+++ b/ext/rx/rx-client-rxjava2/src/main/java/org/glassfish/jersey/client/rx/rxjava2/JerseyRxFlowableInvoker.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2017, 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.client.rx.rxjava2;
+
+import java.util.concurrent.ExecutorService;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.SyncInvoker;
+import javax.ws.rs.core.GenericType;
+
+import org.glassfish.jersey.client.AbstractRxInvoker;
+
+import io.reactivex.BackpressureStrategy;
+import io.reactivex.Flowable;
+import io.reactivex.FlowableEmitter;
+import io.reactivex.FlowableOnSubscribe;
+import io.reactivex.Scheduler;
+import io.reactivex.schedulers.Schedulers;
+
+/**
+ * Implementation of Reactive Invoker for {@code Flowable}. If no executor service is provided the JAX-RS Async client is used
+ * to retrieve data when a subscriber is subscribed. When an executor service is provided a sync call is invoked on a thread
+ * provided on from this service.
+ *
+ * @author Michal Gajdos
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ * @since 2.16
+ */
+final class JerseyRxFlowableInvoker extends AbstractRxInvoker<Flowable> implements RxFlowableInvoker {
+
+    JerseyRxFlowableInvoker(SyncInvoker syncInvoker, ExecutorService executor) {
+        super(syncInvoker, executor);
+    }
+
+    @Override
+    public <T> Flowable<T> method(final String name, final Entity<?> entity, final Class<T> responseType) {
+        return method(name, entity, new GenericType<T>(responseType) { });
+    }
+
+    @Override
+    public <T> Flowable<T> method(final String name, final Entity<?> entity, final GenericType<T> responseType) {
+        final Scheduler scheduler;
+
+        if (getExecutorService() != null) {
+            scheduler = Schedulers.from(getExecutorService());
+        } else {
+            // TODO: use JAX-RS client scheduler
+            // TODO: https://java.net/jira/browse/JAX_RS_SPEC-523
+            scheduler = Schedulers.io();
+        }
+
+        // Invoke as sync JAX-RS client request and subscribe/observe on a scheduler initialized with executor service.
+        return Flowable.create(new FlowableOnSubscribe<T>() {
+            @Override
+            public void subscribe(FlowableEmitter<T> flowableEmitter) throws Exception {
+                    try {
+                        final T response = getSyncInvoker().method(name, entity, responseType);
+                        flowableEmitter.onNext(response);
+                        flowableEmitter.onComplete();
+                    } catch (final Throwable throwable) {
+                        flowableEmitter.onError(throwable);
+                    }
+            }
+        }, BackpressureStrategy.DROP).subscribeOn(scheduler).observeOn(scheduler);
+    }
+}
diff --git a/ext/rx/rx-client-rxjava2/src/main/java/org/glassfish/jersey/client/rx/rxjava2/RxFlowableInvoker.java b/ext/rx/rx-client-rxjava2/src/main/java/org/glassfish/jersey/client/rx/rxjava2/RxFlowableInvoker.java
new file mode 100644
index 0000000..ac9026c
--- /dev/null
+++ b/ext/rx/rx-client-rxjava2/src/main/java/org/glassfish/jersey/client/rx/rxjava2/RxFlowableInvoker.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2017, 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.client.rx.rxjava2;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.RxInvoker;
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.Response;
+
+import io.reactivex.Flowable;
+
+
+/**
+ * Reactive invoker providing support for {@link Flowable flowable} from RxJava.
+ * <p/>
+ * Requests are by default invoked on a separate thread (as JAX-RS Async client requests). This behavior can be overridden by
+ * providing a {@link java.util.concurrent.ExecutorService executor service} when client extension is being created.
+ *
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ * @author Michal Gajdos
+ * @since 2.26
+ */
+public interface RxFlowableInvoker extends RxInvoker<Flowable> {
+
+    @Override
+    Flowable<Response> get();
+
+    @Override
+    <R> Flowable<R> get(Class<R> responseType);
+
+    @Override
+    <R> Flowable<R> get(GenericType<R> responseType);
+
+    @Override
+    Flowable<Response> put(Entity<?> entity);
+
+    @Override
+    <R> Flowable<R> put(Entity<?> entity, Class<R> responseType);
+
+    @Override
+    <R> Flowable<R> put(Entity<?> entity, GenericType<R> responseType);
+
+    @Override
+    Flowable<Response> post(Entity<?> entity);
+
+    @Override
+    <R> Flowable<R> post(Entity<?> entity, Class<R> responseType);
+
+    @Override
+    <R> Flowable<R> post(Entity<?> entity, GenericType<R> responseType);
+
+    @Override
+    Flowable<Response> delete();
+
+    @Override
+    <R> Flowable<R> delete(Class<R> responseType);
+
+    @Override
+    <R> Flowable<R> delete(GenericType<R> responseType);
+
+    @Override
+    Flowable<Response> head();
+
+    @Override
+    Flowable<Response> options();
+
+    @Override
+    <R> Flowable<R> options(Class<R> responseType);
+
+    @Override
+    <R> Flowable<R> options(GenericType<R> responseType);
+
+    @Override
+    Flowable<Response> trace();
+
+    @Override
+    <R> Flowable<R> trace(Class<R> responseType);
+
+    @Override
+    <R> Flowable<R> trace(GenericType<R> responseType);
+
+    @Override
+    Flowable<Response> method(String name);
+
+    @Override
+    <R> Flowable<R> method(String name, Class<R> responseType);
+
+    @Override
+    <R> Flowable<R> method(String name, GenericType<R> responseType);
+
+    @Override
+    Flowable<Response> method(String name, Entity<?> entity);
+
+    @Override
+    <R> Flowable<R> method(String name, Entity<?> entity, Class<R> responseType);
+
+    @Override
+    <R> Flowable<R> method(String name, Entity<?> entity, GenericType<R> responseType);
+}
diff --git a/ext/rx/rx-client-rxjava2/src/main/java/org/glassfish/jersey/client/rx/rxjava2/RxFlowableInvokerProvider.java b/ext/rx/rx-client-rxjava2/src/main/java/org/glassfish/jersey/client/rx/rxjava2/RxFlowableInvokerProvider.java
new file mode 100644
index 0000000..fca6e80
--- /dev/null
+++ b/ext/rx/rx-client-rxjava2/src/main/java/org/glassfish/jersey/client/rx/rxjava2/RxFlowableInvokerProvider.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2017, 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.client.rx.rxjava2;
+
+import java.util.concurrent.ExecutorService;
+
+import javax.ws.rs.client.RxInvokerProvider;
+import javax.ws.rs.client.SyncInvoker;
+
+
+/**
+ * Invoker provider for invokers based on RxJava's {@code Flowable}.
+ *
+ * @author Michal Gajdos
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ * @since 2.26
+ */
+public class RxFlowableInvokerProvider implements RxInvokerProvider<RxFlowableInvoker> {
+
+    @Override
+    public boolean isProviderFor(Class<?> clazz) {
+        return RxFlowableInvoker.class.equals(clazz);
+    }
+
+    @Override
+    public RxFlowableInvoker getRxInvoker(SyncInvoker syncInvoker, ExecutorService executorService) {
+        return new JerseyRxFlowableInvoker(syncInvoker, executorService);
+    }
+}
diff --git a/ext/rx/rx-client-rxjava2/src/test/java/org/glassfish/jersey/client/rx/rxjava2/RxFlowableTest.java b/ext/rx/rx-client-rxjava2/src/test/java/org/glassfish/jersey/client/rx/rxjava2/RxFlowableTest.java
new file mode 100644
index 0000000..b5a72b0
--- /dev/null
+++ b/ext/rx/rx-client-rxjava2/src/test/java/org/glassfish/jersey/client/rx/rxjava2/RxFlowableTest.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (c) 2017, 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.client.rx.rxjava2;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.ws.rs.NotFoundException;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.internal.guava.ThreadFactoryBuilder;
+import org.glassfish.jersey.process.JerseyProcessingUncaughtExceptionHandler;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.reactivestreams.Subscriber;
+import org.reactivestreams.Subscription;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.core.Is.is;
+
+/**
+ * @author Pavel Bucek (pavel.bucek at oracle.com)
+ */
+public class RxFlowableTest {
+
+    private Client client;
+    private Client clientWithExecutor;
+    private ExecutorService executor;
+
+    @Before
+    public void setUp() throws Exception {
+        client = ClientBuilder.newClient().register(TerminalClientRequestFilter.class);
+        client.register(RxFlowableInvokerProvider.class);
+        executor = new ScheduledThreadPoolExecutor(1, new ThreadFactoryBuilder()
+                .setNameFormat("jersey-rx-client-test-%d")
+                .setUncaughtExceptionHandler(new JerseyProcessingUncaughtExceptionHandler())
+                .build());
+
+        clientWithExecutor = ClientBuilder.newBuilder().executorService(executor).build();
+        clientWithExecutor.register(TerminalClientRequestFilter.class);
+        clientWithExecutor.register(RxFlowableInvokerProvider.class);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        executor.shutdown();
+
+        client.close();
+        client = null;
+    }
+
+    @Test
+    public void testNotFoundResponse() throws Exception {
+        final RxFlowableInvoker invoker = client.target("http://jersey.java.net")
+                                                .request()
+                                                .header("Response-Status", 404)
+                                                .rx(RxFlowableInvoker.class);
+
+        testInvoker(invoker, 404, false);
+    }
+
+    @Test
+    public void testNotFoundWithCustomExecutor() throws Exception {
+        final RxFlowableInvoker invoker = clientWithExecutor.target("http://jersey.java.net")
+                                                            .request()
+                                                            .header("Response-Status", 404)
+                                                            .rx(RxFlowableInvoker.class);
+
+        testInvoker(invoker, 404, true);
+    }
+
+    @Test(expected = NotFoundException.class)
+    public void testNotFoundReadEntityViaClass() throws Throwable {
+        try {
+            client.target("http://jersey.java.net")
+                  .request()
+                  .header("Response-Status", 404)
+                  .rx(RxFlowableInvoker.class)
+                  .get(String.class)
+                  .blockingFirst();
+        } catch (final Exception expected) {
+            throw expected;
+        }
+    }
+
+    @Test(expected = NotFoundException.class)
+    public void testNotFoundReadEntityViaGenericType() throws Throwable {
+        try {
+            client.target("http://jersey.java.net")
+                  .request()
+                  .header("Response-Status", 404)
+                  .rx(RxFlowableInvoker.class)
+                  .get(new GenericType<String>() {
+                  })
+                  .blockingFirst();
+        } catch (final Exception expected) {
+            throw expected;
+        }
+    }
+
+    @Test
+    public void testReadEntityViaClass() throws Throwable {
+        final String response = client.target("http://jersey.java.net")
+                                      .request()
+                                      .rx(RxFlowableInvoker.class)
+                                      .get(String.class)
+                                      .blockingFirst();
+
+        assertThat(response, is("NO-ENTITY"));
+    }
+
+    @Test
+    public void testReadEntityViaGenericType() throws Throwable {
+        final String response = client.target("http://jersey.java.net")
+                                      .request()
+                                      .rx(RxFlowableInvoker.class)
+                                      .get(new GenericType<String>() { })
+                                      .blockingFirst();
+
+        assertThat(response, is("NO-ENTITY"));
+    }
+
+    private void testInvoker(final RxFlowableInvoker rx,
+                             final int expectedStatus,
+                             final boolean testDedicatedThread)
+            throws Exception {
+        final CountDownLatch latch = new CountDownLatch(1);
+        final AtomicReference<Response> responseRef = new AtomicReference<>();
+        final AtomicReference<Throwable> errorRef = new AtomicReference<>();
+
+        rx.get().subscribe(new Subscriber<Response>() {
+
+            @Override
+            public void onSubscribe(Subscription s) {
+                s.request(Long.MAX_VALUE);
+            }
+
+            @Override
+            public void onComplete() {
+                latch.countDown();
+            }
+
+            @Override
+            public void onError(final Throwable e) {
+                errorRef.set(e);
+                latch.countDown();
+            }
+
+            @Override
+            public void onNext(final Response response) {
+                responseRef.set(response);
+            }
+        });
+
+        latch.await();
+
+        if (errorRef.get() == null) {
+            testResponse(responseRef.get(), expectedStatus, testDedicatedThread);
+        } else {
+            throw (Exception) errorRef.get();
+        }
+    }
+
+    private static void testResponse(final Response response, final int expectedStatus, final boolean testDedicatedThread) {
+        assertThat(response.getStatus(), is(expectedStatus));
+        assertThat(response.readEntity(String.class), is("NO-ENTITY"));
+
+        // Executor.
+        assertThat(response.getHeaderString("Test-Thread"), testDedicatedThread
+                ? containsString("jersey-rx-client-test") : containsString("jersey-client-async-executor"));
+
+        // Properties.
+        assertThat(response.getHeaderString("Test-Uri"), is("http://jersey.java.net"));
+        assertThat(response.getHeaderString("Test-Method"), is("GET"));
+    }
+}
diff --git a/ext/rx/rx-client-rxjava2/src/test/java/org/glassfish/jersey/client/rx/rxjava2/TerminalClientRequestFilter.java b/ext/rx/rx-client-rxjava2/src/test/java/org/glassfish/jersey/client/rx/rxjava2/TerminalClientRequestFilter.java
new file mode 100644
index 0000000..c724a41
--- /dev/null
+++ b/ext/rx/rx-client-rxjava2/src/test/java/org/glassfish/jersey/client/rx/rxjava2/TerminalClientRequestFilter.java
@@ -0,0 +1,62 @@
+/*
+ * 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.client.rx.rxjava2;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.List;
+
+import javax.ws.rs.client.ClientRequestContext;
+import javax.ws.rs.client.ClientRequestFilter;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+
+/**
+ * @author Michal Gajdos
+ */
+class TerminalClientRequestFilter implements ClientRequestFilter {
+
+    @Override
+    public void filter(final ClientRequestContext requestContext) throws IOException {
+        // Obtain entity - from request or create new.
+        final ByteArrayInputStream entity = new ByteArrayInputStream(
+                requestContext.hasEntity() ? requestContext.getEntity().toString().getBytes() : "NO-ENTITY".getBytes()
+        );
+
+        final int responseStatus = requestContext.getHeaders().getFirst("Response-Status") != null
+                ? (int) requestContext.getHeaders().getFirst("Response-Status") : 200;
+        Response.ResponseBuilder response = Response.status(responseStatus)
+                                                    .entity(entity)
+                                                    .type("text/plain")
+                                                    // Test properties.
+                                                    .header("Test-Thread", Thread.currentThread().getName())
+                                                    .header("Test-Uri", requestContext.getUri().toString())
+                                                    .header("Test-Method", requestContext.getMethod());
+
+        // Request headers -> Response headers (<header> -> Test-Header-<header>)
+        for (final MultivaluedMap.Entry<String, List<String>> entry : requestContext.getStringHeaders().entrySet()) {
+            response = response.header("Test-Header-" + entry.getKey(), entry.getValue());
+        }
+
+        // Request properties -> Response headers (<header> -> Test-Property-<header>)
+        for (final String property : requestContext.getPropertyNames()) {
+            response = response.header("Test-Property-" + property, requestContext.getProperty(property));
+        }
+
+        requestContext.abortWith(response.build());
+    }
+}
diff --git a/ext/servlet-portability/pom.xml b/ext/servlet-portability/pom.xml
new file mode 100644
index 0000000..6e0fada
--- /dev/null
+++ b/ext/servlet-portability/pom.xml
@@ -0,0 +1,77 @@
+<?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>
+        <artifactId>project</artifactId>
+        <groupId>org.glassfish.jersey.ext</groupId>
+        <version>2.28-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>jersey-servlet-portability</artifactId>
+    <packaging>jar</packaging>
+    <name>jersey-ext-servlet-portability</name>
+
+    <description>
+        Library that enables writing web applications that run with both Jersey 1.x and Jersey 2.x servlet containers.
+    </description>
+
+    <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>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>javax.ws.rs</groupId>
+            <artifactId>javax.ws.rs-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.sun.jersey</groupId>
+            <artifactId>jersey-servlet</artifactId>
+            <version>${jersey1.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.jersey.containers</groupId>
+            <artifactId>jersey-container-servlet-core</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>servlet-api</artifactId>
+            <version>${servlet2.version}</version>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/ext/servlet-portability/src/main/java/org/glassfish/jersey/servlet/portability/PortableServletContainer.java b/ext/servlet-portability/src/main/java/org/glassfish/jersey/servlet/portability/PortableServletContainer.java
new file mode 100644
index 0000000..206d494
--- /dev/null
+++ b/ext/servlet-portability/src/main/java/org/glassfish/jersey/servlet/portability/PortableServletContainer.java
@@ -0,0 +1,228 @@
+/*
+ * 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.portability;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.Servlet;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+/**
+ * Jersey Servlet/Filter class that can be referenced in web.xml instead of Jersey 1.x specific
+ * {@code com.sun.jersey.spi.container.servlet.ServletContainer} and Jersey 2.x specific
+ * {@link org.glassfish.jersey.servlet.ServletContainer} to enable web application portability between
+ * Jersey 1.x and Jersey 2.x servlet containers.
+ * <p>
+ *     Since for some of the {@link org.glassfish.jersey.servlet.ServletProperties servlet init parameters} that can be
+ *     specified in web.xml you may want different values depending on which version of Jersey container is present,
+ *     You can prefix the init parameter name either with {@code jersey1#} or {@code jersey2#} to
+ *     make it specific to a given version. For example, to specify different values for
+ *     {@code javax.ws.rs.Application} init parameter depending on the version of Jersey used, you can include
+ *     the following in your web.xml:
+ *     <pre>
+ *     &lt;servlet&gt;
+ *         &lt;servlet-name&gt;Jersey Web Application&lt;/servlet-name&gt;
+ *         &lt;servlet-class&gt;org.glassfish.jersey.servlet.portability.PortableServletContainer&lt;/servlet-class&gt;
+ *         &lt;init-param&gt;
+ *             &lt;param-name&gt;jersey1#javax.ws.rs.Application&lt;/param-name&gt;
+ *             &lt;param-value&gt;myapp.jersey1specific.Jersey1Application&lt;/param-value&gt;
+ *         &lt;/init-param&gt;
+ *         &lt;init-param&gt;
+ *             &lt;param-name&gt;jersey2#javax.ws.rs.Application&lt;/param-name&gt;
+ *             &lt;param-value&gt;myapp.jersey2specific.Jersey2Application&lt;/param-value&gt;
+ *         &lt;/init-param&gt;
+ *     &lt;/servlet&gt;
+ *     </pre>
+ * </p>
+ *
+ * @author Martin Matula
+ */
+public class PortableServletContainer implements Filter, Servlet {
+
+    private static final String JERSEY_1_PREFIX = "jersey1#";
+    private static final String JERSEY_2_PREFIX = "jersey2#";
+
+    private final Servlet wrappedServlet;
+    private final Filter wrappedFilter;
+    private final String includePrefix;
+    private final String excludePrefix;
+
+    /**
+     * Create a new servlet container.
+     */
+    @SuppressWarnings("unchecked")
+    public PortableServletContainer() {
+        Class<Servlet> servletClass;
+        boolean isJersey1 = false;
+        try {
+            servletClass = (Class<Servlet>) Class.forName("com.sun.jersey.spi.container.servlet.ServletContainer");
+            isJersey1 = true;
+        } catch (ClassNotFoundException e) {
+            // Jersey 1.x not present, try Jersey 2.x
+            try {
+                servletClass = (Class<Servlet>) Class.forName("org.glassfish.jersey.servlet.ServletContainer");
+            } catch (ClassNotFoundException ex) {
+                throw new IllegalStateException(LocalizationMessages.JERSEY_NOT_AVAILABLE());
+            }
+        }
+
+        try {
+            wrappedServlet = servletClass.newInstance();
+            wrappedFilter = (Filter) wrappedServlet;
+        } catch (Exception e) {
+            throw new RuntimeException(LocalizationMessages.JERSEY_CONTAINER_CANT_LOAD(), e);
+        }
+        includePrefix = isJersey1 ? JERSEY_1_PREFIX : JERSEY_2_PREFIX;
+        excludePrefix = isJersey1 ? JERSEY_2_PREFIX : JERSEY_1_PREFIX;
+    }
+
+    @Override
+    public void init(final FilterConfig filterConfig) throws ServletException {
+        wrappedFilter.init(new FilterConfigWrapper(filterConfig));
+    }
+
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+            throws IOException, ServletException {
+        wrappedFilter.doFilter(request, response, chain);
+    }
+
+    @Override
+    public void init(ServletConfig config) throws ServletException {
+        wrappedServlet.init(new ServletConfigWrapper(config));
+    }
+
+    @Override
+    public ServletConfig getServletConfig() {
+        return wrappedServlet.getServletConfig();
+    }
+
+    @Override
+    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
+        wrappedServlet.service(req, res);
+    }
+
+    @Override
+    public String getServletInfo() {
+        return wrappedServlet.getServletInfo();
+    }
+
+    @Override
+    public void destroy() {
+        wrappedServlet.destroy();
+    }
+
+    private abstract class InitParamsWrapper {
+
+        private final HashMap<String, String> filteredInitParams = new HashMap<String, String>();
+
+        void init() {
+            for (Enumeration e = getInitParamNames(); e.hasMoreElements(); ) {
+                String name = (String) e.nextElement();
+                String value = getInitParamValue(name);
+                if (name.startsWith(includePrefix)) {
+                    name = name.substring(includePrefix.length());
+                } else if (name.startsWith(excludePrefix)) {
+                    continue;
+                }
+                filteredInitParams.put(name, value);
+            }
+        }
+
+        abstract String getInitParamValue(String name);
+
+        abstract Enumeration getInitParamNames();
+
+        public String getInitParameter(String name) {
+            return filteredInitParams.get(name);
+        }
+
+        public Enumeration getInitParameterNames() {
+            return Collections.enumeration(filteredInitParams.keySet());
+        }
+    }
+
+    private class FilterConfigWrapper extends InitParamsWrapper implements FilterConfig {
+
+        private final FilterConfig wrapped;
+
+        FilterConfigWrapper(FilterConfig wrapped) {
+            this.wrapped = wrapped;
+            init();
+        }
+
+        @Override
+        public String getFilterName() {
+            return wrapped.getFilterName();
+        }
+
+        @Override
+        public ServletContext getServletContext() {
+            return wrapped.getServletContext();
+        }
+
+        @Override
+        String getInitParamValue(String name) {
+            return wrapped.getInitParameter(name);
+        }
+
+        @Override
+        Enumeration getInitParamNames() {
+            return wrapped.getInitParameterNames();
+        }
+    }
+
+    private class ServletConfigWrapper extends InitParamsWrapper implements ServletConfig {
+
+        private final ServletConfig wrapped;
+
+        ServletConfigWrapper(ServletConfig wrapped) {
+            this.wrapped = wrapped;
+            init();
+        }
+
+        @Override
+        String getInitParamValue(String name) {
+            return wrapped.getInitParameter(name);
+        }
+
+        @Override
+        Enumeration getInitParamNames() {
+            return wrapped.getInitParameterNames();
+        }
+
+        @Override
+        public String getServletName() {
+            return wrapped.getServletName();
+        }
+
+        @Override
+        public ServletContext getServletContext() {
+            return wrapped.getServletContext();
+        }
+    }
+}
diff --git a/ext/servlet-portability/src/main/java/org/glassfish/jersey/servlet/portability/package-info.java b/ext/servlet-portability/src/main/java/org/glassfish/jersey/servlet/portability/package-info.java
new file mode 100644
index 0000000..2629627
--- /dev/null
+++ b/ext/servlet-portability/src/main/java/org/glassfish/jersey/servlet/portability/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * 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 containing utility classes enabling web applications to be portable between different versions of Jersey
+ * runtime.
+ */
+package org.glassfish.jersey.servlet.portability;
diff --git a/ext/servlet-portability/src/main/resources/org.glassfish.jersey.servlet.portability/localization.properties b/ext/servlet-portability/src/main/resources/org.glassfish.jersey.servlet.portability/localization.properties
new file mode 100644
index 0000000..03f03a4
--- /dev/null
+++ b/ext/servlet-portability/src/main/resources/org.glassfish.jersey.servlet.portability/localization.properties
@@ -0,0 +1,18 @@
+#
+# 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.container.cant.load=Unable to instantiate Jersey ServletContainer.
+jersey.not.available=No Jersey runtime found on the application classpath.
diff --git a/ext/spring3/src/main/java/org/glassfish/jersey/server/spring/scope/JaxrsServletRequestAttributes.java b/ext/spring3/src/main/java/org/glassfish/jersey/server/spring/scope/JaxrsServletRequestAttributes.java
new file mode 100644
index 0000000..6da105f
--- /dev/null
+++ b/ext/spring3/src/main/java/org/glassfish/jersey/server/spring/scope/JaxrsServletRequestAttributes.java
@@ -0,0 +1,55 @@
+/*
+ * 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.server.spring.scope;
+
+import javax.ws.rs.container.ContainerRequestContext;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+/**
+ * JAX-RS based Spring RequestAttributes implementation for Servlet-based applications.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+class JaxrsServletRequestAttributes extends ServletRequestAttributes {
+
+    private final ContainerRequestContext requestContext;
+
+    /**
+     * Create a new JAX-RS ServletRequestAttributes instance for the given request.
+     *
+     * @param request        current HTTP request
+     * @param requestContext JAX-RS request context
+     */
+    public JaxrsServletRequestAttributes(final HttpServletRequest request, final ContainerRequestContext requestContext) {
+        super(request);
+        this.requestContext = requestContext;
+    }
+
+    @Override
+    public Object resolveReference(String key) {
+        if (REFERENCE_REQUEST.equals(key)) {
+            return this.requestContext;
+        } else if (REFERENCE_SESSION.equals(key)) {
+            return super.getSession(true);
+        } else {
+            return null;
+        }
+    }
+}
diff --git a/ext/spring4/pom.xml b/ext/spring4/pom.xml
new file mode 100644
index 0000000..450eba3
--- /dev/null
+++ b/ext/spring4/pom.xml
@@ -0,0 +1,194 @@
+<?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.ext</groupId>
+        <artifactId>project</artifactId>
+        <version>2.28-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>jersey-spring4</artifactId>
+    <name>jersey-spring4</name>
+
+    <packaging>jar</packaging>
+
+    <description>
+        Jersey extension module providing support for Spring 4 integration.
+    </description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.glassfish.jersey.core</groupId>
+            <artifactId>jersey-server</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.glassfish.jersey.inject</groupId>
+            <artifactId>jersey-hk2</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.glassfish.jersey.containers</groupId>
+            <artifactId>jersey-container-servlet-core</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+            <artifactId>jersey-test-framework-provider-grizzly2</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>commons-logging</groupId>
+            <artifactId>commons-logging</artifactId>
+            <version>1.2</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.glassfish.hk2</groupId>
+            <artifactId>hk2</artifactId>
+            <version>${hk2.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.glassfish.hk2</groupId>
+            <artifactId>spring-bridge</artifactId>
+            <version>${hk2.version}</version>
+            <exclusions>
+                <exclusion>  <!-- already pulled in by jersey-server -->
+                    <groupId>javax.inject</groupId>
+                    <artifactId>javax.inject</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.glassfish.hk2</groupId>
+                    <artifactId>hk2-api</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-beans</artifactId>
+            <version>${spring4.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-core</artifactId>
+            <version>${spring4.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>commons-logging</groupId>
+                    <artifactId>commons-logging</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-web</artifactId>
+            <version>${spring4.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-aop</artifactId>
+            <version>${spring4.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>javax.servlet-api</artifactId>
+            <version>3.0.1</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.glassfish.jersey.test-framework</groupId>
+            <artifactId>jersey-test-framework-core</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.aspectj</groupId>
+            <artifactId>aspectjrt</artifactId>
+            <version>1.6.11</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.aspectj</groupId>
+            <artifactId>aspectjweaver</artifactId>
+            <version>1.6.11</version>
+            <scope>test</scope>
+        </dependency>
+
+    </dependencies>
+
+    <properties>
+        <spring4.version>4.3.4.RELEASE</spring4.version>
+    </properties>
+
+    <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>
+        </plugins>
+    </build>
+
+    <profiles>
+        <profile>
+            <id>delayed-strategy-skip-test</id>
+            <activation>
+                <property>
+                    <name>org.glassfish.jersey.injection.manager.strategy</name>
+                    <value>delayed</value>
+                </property>
+            </activation>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-surefire-plugin</artifactId>
+                        <configuration>
+                            <skipTests>true</skipTests>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
+</project>
diff --git a/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/AutowiredInjectResolver.java b/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/AutowiredInjectResolver.java
new file mode 100644
index 0000000..36b8178
--- /dev/null
+++ b/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/AutowiredInjectResolver.java
@@ -0,0 +1,119 @@
+/*
+ * 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.server.spring;
+
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.logging.Logger;
+
+import javax.inject.Singleton;
+
+import org.glassfish.jersey.internal.inject.Injectee;
+import org.glassfish.jersey.internal.inject.InjectionResolver;
+
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.beans.factory.config.DependencyDescriptor;
+import org.springframework.context.ApplicationContext;
+import org.springframework.core.MethodParameter;
+
+/**
+ * HK2 injection resolver for Spring framework {@link Autowired} annotation injection.
+ *
+ * @author Marko Asplund (marko.asplund at yahoo.com)
+ * @author Vetle Leinonen-Roeim (vetle at roeim.net)
+ */
+@Singleton
+public class AutowiredInjectResolver implements InjectionResolver<Autowired> {
+
+    private static final Logger LOGGER = Logger.getLogger(AutowiredInjectResolver.class.getName());
+
+    private volatile ApplicationContext ctx;
+
+    /**
+     * Create a new instance.
+     *
+     * @param ctx Spring application context.
+     */
+    public AutowiredInjectResolver(ApplicationContext ctx) {
+        this.ctx = ctx;
+    }
+
+    @Override
+    public Object resolve(Injectee injectee) {
+        AnnotatedElement parent = injectee.getParent();
+        String beanName = null;
+        if (parent != null) {
+            Qualifier an = parent.getAnnotation(Qualifier.class);
+            if (an != null) {
+                beanName = an.value();
+            }
+        }
+        boolean required = parent != null ? parent.getAnnotation(Autowired.class).required() : false;
+        return getBeanFromSpringContext(beanName, injectee, required);
+    }
+
+    private Object getBeanFromSpringContext(String beanName, Injectee injectee, final boolean required) {
+        try {
+            DependencyDescriptor dependencyDescriptor = createSpringDependencyDescriptor(injectee);
+            Set<String> autowiredBeanNames = new HashSet<>(1);
+            autowiredBeanNames.add(beanName);
+            return ctx.getAutowireCapableBeanFactory().resolveDependency(dependencyDescriptor, null,
+                    autowiredBeanNames, null);
+        } catch (NoSuchBeanDefinitionException e) {
+            if (required) {
+                LOGGER.warning(e.getMessage());
+                throw e;
+            }
+            return null;
+        }
+    }
+
+    private DependencyDescriptor createSpringDependencyDescriptor(final Injectee injectee) {
+        AnnotatedElement annotatedElement = injectee.getParent();
+
+        if (annotatedElement.getClass().isAssignableFrom(Field.class)) {
+            return new DependencyDescriptor((Field) annotatedElement, !injectee.isOptional());
+        } else if (annotatedElement.getClass().isAssignableFrom(Method.class)) {
+            return new DependencyDescriptor(
+                    new MethodParameter((Method) annotatedElement, injectee.getPosition()), !injectee.isOptional());
+        } else {
+            return new DependencyDescriptor(
+                    new MethodParameter((Constructor) annotatedElement, injectee.getPosition()), !injectee.isOptional());
+        }
+    }
+
+    @Override
+    public boolean isConstructorParameterIndicator() {
+        return false;
+    }
+
+    @Override
+    public boolean isMethodParameterIndicator() {
+        return false;
+    }
+
+    @Override
+    public Class<Autowired> getAnnotation() {
+        return Autowired.class;
+    }
+}
diff --git a/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/SpringComponentProvider.java b/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/SpringComponentProvider.java
new file mode 100644
index 0000000..19f45b3
--- /dev/null
+++ b/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/SpringComponentProvider.java
@@ -0,0 +1,176 @@
+/*
+ * 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.server.spring;
+
+import java.util.Set;
+import java.util.function.Supplier;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.servlet.ServletContext;
+
+import org.glassfish.jersey.inject.hk2.ImmediateHk2InjectionManager;
+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.server.ApplicationHandler;
+import org.glassfish.jersey.server.spi.ComponentProvider;
+
+import org.jvnet.hk2.spring.bridge.api.SpringBridge;
+import org.jvnet.hk2.spring.bridge.api.SpringIntoHK2Bridge;
+
+import org.springframework.aop.framework.Advised;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.support.WebApplicationContextUtils;
+
+/**
+ * Custom ComponentProvider class.
+ * Responsible for 1) bootstrapping Jersey 2 Spring integration and
+ * 2) making Jersey skip JAX-RS Spring component life-cycle management and leave it to us.
+ *
+ * @author Marko Asplund (marko.asplund at yahoo.com)
+ */
+public class SpringComponentProvider implements ComponentProvider {
+
+    private static final Logger LOGGER = Logger.getLogger(SpringComponentProvider.class.getName());
+    private static final String DEFAULT_CONTEXT_CONFIG_LOCATION = "applicationContext.xml";
+    private static final String PARAM_CONTEXT_CONFIG_LOCATION = "contextConfigLocation";
+    private static final String PARAM_SPRING_CONTEXT = "contextConfig";
+
+    private volatile InjectionManager injectionManager;
+    private volatile ApplicationContext ctx;
+
+    @Override
+    public void initialize(InjectionManager injectionManager) {
+        this.injectionManager = injectionManager;
+
+        if (LOGGER.isLoggable(Level.FINE)) {
+            LOGGER.fine(LocalizationMessages.CTX_LOOKUP_STARTED());
+        }
+
+        ServletContext sc = injectionManager.getInstance(ServletContext.class);
+
+        if (sc != null) {
+            // servlet container
+            ctx = WebApplicationContextUtils.getWebApplicationContext(sc);
+        } else {
+            // non-servlet container
+            ctx = createSpringContext();
+        }
+        if (ctx == null) {
+            LOGGER.severe(LocalizationMessages.CTX_LOOKUP_FAILED());
+            return;
+        }
+        LOGGER.config(LocalizationMessages.CTX_LOOKUP_SUCESSFUL());
+
+        // initialize HK2 spring-bridge
+
+        ImmediateHk2InjectionManager hk2InjectionManager = (ImmediateHk2InjectionManager) injectionManager;
+        SpringBridge.getSpringBridge().initializeSpringBridge(hk2InjectionManager.getServiceLocator());
+        SpringIntoHK2Bridge springBridge = injectionManager.getInstance(SpringIntoHK2Bridge.class);
+        springBridge.bridgeSpringBeanFactory(ctx);
+
+        injectionManager.register(Bindings.injectionResolver(new AutowiredInjectResolver(ctx)));
+        injectionManager.register(Bindings.service(ctx).to(ApplicationContext.class).named("SpringContext"));
+        LOGGER.config(LocalizationMessages.SPRING_COMPONENT_PROVIDER_INITIALIZED());
+    }
+
+    // detect JAX-RS classes that are also Spring @Components.
+    // register these with HK2 ServiceLocator to manage their lifecycle using Spring.
+    @Override
+    public boolean bind(Class<?> component, Set<Class<?>> providerContracts) {
+
+        if (ctx == null) {
+            return false;
+        }
+
+        if (AnnotationUtils.findAnnotation(component, Component.class) != null) {
+            String[] beanNames = ctx.getBeanNamesForType(component);
+            if (beanNames == null || beanNames.length != 1) {
+                LOGGER.severe(LocalizationMessages.NONE_OR_MULTIPLE_BEANS_AVAILABLE(component));
+                return false;
+            }
+            String beanName = beanNames[0];
+
+            Binding binding = Bindings.supplier(new SpringManagedBeanFactory(ctx, injectionManager, beanName))
+                    .to(component)
+                    .to(providerContracts);
+            injectionManager.register(binding);
+
+            LOGGER.config(LocalizationMessages.BEAN_REGISTERED(beanNames[0]));
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public void done() {
+    }
+
+    private ApplicationContext createSpringContext() {
+        ApplicationHandler applicationHandler = injectionManager.getInstance(ApplicationHandler.class);
+        ApplicationContext springContext = (ApplicationContext) applicationHandler.getConfiguration()
+                .getProperty(PARAM_SPRING_CONTEXT);
+        if (springContext == null) {
+            String contextConfigLocation = (String) applicationHandler.getConfiguration()
+                    .getProperty(PARAM_CONTEXT_CONFIG_LOCATION);
+            springContext = createXmlSpringConfiguration(contextConfigLocation);
+        }
+        return springContext;
+    }
+
+    private ApplicationContext createXmlSpringConfiguration(String contextConfigLocation) {
+        if (contextConfigLocation == null) {
+            contextConfigLocation = DEFAULT_CONTEXT_CONFIG_LOCATION;
+        }
+        return ctx = new ClassPathXmlApplicationContext(contextConfigLocation, "jersey-spring-applicationContext.xml");
+    }
+
+    private static class SpringManagedBeanFactory implements Supplier {
+
+        private final ApplicationContext ctx;
+        private final InjectionManager injectionManager;
+        private final String beanName;
+
+        private SpringManagedBeanFactory(ApplicationContext ctx, InjectionManager injectionManager, String beanName) {
+            this.ctx = ctx;
+            this.injectionManager = injectionManager;
+            this.beanName = beanName;
+        }
+
+        @Override
+        public Object get() {
+            Object bean = ctx.getBean(beanName);
+            if (bean instanceof Advised) {
+                try {
+                    // Unwrap the bean and inject the values inside of it
+                    Object localBean = ((Advised) bean).getTargetSource().getTarget();
+                    injectionManager.inject(localBean);
+                } catch (Exception e) {
+                    // Ignore and let the injection happen as it normally would.
+                    injectionManager.inject(bean);
+                }
+            } else {
+                injectionManager.inject(bean);
+            }
+            return bean;
+        }
+    }
+}
diff --git a/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/SpringLifecycleListener.java b/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/SpringLifecycleListener.java
new file mode 100644
index 0000000..70a57a5
--- /dev/null
+++ b/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/SpringLifecycleListener.java
@@ -0,0 +1,57 @@
+/*
+ * 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.server.spring;
+
+import javax.ws.rs.ext.Provider;
+
+import javax.inject.Inject;
+
+import org.glassfish.jersey.server.spi.Container;
+import org.glassfish.jersey.server.spi.ContainerLifecycleListener;
+
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ConfigurableApplicationContext;
+
+/**
+ * JAX-RS Provider class for processing Jersey 2 Spring integration container life-cycle events.
+ *
+ * @author Marko Asplund (marko.asplund at yahoo.com)
+ */
+@Provider
+public class SpringLifecycleListener implements ContainerLifecycleListener {
+
+    @Inject
+    private ApplicationContext ctx;
+
+    @Override
+    public void onStartup(Container container) {
+    }
+
+    @Override
+    public void onReload(Container container) {
+        if (ctx instanceof ConfigurableApplicationContext) {
+            ((ConfigurableApplicationContext) ctx).refresh();
+        }
+    }
+
+    @Override
+    public void onShutdown(Container container) {
+        if (ctx instanceof ConfigurableApplicationContext) {
+            ((ConfigurableApplicationContext) ctx).close();
+        }
+    }
+}
diff --git a/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/SpringWebApplicationInitializer.java b/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/SpringWebApplicationInitializer.java
new file mode 100644
index 0000000..80a054f
--- /dev/null
+++ b/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/SpringWebApplicationInitializer.java
@@ -0,0 +1,49 @@
+/*
+ * 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.server.spring;
+
+import java.util.logging.Logger;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+
+import org.springframework.web.WebApplicationInitializer;
+
+/**
+ * Spring WebApplicationInitializer implementation initializes Spring context by
+ * adding a Spring ContextLoaderListener to the ServletContext.
+ *
+ * @author Marko Asplund (marko.asplund at yahoo.com)
+ */
+public class SpringWebApplicationInitializer implements WebApplicationInitializer {
+
+    private static final Logger LOGGER = Logger.getLogger(SpringWebApplicationInitializer.class.getName());
+
+    private static final String PAR_NAME_CTX_CONFIG_LOCATION = "contextConfigLocation";
+
+    @Override
+    public void onStartup(ServletContext sc) throws ServletException {
+        if (sc.getInitParameter(PAR_NAME_CTX_CONFIG_LOCATION) == null) {
+            LOGGER.config(LocalizationMessages.REGISTERING_CTX_LOADER_LISTENER());
+            sc.setInitParameter(PAR_NAME_CTX_CONFIG_LOCATION, "classpath:applicationContext.xml");
+            sc.addListener("org.springframework.web.context.ContextLoaderListener");
+            sc.addListener("org.springframework.web.context.request.RequestContextListener");
+        } else {
+            LOGGER.config(LocalizationMessages.SKIPPING_CTX_LODAER_LISTENER_REGISTRATION());
+        }
+    }
+}
diff --git a/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/package-info.java b/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/package-info.java
new file mode 100644
index 0000000..0176470
--- /dev/null
+++ b/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * 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 server-side Spring 4 integration classes.
+ *
+ */
+package org.glassfish.jersey.server.spring;
diff --git a/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/scope/JaxrsRequestAttributes.java b/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/scope/JaxrsRequestAttributes.java
new file mode 100644
index 0000000..c1eb321
--- /dev/null
+++ b/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/scope/JaxrsRequestAttributes.java
@@ -0,0 +1,94 @@
+/*
+ * 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.server.spring.scope;
+
+import javax.ws.rs.container.ContainerRequestContext;
+
+import org.glassfish.jersey.server.spring.LocalizationMessages;
+import org.springframework.util.StringUtils;
+import org.springframework.web.context.request.AbstractRequestAttributes;
+
+/**
+ * JAX-RS based Spring RequestAttributes implementation.
+ *
+ * @author Marko Asplund (marko.asplund at yahoo.com)
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+class JaxrsRequestAttributes extends AbstractRequestAttributes {
+
+    private final ContainerRequestContext requestContext;
+
+    /**
+     * Create a new instance.
+     *
+     * @param requestContext JAX-RS container request context
+     */
+    public JaxrsRequestAttributes(ContainerRequestContext requestContext) {
+        this.requestContext = requestContext;
+    }
+
+    @Override
+    protected void updateAccessedSessionAttributes() {
+        // sessions not supported
+    }
+
+    @Override
+    public Object getAttribute(String name, int scope) {
+        return requestContext.getProperty(name);
+    }
+
+    @Override
+    public void setAttribute(String name, Object value, int scope) {
+        requestContext.setProperty(name, value);
+    }
+
+    @Override
+    public void removeAttribute(String name, int scope) {
+        requestContext.removeProperty(name);
+    }
+
+    @Override
+    public String[] getAttributeNames(int scope) {
+        if (!isRequestActive()) {
+            throw new IllegalStateException(LocalizationMessages.NOT_IN_REQUEST_SCOPE());
+        }
+        return StringUtils.toStringArray(requestContext.getPropertyNames());
+    }
+
+    @Override
+    public void registerDestructionCallback(String name, Runnable callback, int scope) {
+        registerRequestDestructionCallback(name, callback);
+    }
+
+    @Override
+    public Object resolveReference(String key) {
+        if (REFERENCE_REQUEST.equals(key)) {
+            return requestContext;
+        }
+        return null;
+    }
+
+    @Override
+    public String getSessionId() {
+        return null;
+    }
+
+    @Override
+    public Object getSessionMutex() {
+        return null;
+    }
+}
diff --git a/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/scope/JaxrsServletRequestAttributes.java b/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/scope/JaxrsServletRequestAttributes.java
new file mode 100644
index 0000000..6da105f
--- /dev/null
+++ b/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/scope/JaxrsServletRequestAttributes.java
@@ -0,0 +1,55 @@
+/*
+ * 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.server.spring.scope;
+
+import javax.ws.rs.container.ContainerRequestContext;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+/**
+ * JAX-RS based Spring RequestAttributes implementation for Servlet-based applications.
+ *
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+class JaxrsServletRequestAttributes extends ServletRequestAttributes {
+
+    private final ContainerRequestContext requestContext;
+
+    /**
+     * Create a new JAX-RS ServletRequestAttributes instance for the given request.
+     *
+     * @param request        current HTTP request
+     * @param requestContext JAX-RS request context
+     */
+    public JaxrsServletRequestAttributes(final HttpServletRequest request, final ContainerRequestContext requestContext) {
+        super(request);
+        this.requestContext = requestContext;
+    }
+
+    @Override
+    public Object resolveReference(String key) {
+        if (REFERENCE_REQUEST.equals(key)) {
+            return this.requestContext;
+        } else if (REFERENCE_SESSION.equals(key)) {
+            return super.getSession(true);
+        } else {
+            return null;
+        }
+    }
+}
diff --git a/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/scope/RequestContextFilter.java b/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/scope/RequestContextFilter.java
new file mode 100644
index 0000000..1cbcbd7
--- /dev/null
+++ b/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/scope/RequestContextFilter.java
@@ -0,0 +1,116 @@
+/*
+ * 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.server.spring.scope;
+
+import java.io.IOException;
+
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerRequestFilter;
+import javax.ws.rs.container.ContainerResponseContext;
+import javax.ws.rs.container.ContainerResponseFilter;
+import javax.ws.rs.container.PreMatching;
+import javax.ws.rs.ext.Provider;
+
+import javax.inject.Inject;
+import javax.servlet.http.HttpServletRequest;
+
+import org.glassfish.jersey.internal.inject.InjectionManager;
+
+import org.springframework.context.ApplicationContext;
+import org.springframework.web.context.WebApplicationContext;
+import org.springframework.web.context.request.AbstractRequestAttributes;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.RequestContextHolder;
+
+/**
+ * Spring filter to provide a bridge between JAX-RS and Spring request attributes.
+ *
+ * @author Marko Asplund (marko.asplund at yahoo.com)
+ * @author Jakub Podlesak (jakub.podlesak at oracle.com)
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+@Provider
+@PreMatching
+public final class RequestContextFilter implements ContainerRequestFilter, ContainerResponseFilter {
+
+    private static final String REQUEST_ATTRIBUTES_PROPERTY = RequestContextFilter.class.getName() + ".REQUEST_ATTRIBUTES";
+
+    private final SpringAttributeController attributeController;
+
+    private static final SpringAttributeController EMPTY_ATTRIBUTE_CONTROLLER = new SpringAttributeController() {
+        @Override
+        public void setAttributes(final ContainerRequestContext requestContext) {
+        }
+
+        @Override
+        public void resetAttributes(final ContainerRequestContext requestContext) {
+        }
+    };
+
+    private interface SpringAttributeController {
+
+        void setAttributes(final ContainerRequestContext requestContext);
+
+        void resetAttributes(final ContainerRequestContext requestContext);
+    }
+
+    /**
+     * Create a new request context filter instance.
+     *
+     * @param injectionManager injection manager.
+     */
+    @Inject
+    public RequestContextFilter(final InjectionManager injectionManager) {
+        final ApplicationContext appCtx = injectionManager.getInstance(ApplicationContext.class);
+        final boolean isWebApp = appCtx instanceof WebApplicationContext;
+
+        attributeController = appCtx != null ? new SpringAttributeController() {
+
+            @Override
+            public void setAttributes(final ContainerRequestContext requestContext) {
+                final RequestAttributes attributes;
+                if (isWebApp) {
+                    final HttpServletRequest httpRequest = injectionManager.getInstance(HttpServletRequest.class);
+                    attributes = new JaxrsServletRequestAttributes(httpRequest, requestContext);
+                } else {
+                    attributes = new JaxrsRequestAttributes(requestContext);
+                }
+                requestContext.setProperty(REQUEST_ATTRIBUTES_PROPERTY, attributes);
+                RequestContextHolder.setRequestAttributes(attributes);
+            }
+
+            @Override
+            public void resetAttributes(final ContainerRequestContext requestContext) {
+                final AbstractRequestAttributes attributes =
+                        (AbstractRequestAttributes) requestContext.getProperty(REQUEST_ATTRIBUTES_PROPERTY);
+                RequestContextHolder.resetRequestAttributes();
+                attributes.requestCompleted();
+            }
+        } : EMPTY_ATTRIBUTE_CONTROLLER;
+    }
+
+    @Override
+    public void filter(final ContainerRequestContext requestContext) throws IOException {
+        attributeController.setAttributes(requestContext);
+    }
+
+    @Override
+    public void filter(final ContainerRequestContext requestContext, final ContainerResponseContext responseContext)
+            throws IOException {
+        attributeController.resetAttributes(requestContext);
+    }
+}
diff --git a/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/scope/package-info.java b/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/scope/package-info.java
new file mode 100644
index 0000000..e15b2c5
--- /dev/null
+++ b/ext/spring4/src/main/java/org/glassfish/jersey/server/spring/scope/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * 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 server-side Spring 4 integration injection scopes related classes.
+ *
+ */
+package org.glassfish.jersey.server.spring.scope;
diff --git a/ext/spring4/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ComponentProvider b/ext/spring4/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ComponentProvider
new file mode 100644
index 0000000..5aec207
--- /dev/null
+++ b/ext/spring4/src/main/resources/META-INF/services/org.glassfish.jersey.server.spi.ComponentProvider
@@ -0,0 +1 @@
+org.glassfish.jersey.server.spring.SpringComponentProvider
diff --git a/ext/spring4/src/main/resources/jersey-spring-applicationContext.xml b/ext/spring4/src/main/resources/jersey-spring-applicationContext.xml
new file mode 100644
index 0000000..00e4dda
--- /dev/null
+++ b/ext/spring4/src/main/resources/jersey-spring-applicationContext.xml
@@ -0,0 +1,34 @@
+<?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
+
+-->
+
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans
+	http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
+
+    <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
+        <property name="scopes">
+            <map>
+                <entry key="request">
+                    <bean class="org.springframework.web.context.request.RequestScope"/>
+                </entry>
+            </map>
+        </property>
+    </bean>
+</beans>
diff --git a/ext/spring4/src/main/resources/org/glassfish/jersey/server/spring/localization.properties b/ext/spring4/src/main/resources/org/glassfish/jersey/server/spring/localization.properties
new file mode 100644
index 0000000..6fd0a06
--- /dev/null
+++ b/ext/spring4/src/main/resources/org/glassfish/jersey/server/spring/localization.properties
@@ -0,0 +1,26 @@
+#
+# 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
+#
+
+no.beans.found.for.type=No beans found. Resolution failed for type {0}.
+ctx.lookup.started=Spring context lookup started.
+ctx.lookup.failed=Spring context lookup failed, skipping spring component provider initialization.
+ctx.lookup.sucessful=Spring context lookup done.
+spring.component.provider.initialized=Spring component provider initialized.
+none.or.multiple.beans.available=None or multiple beans found in Spring context for type {0}, skipping the type.
+bean.registered=Spring managed bean, {0}, registered with HK2.
+registering.ctx.loader.listener=Registering Spring ContextLoaderListener
+skipping.ctx.lodaer.listener.registration=Presuming Spring ContextLoaderListener was manually registered. Skipping context loader registration.
+not.in.request.scope=Cannot ask for request attributes - request is not active anymore!
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/NoComponent.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/NoComponent.java
new file mode 100644
index 0000000..111361c
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/NoComponent.java
@@ -0,0 +1,21 @@
+/*
+ * 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.server.spring;
+
+public class NoComponent {
+
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/SpringTestConfiguration.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/SpringTestConfiguration.java
new file mode 100644
index 0000000..5a8123c
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/SpringTestConfiguration.java
@@ -0,0 +1,25 @@
+/*
+ * 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.server.spring;
+
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@ComponentScan(basePackages = "org.glassfish.jersey.server.spring")
+public class SpringTestConfiguration {
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/TestComponent1.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/TestComponent1.java
new file mode 100644
index 0000000..7a065c1
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/TestComponent1.java
@@ -0,0 +1,27 @@
+/*
+ * 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.server.spring;
+
+import org.springframework.stereotype.Component;
+
+@Component
+public class TestComponent1 {
+
+    public String result() {
+        return "test ok";
+    }
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/TestComponent2.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/TestComponent2.java
new file mode 100644
index 0000000..792d9b8
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/TestComponent2.java
@@ -0,0 +1,21 @@
+/*
+ * 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.server.spring;
+
+public interface TestComponent2 {
+    String result();
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/TestComponent2Impl1.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/TestComponent2Impl1.java
new file mode 100644
index 0000000..c8c61d0
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/TestComponent2Impl1.java
@@ -0,0 +1,27 @@
+/*
+ * 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.server.spring;
+
+import org.springframework.stereotype.Component;
+
+@Component
+public class TestComponent2Impl1 implements TestComponent2 {
+    @Override
+    public String result() {
+        return "test ok";
+    }
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/TestComponent2Impl2.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/TestComponent2Impl2.java
new file mode 100644
index 0000000..9900cd4
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/TestComponent2Impl2.java
@@ -0,0 +1,27 @@
+/*
+ * 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.server.spring;
+
+import org.springframework.stereotype.Component;
+
+@Component
+public class TestComponent2Impl2 implements TestComponent2 {
+    @Override
+    public String result() {
+        return "test ok";
+    }
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/aspect4j/Aspect4JTest.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/aspect4j/Aspect4JTest.java
new file mode 100644
index 0000000..6b64034
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/aspect4j/Aspect4JTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.server.spring.aspect4j;
+
+import javax.ws.rs.core.Application;
+
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+
+import static org.junit.Assert.assertEquals;
+
+public class Aspect4JTest extends JerseyTest {
+
+    private ClassPathXmlApplicationContext applicationContext;
+
+    private TestAspect testAspect;
+
+    @Before
+    public void before() {
+        testAspect.reset();
+    }
+
+    @Override
+    protected Application configure() {
+        applicationContext = new ClassPathXmlApplicationContext("jersey-spring-aspect4j-applicationContext.xml");
+        testAspect = applicationContext.getBean(TestAspect.class);
+        return new Aspect4jJerseyConfig()
+                .property("contextConfig", applicationContext);
+    }
+
+    @Test
+    public void methodCallShouldNotBeIntercepted() {
+        target("test1").request().get(String.class);
+        assertEquals(0, testAspect.getInterceptions());
+    }
+
+    @Test
+    public void methodCallShouldBeIntercepted() {
+        target("test2").request().get(String.class);
+        assertEquals(1, applicationContext.getBean(TestAspect.class).getInterceptions());
+    }
+
+    @Test
+    public void JERSEY_3126() {
+        final String result = target("JERSEY-3126").request().get(String.class);
+        assertEquals("test ok", result);
+    }
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/aspect4j/Aspect4jJerseyConfig.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/aspect4j/Aspect4jJerseyConfig.java
new file mode 100644
index 0000000..4506c7e
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/aspect4j/Aspect4jJerseyConfig.java
@@ -0,0 +1,29 @@
+/*
+ * 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.server.spring.aspect4j;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.spring.scope.RequestContextFilter;
+
+public class Aspect4jJerseyConfig extends ResourceConfig {
+
+    public Aspect4jJerseyConfig() {
+        register(RequestContextFilter.class);
+        register(NoComponentResource.class);
+        register(ComponentResource.class);
+    }
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/aspect4j/ComponentResource.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/aspect4j/ComponentResource.java
new file mode 100644
index 0000000..b9b3596
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/aspect4j/ComponentResource.java
@@ -0,0 +1,49 @@
+/*
+ * 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.server.spring.aspect4j;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.UriInfo;
+
+import org.glassfish.jersey.server.spring.TestComponent1;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Path("/")
+@Component
+public class ComponentResource {
+
+    @Autowired
+    private TestComponent1 testComponent1;
+    @Context
+    private UriInfo uriInfo;
+
+    @Path("test2")
+    @GET
+    public String test2() {
+        return testComponent1.result();
+    }
+
+    @Path("JERSEY-3126")
+    @GET
+    public String JERSEY_3126() {
+        return uriInfo == null ? "test failed" : "test ok";
+    }
+
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/aspect4j/NoComponentResource.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/aspect4j/NoComponentResource.java
new file mode 100644
index 0000000..eaa3643
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/aspect4j/NoComponentResource.java
@@ -0,0 +1,38 @@
+/*
+ * 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.server.spring.aspect4j;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+
+import org.glassfish.jersey.server.spring.TestComponent1;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Path("/")
+public class NoComponentResource {
+
+    @Autowired
+    private TestComponent1 testComponent1;
+
+    @Path("test1")
+    @GET
+    public String test1() {
+        return testComponent1.result();
+    }
+
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/aspect4j/TestAspect.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/aspect4j/TestAspect.java
new file mode 100644
index 0000000..3a80614
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/aspect4j/TestAspect.java
@@ -0,0 +1,43 @@
+/*
+ * 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.server.spring.aspect4j;
+
+import javax.inject.Singleton;
+
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+
+@Aspect
+@Singleton
+public class TestAspect {
+
+    private int interceptions = 0;
+
+    @Before("execution(* org.glassfish.jersey.server.spring.aspect4j.*.*())")
+    public void intercept(JoinPoint joinPoint) {
+        interceptions++;
+    }
+
+    public int getInterceptions() {
+        return interceptions;
+    }
+
+    public void reset() {
+        interceptions = 0;
+    }
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/fieldinjection/SpringFieldInjectionJerseyTestConfig.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/fieldinjection/SpringFieldInjectionJerseyTestConfig.java
new file mode 100644
index 0000000..df30805
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/fieldinjection/SpringFieldInjectionJerseyTestConfig.java
@@ -0,0 +1,27 @@
+/*
+ * 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.server.spring.fieldinjection;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.spring.scope.RequestContextFilter;
+
+public class SpringFieldInjectionJerseyTestConfig extends ResourceConfig {
+    public SpringFieldInjectionJerseyTestConfig() {
+        register(RequestContextFilter.class);
+        register(SpringFieldInjectionTestResource.class);
+    }
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/fieldinjection/SpringFieldInjectionTest.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/fieldinjection/SpringFieldInjectionTest.java
new file mode 100644
index 0000000..9ec3a54
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/fieldinjection/SpringFieldInjectionTest.java
@@ -0,0 +1,61 @@
+/*
+ * 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.server.spring.fieldinjection;
+
+import javax.ws.rs.core.Application;
+
+import org.glassfish.jersey.server.spring.SpringTestConfiguration;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.Test;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+
+import static org.junit.Assert.assertEquals;
+
+public class SpringFieldInjectionTest extends JerseyTest {
+
+    @Override
+    protected Application configure() {
+        ApplicationContext context = new AnnotationConfigApplicationContext(SpringTestConfiguration.class);
+        return new SpringFieldInjectionJerseyTestConfig()
+                .property("contextConfig", context);
+    }
+
+    @Test
+    public void testInjectionOfSingleBean() {
+        String result = target("test1").request().get(String.class);
+        assertEquals("test ok", result);
+    }
+
+    @Test
+    public void testInjectionOfListOfBeans() {
+        String result = target("test2").request().get(String.class);
+        assertEquals("test ok", result);
+    }
+
+    @Test
+    public void testInjectionOfSetOfBeans() {
+        String result = target("test3").request().get(String.class);
+        assertEquals("test ok", result);
+    }
+
+    @Test
+    public void JERSEY_2643() {
+        String result = target("JERSEY-2643").request().get(String.class);
+        assertEquals("test ok", result);
+    }
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/fieldinjection/SpringFieldInjectionTestResource.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/fieldinjection/SpringFieldInjectionTestResource.java
new file mode 100644
index 0000000..16e6de1
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/fieldinjection/SpringFieldInjectionTestResource.java
@@ -0,0 +1,72 @@
+/*
+ * 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.server.spring.fieldinjection;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+
+import org.glassfish.jersey.server.spring.NoComponent;
+import org.glassfish.jersey.server.spring.TestComponent1;
+import org.glassfish.jersey.server.spring.TestComponent2;
+import org.springframework.beans.factory.annotation.Autowired;
+
+@Path("/")
+public class SpringFieldInjectionTestResource {
+
+    @Autowired
+    private TestComponent1 testComponent1;
+
+    @Autowired
+    private List<TestComponent2> testComponent2List;
+
+    @Autowired
+    Set<TestComponent2> testComponent2Set;
+
+    @Autowired(required = false)
+    private NoComponent noComponent;
+
+    @Path("test1")
+    @GET
+    public String test1() {
+        return testComponent1.result();
+    }
+
+    @Path("test2")
+    @GET
+    public String test2() {
+        return (testComponent2List.size() == 2 && "test ok".equals(testComponent2List.get(0).result())
+                && "test ok".equals(testComponent2List.get(1).result())) ? "test ok" : "test failed";
+    }
+
+    @Path("test3")
+    @GET
+    public String test3() {
+        Iterator<TestComponent2> iterator = testComponent2Set.iterator();
+        return (testComponent2Set.size() == 2 && "test ok".equals(iterator.next().result())
+                && "test ok".equals(iterator.next().result())) ? "test ok" : "test failed";
+    }
+
+    @Path("JERSEY-2643")
+    @GET
+    public String JERSEY_2643() {
+        return noComponent == null ? "test ok" : "test failed";
+    }
+
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/filter/Counter.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/filter/Counter.java
new file mode 100644
index 0000000..28de237
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/filter/Counter.java
@@ -0,0 +1,34 @@
+/*
+ * 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.server.spring.filter;
+
+import org.springframework.stereotype.Component;
+
+@Component
+public class Counter {
+
+    private int count = 0;
+
+    public void inc() {
+        count++;
+    }
+
+    public int getCount() {
+        return count;
+    }
+
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/filter/FilterTest.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/filter/FilterTest.java
new file mode 100644
index 0000000..539b633
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/filter/FilterTest.java
@@ -0,0 +1,46 @@
+/*
+ * 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.server.spring.filter;
+
+import javax.ws.rs.core.Application;
+
+import org.glassfish.jersey.server.spring.SpringTestConfiguration;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.Test;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+
+import static org.junit.Assert.assertEquals;
+
+public class FilterTest extends JerseyTest {
+
+    private ApplicationContext context;
+
+    @Override
+    protected Application configure() {
+        context = new AnnotationConfigApplicationContext(SpringTestConfiguration.class);
+        return new JerseyTestConfig()
+                .property("contextConfig", context);
+    }
+
+    @Test
+    public void testInjectionOfSingleBean() {
+        target("test1").request().get(String.class);
+        assertEquals(1, context.getBean(Counter.class).getCount());
+    }
+
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/filter/JerseyTestConfig.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/filter/JerseyTestConfig.java
new file mode 100644
index 0000000..9fb32a8
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/filter/JerseyTestConfig.java
@@ -0,0 +1,29 @@
+/*
+ * 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.server.spring.filter;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.spring.scope.RequestContextFilter;
+
+public class JerseyTestConfig extends ResourceConfig {
+
+    public JerseyTestConfig() {
+        register(RequestContextFilter.class);
+        register(TestResource.class);
+        register(TestFilter.class);
+    }
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/filter/TestFilter.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/filter/TestFilter.java
new file mode 100644
index 0000000..dbc4afb
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/filter/TestFilter.java
@@ -0,0 +1,39 @@
+/*
+ * 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.server.spring.filter;
+
+import java.io.IOException;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerRequestFilter;
+
+import org.springframework.stereotype.Component;
+
+@Component
+@Singleton
+public class TestFilter implements ContainerRequestFilter {
+
+    @Inject
+    private Counter counter;
+
+    @Override
+    public void filter(final ContainerRequestContext requestContext) throws IOException {
+        counter.inc();
+    }
+
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/filter/TestResource.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/filter/TestResource.java
new file mode 100644
index 0000000..4401629
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/filter/TestResource.java
@@ -0,0 +1,30 @@
+/*
+ * 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.server.spring.filter;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+
+@Path("/")
+public class TestResource {
+
+    @Path("test1")
+    @GET
+    public String test1() {
+        return "Hello, Test!";
+    }
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/methodinjection/SpringMethodInjectionJerseyTestConfig.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/methodinjection/SpringMethodInjectionJerseyTestConfig.java
new file mode 100644
index 0000000..5a9d3b7
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/methodinjection/SpringMethodInjectionJerseyTestConfig.java
@@ -0,0 +1,27 @@
+/*
+ * 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.server.spring.methodinjection;
+
+import org.glassfish.jersey.server.spring.scope.RequestContextFilter;
+import org.glassfish.jersey.server.ResourceConfig;
+
+public class SpringMethodInjectionJerseyTestConfig extends ResourceConfig {
+    public SpringMethodInjectionJerseyTestConfig() {
+        register(RequestContextFilter.class);
+        register(SpringMethodInjectionTestResource.class);
+    }
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/methodinjection/SpringMethodInjectionTest.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/methodinjection/SpringMethodInjectionTest.java
new file mode 100644
index 0000000..5ce4fec
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/methodinjection/SpringMethodInjectionTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.server.spring.methodinjection;
+
+import static org.junit.Assert.assertEquals;
+
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.Test;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.glassfish.jersey.server.spring.SpringTestConfiguration;
+
+import javax.ws.rs.core.Application;
+
+public class SpringMethodInjectionTest extends JerseyTest {
+
+    @Override
+    protected Application configure() {
+        ApplicationContext context = new AnnotationConfigApplicationContext(SpringTestConfiguration.class);
+        return new SpringMethodInjectionJerseyTestConfig()
+                .property("contextConfig", context);
+    }
+
+    @Test
+    public void testInjectionOfSingleBean() {
+        String result = target("test1").request().get(String.class);
+        assertEquals("test ok", result);
+    }
+
+    @Test
+    public void testInjectionOfListOfBeans() {
+        String result = target("test2").request().get(String.class);
+        assertEquals("test ok", result);
+    }
+
+    @Test
+    public void testInjectionOfSetOfBeans() {
+        String result = target("test3").request().get(String.class);
+        assertEquals("test ok", result);
+    }
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/methodinjection/SpringMethodInjectionTestResource.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/methodinjection/SpringMethodInjectionTestResource.java
new file mode 100644
index 0000000..0cb3d68
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/methodinjection/SpringMethodInjectionTestResource.java
@@ -0,0 +1,83 @@
+/*
+ * 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.server.spring.methodinjection;
+
+import org.glassfish.jersey.server.spring.NoComponent;
+import org.glassfish.jersey.server.spring.TestComponent1;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import java.util.List;
+import java.util.Set;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+
+@Path("/")
+public class SpringMethodInjectionTestResource {
+
+    private TestComponent1 testComponent1;
+    private List<org.glassfish.jersey.server.spring.TestComponent2> testComponent2List;
+    private Set<org.glassfish.jersey.server.spring.TestComponent2> testComponent2Set;
+    private NoComponent noComponent;
+
+    @Autowired
+    public void setTestComponent1(TestComponent1 testComponent1) {
+        this.testComponent1 = testComponent1;
+    }
+
+    @Autowired
+    public void setTestComponent2List(List<org.glassfish.jersey.server.spring.TestComponent2> testComponent2List) {
+        this.testComponent2List = testComponent2List;
+    }
+
+    @Autowired
+    public void setTestComponent2Set(Set<org.glassfish.jersey.server.spring.TestComponent2> testComponent2Set) {
+        this.testComponent2Set = testComponent2Set;
+    }
+
+    @Autowired(required = false)
+    public void setNoComponent(NoComponent noComponent) {
+        this.noComponent = noComponent;
+    }
+
+    @Path("test1")
+    @GET
+    public String test1() {
+        return testComponent1.result();
+    }
+
+    @Path("test2")
+    @GET
+    public String test2() {
+        return (testComponent2List.size() == 2 && "test ok".equals(testComponent2List.get(0).result())
+                && "test ok".equals(testComponent2List.get(1).result())) ? "test ok" : "test failed";
+    }
+
+    @Path("test3")
+    @GET
+    public String test3() {
+        java.util.Iterator<org.glassfish.jersey.server.spring.TestComponent2> iterator = testComponent2Set.iterator();
+        return (testComponent2Set.size() == 2 && "test ok".equals(iterator.next().result())
+                && "test ok".equals(iterator.next().result())) ? "test ok" : "test failed";
+    }
+
+    @Path("JERSEY-2643")
+    @GET
+    public String JERSEY_2643() {
+        return noComponent == null ? "test ok" : "test failed";
+    }
+
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/parameterinjection/SpringParameterInjectionJerseyTestConfig.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/parameterinjection/SpringParameterInjectionJerseyTestConfig.java
new file mode 100644
index 0000000..d67ebd4
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/parameterinjection/SpringParameterInjectionJerseyTestConfig.java
@@ -0,0 +1,27 @@
+/*
+ * 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.server.spring.parameterinjection;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.spring.scope.RequestContextFilter;
+
+public class SpringParameterInjectionJerseyTestConfig extends ResourceConfig {
+    public SpringParameterInjectionJerseyTestConfig() {
+        register(RequestContextFilter.class);
+        register(SpringParameterInjectionTestResource.class);
+    }
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/parameterinjection/SpringParameterInjectionTest.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/parameterinjection/SpringParameterInjectionTest.java
new file mode 100644
index 0000000..35caa98
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/parameterinjection/SpringParameterInjectionTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.server.spring.parameterinjection;
+
+import org.glassfish.jersey.server.spring.SpringTestConfiguration;
+import org.glassfish.jersey.server.spring.fieldinjection.SpringFieldInjectionJerseyTestConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.Test;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+
+import javax.ws.rs.core.Application;
+
+import static org.junit.Assert.assertEquals;
+
+public class SpringParameterInjectionTest extends JerseyTest {
+    @Override
+    protected Application configure() {
+        ApplicationContext context = new AnnotationConfigApplicationContext(SpringTestConfiguration.class);
+        return new SpringParameterInjectionJerseyTestConfig()
+                .property("contextConfig", context);
+    }
+
+    @Test
+    public void testInjectionOfSingleBean() {
+        String result = target("test1").request().get(String.class);
+        assertEquals("test ok", result);
+    }
+
+    @Test
+    public void testInjectionOfListOfBeans() {
+        String result = target("test2").request().get(String.class);
+        assertEquals("test ok", result);
+    }
+
+    @Test
+    public void testInjectionOfSetOfBeans() {
+        String result = target("test3").request().get(String.class);
+        assertEquals("test ok", result);
+    }
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/parameterinjection/SpringParameterInjectionTestResource.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/parameterinjection/SpringParameterInjectionTestResource.java
new file mode 100644
index 0000000..aefb0d1
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/parameterinjection/SpringParameterInjectionTestResource.java
@@ -0,0 +1,65 @@
+/*
+ * 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.server.spring.parameterinjection;
+
+import org.glassfish.jersey.server.spring.TestComponent1;
+import org.glassfish.jersey.server.spring.TestComponent2;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+@Path("/")
+public class SpringParameterInjectionTestResource {
+
+    private final TestComponent1 testComponent1;
+    private final List<TestComponent2> testComponent2List;
+    private final Set<TestComponent2> testComponent2Set;
+
+    @Autowired
+    public SpringParameterInjectionTestResource(final TestComponent1 testComponent1,
+                                                final List<TestComponent2> testComponent2List,
+                                                final Set<TestComponent2> testComponent2Set) {
+        this.testComponent1 = testComponent1;
+        this.testComponent2List = testComponent2List;
+        this.testComponent2Set = testComponent2Set;
+    }
+
+    @Path("test1")
+    @GET
+    public String test1() {
+        return testComponent1.result();
+    }
+
+    @Path("test2")
+    @GET
+    public String test2() {
+        return (testComponent2List.size() == 2 && "test ok".equals(testComponent2List.get(0).result())
+                && "test ok".equals(testComponent2List.get(1).result())) ? "test ok" : "test failed";
+    }
+
+    @Path("test3")
+    @GET
+    public String test3() {
+        Iterator<TestComponent2> iterator = testComponent2Set.iterator();
+        return (testComponent2Set.size() == 2 && "test ok".equals(iterator.next().result())
+                && "test ok".equals(iterator.next().result())) ? "test ok" : "test failed";
+    }
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/DefaultTestService.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/DefaultTestService.java
new file mode 100644
index 0000000..040d053
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/DefaultTestService.java
@@ -0,0 +1,28 @@
+/*
+ * 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.server.spring.profiles;
+
+import org.springframework.stereotype.Component;
+
+@Component
+public class DefaultTestService implements TestService {
+
+    @Override
+    public String test() {
+        return "default";
+    }
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/DevTestService.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/DevTestService.java
new file mode 100644
index 0000000..dc3c995
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/DevTestService.java
@@ -0,0 +1,32 @@
+/*
+ * 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.server.spring.profiles;
+
+import org.springframework.context.annotation.Primary;
+import org.springframework.context.annotation.Profile;
+import org.springframework.stereotype.Component;
+
+@Component
+@Primary
+@Profile("dev")
+public class DevTestService implements TestService {
+
+    @Override
+    public String test() {
+        return "dev";
+    }
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/SpringDefaultProfileResourceTest.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/SpringDefaultProfileResourceTest.java
new file mode 100644
index 0000000..f390c5c
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/SpringDefaultProfileResourceTest.java
@@ -0,0 +1,49 @@
+/*
+ * 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.server.spring.profiles;
+
+import javax.ws.rs.core.Application;
+
+import org.glassfish.jersey.logging.LoggingFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.spring.scope.RequestContextFilter;
+import org.glassfish.jersey.test.JerseyTest;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+
+public class SpringDefaultProfileResourceTest extends JerseyTest {
+
+    @Override
+    protected Application configure() {
+        System.setProperty("spring.profiles.active", "");
+        ApplicationContext context = new AnnotationConfigApplicationContext("org.glassfish.jersey.server.spring.profiles");
+        return new ResourceConfig()
+                .register(RequestContextFilter.class)
+                .register(LoggingFeature.class)
+                .packages("org.glassfish.jersey.server.spring.profiles")
+                .property("contextConfig", context);
+    }
+
+    @Test
+    public void shouldUseDefaultComponent() {
+        final String result = target("spring-resource").request().get(String.class);
+        Assert.assertEquals("default", result);
+    }
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/SpringDevProfileResourceTest.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/SpringDevProfileResourceTest.java
new file mode 100644
index 0000000..6c8450c
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/SpringDevProfileResourceTest.java
@@ -0,0 +1,49 @@
+/*
+ * 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.server.spring.profiles;
+
+import javax.ws.rs.core.Application;
+
+import org.glassfish.jersey.logging.LoggingFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.spring.scope.RequestContextFilter;
+import org.glassfish.jersey.test.JerseyTest;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+
+public class SpringDevProfileResourceTest extends JerseyTest {
+
+    @Override
+    protected Application configure() {
+        System.setProperty("spring.profiles.active", "dev");
+        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
+                "org.glassfish.jersey.server.spring.profiles");
+        return new ResourceConfig()
+                .register(RequestContextFilter.class)
+                .register(LoggingFeature.class)
+                .packages("org.glassfish.jersey.server.spring.profiles")
+                .property("contextConfig", context);
+    }
+
+    @Test
+    public void shouldUseDevProfileBean() {
+        final String result = target("spring-resource").request().get(String.class);
+        Assert.assertEquals("dev", result);
+    }
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/SpringProfilesTest.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/SpringProfilesTest.java
new file mode 100644
index 0000000..8bccbe3
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/SpringProfilesTest.java
@@ -0,0 +1,40 @@
+/*
+ * 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.server.spring.profiles;
+
+import org.junit.Test;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import static org.junit.Assert.assertEquals;
+
+public class SpringProfilesTest {
+
+    @Test
+    public void shouldGetDefaultBean() {
+        System.setProperty("spring.profiles.active", "");
+        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
+                "org.glassfish.jersey.server.spring.profiles");
+        assertEquals("default", context.getBean(TestService.class).test());
+    }
+
+    @Test
+    public void shouldGetDevProfileBean() {
+        System.setProperty("spring.profiles.active", "dev");
+        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
+                "org.glassfish.jersey.server.spring.profiles");
+        assertEquals("dev", context.getBean(TestService.class).test());
+    }
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/SpringRequestResource.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/SpringRequestResource.java
new file mode 100644
index 0000000..11f8bf7
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/SpringRequestResource.java
@@ -0,0 +1,43 @@
+/*
+ * 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.server.spring.profiles;
+
+import javax.inject.Singleton;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+
+@Singleton
+@Path("spring-resource")
+@Service
+public class SpringRequestResource {
+
+    @Autowired
+    private TestService testService;
+
+    @GET
+    @Produces(MediaType.TEXT_PLAIN)
+    public String getGoodbye() {
+        return testService.test();
+    }
+
+}
diff --git a/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/TestService.java b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/TestService.java
new file mode 100644
index 0000000..045d3e2
--- /dev/null
+++ b/ext/spring4/src/test/java/org/glassfish/jersey/server/spring/profiles/TestService.java
@@ -0,0 +1,22 @@
+/*
+ * 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.server.spring.profiles;
+
+public interface TestService {
+
+    String test();
+}
diff --git a/ext/spring4/src/test/resources/jersey-spring-aspect4j-applicationContext.xml b/ext/spring4/src/test/resources/jersey-spring-aspect4j-applicationContext.xml
new file mode 100644
index 0000000..c80c155
--- /dev/null
+++ b/ext/spring4/src/test/resources/jersey-spring-aspect4j-applicationContext.xml
@@ -0,0 +1,33 @@
+<?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
+
+-->
+
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans
+    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+    http://www.springframework.org/schema/aop
+    http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
+
+    <aop:aspectj-autoproxy/>
+
+    <context:component-scan base-package="org.glassfish.jersey.server.spring"/>
+
+    <bean id="someAspect" class="org.glassfish.jersey.server.spring.aspect4j.TestAspect"/>
+</beans>
diff --git a/ext/wadl-doclet/pom.xml b/ext/wadl-doclet/pom.xml
new file mode 100644
index 0000000..c90da71
--- /dev/null
+++ b/ext/wadl-doclet/pom.xml
@@ -0,0 +1,101 @@
+<?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">
+    <parent>
+        <artifactId>project</artifactId>
+        <groupId>org.glassfish.jersey.ext</groupId>
+        <version>2.28-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>jersey-wadl-doclet</artifactId>
+    <packaging>jar</packaging>
+    <name>jersey-wadl-doclet</name>
+
+    <description>A doclet that generates a resourcedoc xml file: this file contains the javadoc documentation
+        of resource classes, so that this can be used for extending generated wadl with useful
+        documentation.
+    </description>
+
+
+    <profiles>
+        <profile>
+            <id>jdk1.7</id>
+            <activation>
+                <jdk>1.7</jdk>
+            </activation>
+            <dependencies>
+                <dependency>
+                    <groupId>com.sun</groupId>
+                    <artifactId>tools</artifactId>
+                    <version>1.7.0</version>
+                    <scope>system</scope>
+                    <systemPath>${java.home}/../lib/tools.jar</systemPath>
+                </dependency>
+            </dependencies>
+        </profile>
+        <profile>
+            <id>jdk1.8</id>
+            <activation>
+                <jdk>1.8</jdk>
+            </activation>
+            <dependencies>
+                <dependency>
+                    <groupId>com.sun</groupId>
+                    <artifactId>tools</artifactId>
+                    <version>1.8.0</version>
+                    <scope>system</scope>
+                    <systemPath>${java.home}/../lib/tools.jar</systemPath>
+                </dependency>
+            </dependencies>
+        </profile>
+        <profile>
+            <id>tools.jar</id>
+            <activation>
+                <!-- Activation should be done with <file>${java.home}/../lib/tools.jar</file But this is not working with maven as the property is not expansed. -->
+                <property>
+                    <name>java.vendor</name>
+                    <value>Sun Microsystems Inc.</value>
+                </property>
+            </activation>
+            <dependencies>
+                <dependency>
+                    <groupId>com.sun</groupId>
+                    <artifactId>tools</artifactId>
+                    <version>1.6</version>
+                    <scope>system</scope>
+                    <systemPath>${java.home}/../lib/tools.jar</systemPath>
+                </dependency>
+            </dependencies>
+        </profile>
+    </profiles>
+
+    <dependencies>
+        <dependency>
+            <groupId>xerces</groupId>
+            <artifactId>xercesImpl</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.jersey.core</groupId>
+            <artifactId>jersey-server</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/ext/wadl-doclet/src/main/java/org/glassfish/jersey/wadl/doclet/DocProcessor.java b/ext/wadl-doclet/src/main/java/org/glassfish/jersey/wadl/doclet/DocProcessor.java
new file mode 100644
index 0000000..61d832e
--- /dev/null
+++ b/ext/wadl-doclet/src/main/java/org/glassfish/jersey/wadl/doclet/DocProcessor.java
@@ -0,0 +1,87 @@
+/*
+ * 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.wadl.doclet;
+
+import org.glassfish.jersey.server.wadl.internal.generators.resourcedoc.model.ClassDocType;
+import org.glassfish.jersey.server.wadl.internal.generators.resourcedoc.model.MethodDocType;
+import org.glassfish.jersey.server.wadl.internal.generators.resourcedoc.model.ParamDocType;
+
+import com.sun.javadoc.ClassDoc;
+import com.sun.javadoc.MethodDoc;
+import com.sun.javadoc.ParamTag;
+import com.sun.javadoc.Parameter;
+
+/**
+ * A doc processor is handed over javadoc elements so that it can turn this into
+ * resource doc elements, even self defined.
+ *
+ * @author Martin Grotzke (martin.grotzke at freiheit.com)
+ */
+public interface DocProcessor {
+
+    /**
+     * Specify jaxb classes of instances that you add to the {@code resourcedoc} model.
+     * These classes are added to the list of classes when creating the jaxb context
+     * with {@code JAXBContext.newInstance( clazzes );}.
+     *
+     * @return a list of classes or {@code null}
+     */
+    Class<?>[] getRequiredJaxbContextClasses();
+
+    /**
+     * specify which of your elements you want to be handled as CDATA.
+     * The use of the '^' between the {@code namespaceURI} and the {@code localname}
+     * seems to be an implementation detail of the xerces code.
+     * When processing xml that doesn't use namespaces, simply omit the
+     * namespace prefix as shown in the third CDataElement below.
+     *
+     * @return an Array of element descriptors or {@code null}
+     */
+    String[] getCDataElements();
+
+    /**
+     * Use this method to extend the provided {@link ClassDocType} with the information from
+     * the given {@link ClassDoc}.
+     *
+     * @param classDoc     the class javadoc
+     * @param classDocType the {@link ClassDocType} to extend. This will later be processed by the
+     *                     {@link org.glassfish.jersey.server.wadl.WadlGenerator}s.
+     */
+    void processClassDoc(ClassDoc classDoc, ClassDocType classDocType);
+
+    /**
+     * Process the provided methodDoc and add your custom information to the methodDocType.<br>
+     * Use e.g. {@link MethodDocType#getAny()} to store custom elements.
+     *
+     * @param methodDoc     the {@link MethodDoc} representing the docs of your method.
+     * @param methodDocType the related {@link MethodDocType} that will later be processed by the
+     *                      {@link org.glassfish.jersey.server.wadl.WadlGenerator}s.
+     */
+    void processMethodDoc(MethodDoc methodDoc, MethodDocType methodDocType);
+
+    /**
+     * Use this method to extend the provided {@link ParamDocType} with the information from the
+     * given {@link ParamTag} and {@link Parameter}.
+     *
+     * @param paramTag     the parameter javadoc
+     * @param parameter    the parameter (that is documented or not)
+     * @param paramDocType the {@link ParamDocType} to extend. This will later be processed by the
+     *                     {@link org.glassfish.jersey.server.wadl.WadlGenerator}s.
+     */
+    void processParamTag(ParamTag paramTag, Parameter parameter, ParamDocType paramDocType);
+
+}
diff --git a/ext/wadl-doclet/src/main/java/org/glassfish/jersey/wadl/doclet/DocProcessorWrapper.java b/ext/wadl-doclet/src/main/java/org/glassfish/jersey/wadl/doclet/DocProcessorWrapper.java
new file mode 100644
index 0000000..8dec3b5
--- /dev/null
+++ b/ext/wadl-doclet/src/main/java/org/glassfish/jersey/wadl/doclet/DocProcessorWrapper.java
@@ -0,0 +1,104 @@
+/*
+ * 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.wadl.doclet;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.glassfish.jersey.server.wadl.internal.generators.resourcedoc.model.ClassDocType;
+import org.glassfish.jersey.server.wadl.internal.generators.resourcedoc.model.MethodDocType;
+import org.glassfish.jersey.server.wadl.internal.generators.resourcedoc.model.ParamDocType;
+
+import com.sun.javadoc.ClassDoc;
+import com.sun.javadoc.MethodDoc;
+import com.sun.javadoc.ParamTag;
+import com.sun.javadoc.Parameter;
+
+/**
+ * This {@link DocProcessor} wraps multiple {@code DocProcessor}s.
+ *
+ * @author Martin Grotzke (martin.grotzke at freiheit.com)
+ */
+public class DocProcessorWrapper implements DocProcessor {
+
+    private final List<DocProcessor> _docProcessors;
+
+    /**
+     * Create new {@code DocProcessorWrapper} instance.
+     */
+    public DocProcessorWrapper() {
+        _docProcessors = new ArrayList<>();
+    }
+
+    /**
+     * Add a new  {@code DocProcessor} instance to the list of wrapped instances.
+     *
+     * @param docProcessor  {@code DocProcessor} instance to wrap.
+     */
+    void add(DocProcessor docProcessor) {
+        _docProcessors.add(docProcessor);
+    }
+
+    @Override
+    public Class<?>[] getRequiredJaxbContextClasses() {
+        final List<Class<?>> result = new ArrayList<>();
+        for (DocProcessor docProcessor : _docProcessors) {
+            final Class<?>[] requiredJaxbContextClasses = docProcessor.getRequiredJaxbContextClasses();
+            if (requiredJaxbContextClasses != null && requiredJaxbContextClasses.length > 0) {
+                result.addAll(Arrays.asList(requiredJaxbContextClasses));
+            }
+        }
+        return result.toArray(new Class<?>[result.size()]);
+    }
+
+    @Override
+    public String[] getCDataElements() {
+        final List<String> result = new ArrayList<>();
+        for (DocProcessor docProcessor : _docProcessors) {
+            final String[] cdataElements = docProcessor.getCDataElements();
+            if (cdataElements != null && cdataElements.length > 0) {
+                result.addAll(Arrays.asList(cdataElements));
+            }
+        }
+        return result.toArray(new String[result.size()]);
+    }
+
+    @Override
+    public void processClassDoc(ClassDoc classDoc, ClassDocType classDocType) {
+        for (DocProcessor docProcessor : _docProcessors) {
+            docProcessor.processClassDoc(classDoc, classDocType);
+        }
+    }
+
+    @Override
+    public void processMethodDoc(MethodDoc methodDoc,
+                                 MethodDocType methodDocType) {
+        for (DocProcessor docProcessor : _docProcessors) {
+            docProcessor.processMethodDoc(methodDoc, methodDocType);
+        }
+    }
+
+    @Override
+    public void processParamTag(ParamTag paramTag, Parameter parameter,
+                                ParamDocType paramDocType) {
+        for (DocProcessor docProcessor : _docProcessors) {
+            docProcessor.processParamTag(paramTag, parameter, paramDocType);
+        }
+    }
+
+}
diff --git a/ext/wadl-doclet/src/main/java/org/glassfish/jersey/wadl/doclet/ResourceDoclet.java b/ext/wadl-doclet/src/main/java/org/glassfish/jersey/wadl/doclet/ResourceDoclet.java
new file mode 100644
index 0000000..a6b472d
--- /dev/null
+++ b/ext/wadl-doclet/src/main/java/org/glassfish/jersey/wadl/doclet/ResourceDoclet.java
@@ -0,0 +1,641 @@
+/*
+ * 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.wadl.doclet;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+import java.io.StringWriter;
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.Marshaller;
+import javax.xml.namespace.QName;
+
+import org.glassfish.jersey.server.wadl.internal.generators.resourcedoc.model.AnnotationDocType;
+import org.glassfish.jersey.server.wadl.internal.generators.resourcedoc.model.ClassDocType;
+import org.glassfish.jersey.server.wadl.internal.generators.resourcedoc.model.MethodDocType;
+import org.glassfish.jersey.server.wadl.internal.generators.resourcedoc.model.NamedValueType;
+import org.glassfish.jersey.server.wadl.internal.generators.resourcedoc.model.ParamDocType;
+import org.glassfish.jersey.server.wadl.internal.generators.resourcedoc.model.RepresentationDocType;
+import org.glassfish.jersey.server.wadl.internal.generators.resourcedoc.model.RequestDocType;
+import org.glassfish.jersey.server.wadl.internal.generators.resourcedoc.model.ResourceDocType;
+import org.glassfish.jersey.server.wadl.internal.generators.resourcedoc.model.ResponseDocType;
+import org.glassfish.jersey.server.wadl.internal.generators.resourcedoc.model.WadlParamType;
+
+import org.apache.xml.serialize.OutputFormat;
+import org.apache.xml.serialize.XMLSerializer;
+
+import com.sun.javadoc.AnnotationDesc;
+import com.sun.javadoc.AnnotationDesc.ElementValuePair;
+import com.sun.javadoc.ClassDoc;
+import com.sun.javadoc.DocErrorReporter;
+import com.sun.javadoc.MemberDoc;
+import com.sun.javadoc.MethodDoc;
+import com.sun.javadoc.ParamTag;
+import com.sun.javadoc.Parameter;
+import com.sun.javadoc.RootDoc;
+import com.sun.javadoc.SeeTag;
+import com.sun.javadoc.Tag;
+
+/**
+ * Creates a resourcedoc XML file.
+ * <p/>
+ * <p>
+ * The ResourceDoc file contains the javadoc documentation
+ * of resource classes, so that this can be used for extending generated wadl with useful
+ * documentation.
+ * </p>
+ *
+ * @author <a href="mailto:martin.grotzke@freiheit.com">Martin Grotzke</a>
+ */
+public class ResourceDoclet {
+
+    private static final Pattern PATTERN_RESPONSE_REPRESENTATION = Pattern.compile("@response\\.representation\\.([\\d]+)\\..*");
+    private static final String OPTION_OUTPUT = "-output";
+    private static final String OPTION_CLASSPATH = "-classpath";
+    private static final String OPTION_DOC_PROCESSORS = "-processors";
+
+    private static final Logger LOG = Logger.getLogger(ResourceDoclet.class.getName());
+
+    /**
+     * Start the doclet.
+     *
+     * @param root the root JavaDoc document.
+     * @return true if no exception is thrown.
+     */
+    public static boolean start(final RootDoc root) {
+        final String output = getOptionArg(root.options(), OPTION_OUTPUT);
+
+        final String classpath = getOptionArg(root.options(), OPTION_CLASSPATH);
+        // LOG.info( "Have classpath: " + classpath );
+        final String[] classpathElements = classpath.split(File.pathSeparator);
+
+        final ClassLoader cl = Thread.currentThread().getContextClassLoader();
+        final ClassLoader ncl = new Loader(classpathElements,
+                ResourceDoclet.class.getClassLoader());
+        Thread.currentThread().setContextClassLoader(ncl);
+
+        final String docProcessorOption = getOptionArg(root.options(), OPTION_DOC_PROCESSORS);
+        final String[] docProcessors = docProcessorOption != null ? docProcessorOption.split(":") : null;
+        final DocProcessorWrapper docProcessor = new DocProcessorWrapper();
+        try {
+            if (docProcessors != null && docProcessors.length > 0) {
+                final Class<?> clazz = Class.forName(docProcessors[0], true, Thread.currentThread().getContextClassLoader());
+                final Class<? extends DocProcessor> dpClazz = clazz.asSubclass(DocProcessor.class);
+                docProcessor.add(dpClazz.newInstance());
+            }
+        } catch (final Exception e) {
+            LOG.log(Level.SEVERE, "Could not load docProcessors " + docProcessorOption, e);
+        }
+
+        try {
+            final ResourceDocType result = new ResourceDocType();
+
+            final ClassDoc[] classes = root.classes();
+            for (final ClassDoc classDoc : classes) {
+                LOG.fine("Writing class " + classDoc.qualifiedTypeName());
+                final ClassDocType classDocType = new ClassDocType();
+                classDocType.setClassName(classDoc.qualifiedTypeName());
+                classDocType.setCommentText(classDoc.commentText());
+                docProcessor.processClassDoc(classDoc, classDocType);
+
+                for (final MethodDoc methodDoc : classDoc.methods()) {
+
+                    final MethodDocType methodDocType = new MethodDocType();
+                    methodDocType.setMethodName(methodDoc.name());
+                    methodDocType.setMethodSignature(methodDoc.signature());
+                    methodDocType.setCommentText(methodDoc.commentText());
+                    docProcessor.processMethodDoc(methodDoc, methodDocType);
+
+                    addParamDocs(methodDoc, methodDocType, docProcessor);
+
+                    addRequestRepresentationDoc(methodDoc, methodDocType);
+
+                    addResponseDoc(methodDoc, methodDocType);
+
+                    classDocType.getMethodDocs().add(methodDocType);
+                }
+
+                result.getDocs().add(classDocType);
+            }
+
+            try {
+                final Class<?>[] clazzes = getJAXBContextClasses(result, docProcessor);
+                final JAXBContext c = JAXBContext.newInstance(clazzes);
+                final Marshaller m = c.createMarshaller();
+                m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
+                final OutputStream out = new BufferedOutputStream(new FileOutputStream(output));
+
+                final String[] cdataElements = getCDataElements(docProcessor);
+                final XMLSerializer serializer = getXMLSerializer(out, cdataElements);
+
+                m.marshal(result, serializer);
+                out.close();
+
+                LOG.info("Wrote " + output);
+
+            } catch (final Exception e) {
+                LOG.log(Level.SEVERE, "Could not serialize ResourceDoc.", e);
+                return false;
+            }
+        } finally {
+            Thread.currentThread().setContextClassLoader(cl);
+        }
+
+        return true;
+    }
+
+    private static String[] getCDataElements(final DocProcessor docProcessor) {
+        final String[] original = new String[] {"ns1^commentText", "ns2^commentText", "^commentText"};
+        if (docProcessor == null) {
+            return original;
+        } else {
+            final String[] cdataElements = docProcessor.getCDataElements();
+            if (cdataElements == null || cdataElements.length == 0) {
+                return original;
+            } else {
+
+                final String[] result = copyOf(original, original.length + cdataElements.length);
+                for (int i = 0; i < cdataElements.length; i++) {
+                    result[original.length + i] = cdataElements[i];
+                }
+                return result;
+            }
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private static <T, U> T[] copyOf(final U[] original, final int newLength) {
+        final T[] copy = (original.getClass() == Object[].class)
+                ? (T[]) new Object[newLength]
+                : (T[]) Array.newInstance(original.getClass().getComponentType(), newLength);
+        System.arraycopy(original, 0, copy, 0,
+                Math.min(original.length, newLength));
+        return copy;
+    }
+
+    private static Class<?>[] getJAXBContextClasses(
+            final ResourceDocType result, final DocProcessor docProcessor) {
+        final Class<?>[] clazzes;
+        if (docProcessor == null) {
+            clazzes = new Class<?>[1];
+        } else {
+            final Class<?>[] requiredJaxbContextClasses = docProcessor.getRequiredJaxbContextClasses();
+            if (requiredJaxbContextClasses != null) {
+                clazzes = new Class<?>[1 + requiredJaxbContextClasses.length];
+                for (int i = 0; i < requiredJaxbContextClasses.length; i++) {
+                    clazzes[i + 1] = requiredJaxbContextClasses[i];
+                }
+            } else {
+                clazzes = new Class<?>[1];
+            }
+        }
+        clazzes[0] = result.getClass();
+        return clazzes;
+    }
+
+    private static XMLSerializer getXMLSerializer(final OutputStream os, final String[] cdataElements)
+            throws InstantiationException,
+            IllegalAccessException, ClassNotFoundException {
+        // configure an OutputFormat to handle CDATA
+        final OutputFormat of = new OutputFormat();
+
+        // specify which of your elements you want to be handled as CDATA.
+        // The use of the '^' between the namespaceURI and the localname
+        // seems to be an implementation detail of the xerces code.
+        // When processing xml that doesn't use namespaces, simply omit the
+        // namespace prefix as shown in the third CDataElement below.
+        of.setCDataElements(cdataElements);
+
+        // set any other options you'd like
+        of.setPreserveSpace(true);
+        of.setIndenting(true);
+
+        // create the serializer
+        final XMLSerializer serializer = new XMLSerializer(of);
+
+        serializer.setOutputByteStream(os);
+
+        return serializer;
+    }
+
+    private static void addResponseDoc(final MethodDoc methodDoc,
+                                       final MethodDocType methodDocType) {
+
+        final ResponseDocType responseDoc = new ResponseDocType();
+
+        final Tag returnTag = getSingleTagOrNull(methodDoc, "return");
+        if (returnTag != null) {
+            responseDoc.setReturnDoc(returnTag.text());
+        }
+
+        final Tag[] responseParamTags = methodDoc.tags("response.param");
+        for (final Tag responseParamTag : responseParamTags) {
+            // LOG.info( "Have responseparam tag: " + print( responseParamTag ) );
+            final WadlParamType wadlParam = new WadlParamType();
+            for (final Tag inlineTag : responseParamTag.inlineTags()) {
+                final String tagName = inlineTag.name();
+                final String tagText = inlineTag.text();
+                /* skip empty tags
+                 */
+                if (isEmpty(tagText)) {
+                    if (LOG.isLoggable(Level.FINE)) {
+                        LOG.fine("Skipping empty inline tag of @response.param in method "
+                                + methodDoc.qualifiedName() + ": " + tagName);
+                    }
+                    continue;
+                }
+                switch (tagName) {
+                    case "@name":
+                        wadlParam.setName(tagText);
+                        break;
+                    case "@style":
+                        wadlParam.setStyle(tagText);
+                        break;
+                    case "@type":
+                        wadlParam.setType(QName.valueOf(tagText));
+                        break;
+                    case "@doc":
+                        wadlParam.setDoc(tagText);
+                        break;
+                    default:
+                        LOG.warning("Unknown inline tag of @response.param in method "
+                                + methodDoc.qualifiedName() + ": " + tagName
+                                + " (value: " + tagText + ")");
+                        break;
+                }
+            }
+            responseDoc.getWadlParams().add(wadlParam);
+        }
+
+        final Map<String, List<Tag>> tagsByStatus = getResponseRepresentationTags(methodDoc);
+        for (final Entry<String, List<Tag>> entry : tagsByStatus.entrySet()) {
+            final RepresentationDocType representationDoc = new RepresentationDocType();
+            representationDoc.setStatus(Long.valueOf(entry.getKey()));
+            for (final Tag tag : entry.getValue()) {
+                if (tag.name().endsWith(".qname")) {
+                    representationDoc.setElement(QName.valueOf(tag.text()));
+                } else if (tag.name().endsWith(".mediaType")) {
+                    representationDoc.setMediaType(tag.text());
+                } else if (tag.name().endsWith(".example")) {
+                    representationDoc.setExample(getSerializedExample(tag));
+                } else if (tag.name().endsWith(".doc")) {
+                    representationDoc.setDoc(tag.text());
+                } else {
+                    LOG.warning("Unknown response representation tag " + tag.name());
+                }
+            }
+            responseDoc.getRepresentations().add(representationDoc);
+        }
+
+        methodDocType.setResponseDoc(responseDoc);
+    }
+
+    private static boolean isEmpty(final String value) {
+        return value == null || value.isEmpty() || value.trim().isEmpty();
+    }
+
+    private static void addRequestRepresentationDoc(final MethodDoc methodDoc,
+                                                    final MethodDocType methodDocType) {
+        final Tag requestElement = getSingleTagOrNull(methodDoc, "request.representation.qname");
+        final Tag requestExample = getSingleTagOrNull(methodDoc, "request.representation.example");
+        if (requestElement != null || requestExample != null) {
+            final RequestDocType requestDoc = new RequestDocType();
+            final RepresentationDocType representationDoc = new RepresentationDocType();
+
+            /* requestElement exists
+             */
+            if (requestElement != null) {
+                representationDoc.setElement(QName.valueOf(requestElement.text()));
+            }
+
+            /* requestExample exists
+             */
+            if (requestExample != null) {
+                final String example = getSerializedExample(requestExample);
+                if (!isEmpty(example)) {
+                    representationDoc.setExample(example);
+                } else {
+                    LOG.warning("Could not get serialized example for method " + methodDoc.qualifiedName());
+                }
+            }
+
+            requestDoc.setRepresentationDoc(representationDoc);
+            methodDocType.setRequestDoc(requestDoc);
+        }
+    }
+
+    private static Map<String, List<Tag>> getResponseRepresentationTags(final MethodDoc methodDoc) {
+        final Map<String, List<Tag>> tagsByStatus = new HashMap<>();
+        for (final Tag tag : methodDoc.tags()) {
+            final Matcher matcher = PATTERN_RESPONSE_REPRESENTATION.matcher(tag.name());
+            if (matcher.matches()) {
+                final String status = matcher.group(1);
+                List<Tag> tags = tagsByStatus.get(status);
+                if (tags == null) {
+                    tags = new ArrayList<>();
+                    tagsByStatus.put(status, tags);
+                }
+                tags.add(tag);
+            }
+        }
+        return tagsByStatus;
+    }
+
+    /**
+     * Searches an <code>@link</code> tag within the inline tags of the specified tag
+     * and serializes the referenced instance.
+     *
+     * @param tag the tag containing the inline tags to be searched.
+     * @return the {@code String} representation of the {@link com.sun.javadoc.Tag} or null if the parameter is null.
+     */
+    private static String getSerializedExample(final Tag tag) {
+        if (tag != null) {
+            final Tag[] inlineTags = tag.inlineTags();
+            if (inlineTags != null && inlineTags.length > 0) {
+                for (final Tag inlineTag : inlineTags) {
+                    if (LOG.isLoggable(Level.FINE)) {
+                        LOG.fine("Have inline tag: " + print(inlineTag));
+                    }
+                    if ("@link".equals(inlineTag.name())) {
+                        if (LOG.isLoggable(Level.FINE)) {
+                            LOG.fine("Have link: " + print(inlineTag));
+                        }
+                        final SeeTag linkTag = (SeeTag) inlineTag;
+                        return getSerializedLinkFromTag(linkTag);
+                    } else if (!isEmpty(inlineTag.text())) {
+                        return inlineTag.text();
+                    }
+                }
+            } else {
+                LOG.fine("Have example: " + print(tag));
+                return tag.text();
+            }
+        }
+        return null;
+    }
+
+    private static Tag getSingleTagOrNull(final MethodDoc methodDoc, final String tagName) {
+        final Tag[] tags = methodDoc.tags(tagName);
+        if (tags != null && tags.length == 1) {
+            return tags[0];
+        }
+        return null;
+    }
+
+    private static void addParamDocs(final MethodDoc methodDoc,
+                                     final MethodDocType methodDocType,
+                                     final DocProcessor docProcessor) {
+        final Parameter[] parameters = methodDoc.parameters();
+        final ParamTag[] paramTags = methodDoc.paramTags();
+
+        /* only use both javadoc and reflection information when the number
+         * of params are the same
+         */
+        if (parameters != null && paramTags != null
+                && parameters.length == paramTags.length) {
+
+            for (int i = 0; i < parameters.length; i++) {
+                final Parameter parameter = parameters[i];
+
+                /* TODO: this only works if the params and tags are in the same
+                 * order. If the param tags are mixed up, the comments for parameters
+                 * will be wrong.
+                 */
+                final ParamTag paramTag = paramTags[i];
+
+                final ParamDocType paramDocType = new ParamDocType();
+                paramDocType.setParamName(paramTag.parameterName());
+                paramDocType.setCommentText(paramTag.parameterComment());
+                docProcessor.processParamTag(paramTag, parameter, paramDocType);
+
+                final AnnotationDesc[] annotations = parameter.annotations();
+                if (annotations != null) {
+                    for (final AnnotationDesc annotationDesc : annotations) {
+                        final AnnotationDocType annotationDocType = new AnnotationDocType();
+                        final String typeName = annotationDesc.annotationType().qualifiedName();
+                        annotationDocType.setAnnotationTypeName(typeName);
+                        for (final ElementValuePair elementValuePair : annotationDesc.elementValues()) {
+                            final NamedValueType namedValueType = new NamedValueType();
+                            namedValueType.setName(elementValuePair.element().name());
+                            namedValueType.setValue(elementValuePair.value().value().toString());
+                            annotationDocType.getAttributeDocs().add(namedValueType);
+                        }
+                        paramDocType.getAnnotationDocs().add(annotationDocType);
+                    }
+                }
+
+                methodDocType.getParamDocs().add(paramDocType);
+
+            }
+
+        }
+    }
+
+    private static String getSerializedLinkFromTag(final SeeTag linkTag) {
+        final MemberDoc referencedMember = linkTag.referencedMember();
+
+        if (referencedMember == null) {
+            throw new NullPointerException("Referenced member of @link " + print(linkTag) + " cannot be resolved.");
+        }
+
+        if (!referencedMember.isStatic()) {
+            LOG.warning("Referenced member of @link " + print(linkTag) + " is not static."
+                    + " Right now only references to static members are supported.");
+            return null;
+        }
+
+        /* Get referenced example bean
+         */
+        final ClassDoc containingClass = referencedMember.containingClass();
+        final Object object;
+        try {
+            final Field declaredField = Class.forName(containingClass.qualifiedName(), false, Thread.currentThread()
+                    .getContextClassLoader()).getDeclaredField(referencedMember.name());
+            if (referencedMember.isFinal()) {
+                declaredField.setAccessible(true);
+            }
+            object = declaredField.get(null);
+            LOG.log(Level.FINE, "Got object " + object);
+        } catch (final Exception e) {
+            LOG.info("Have classloader: " + ResourceDoclet.class.getClassLoader().getClass());
+            LOG.info("Have thread classloader " + Thread.currentThread().getContextClassLoader().getClass());
+            LOG.info("Have system classloader " + ClassLoader.getSystemClassLoader().getClass());
+            LOG.log(Level.SEVERE, "Could not get field " + referencedMember.qualifiedName(), e);
+            return null;
+        }
+
+        /* marshal the bean to xml
+         */
+        try {
+            final JAXBContext jaxbContext = JAXBContext.newInstance(object.getClass());
+            final StringWriter stringWriter = new StringWriter();
+            final Marshaller marshaller = jaxbContext.createMarshaller();
+            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
+            marshaller.marshal(object, stringWriter);
+            final String result = stringWriter.getBuffer().toString();
+            LOG.log(Level.FINE, "Got marshalled output:\n" + result);
+            return result;
+        } catch (final Exception e) {
+            LOG.log(Level.SEVERE, "Could serialize bean to xml: " + object, e);
+            return null;
+        }
+    }
+
+    private static String print(final Tag tag) {
+        return String.valueOf(tag.getClass()) + "["
+                + "firstSentenceTags=" + toCSV(tag.firstSentenceTags())
+                + ", inlineTags=" + toCSV(tag.inlineTags())
+                + ", kind=" + tag.kind()
+                + ", name=" + tag.name()
+                + ", text=" + tag.text()
+                + "]";
+    }
+
+    static String toCSV(final Tag[] items) {
+        if (items == null) {
+            return null;
+        }
+        return toCSV(Arrays.asList(items));
+    }
+
+    static String toCSV(final Collection<Tag> items) {
+        return toCSV(items, ", ", null);
+    }
+
+    static String toCSV(final Collection<Tag> items, final String separator, final String delimiter) {
+        if (items == null) {
+            return null;
+        }
+        if (items.isEmpty()) {
+            return "";
+        }
+        final StringBuilder sb = new StringBuilder();
+        for (final Iterator<Tag> iter = items.iterator(); iter.hasNext(); ) {
+            if (delimiter != null) {
+                sb.append(delimiter);
+            }
+            final Tag item = iter.next();
+            sb.append(item.name());
+            if (delimiter != null) {
+                sb.append(delimiter);
+            }
+            if (iter.hasNext()) {
+                sb.append(separator);
+            }
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Return array length for given option: 1 + the number of arguments that
+     * the option takes.
+     *
+     * @param option option
+     * @return the number of args for the specified option
+     */
+    public static int optionLength(final String option) {
+        LOG.fine("Invoked with option " + option);
+
+        if (OPTION_OUTPUT.equals(option)
+                || OPTION_CLASSPATH.equals(option)
+                || OPTION_DOC_PROCESSORS.equals(option)) {
+            return 2;
+        }
+
+        return 0;
+    }
+
+    /**
+     * Validate options.
+     *
+     * @param options  options to be validated
+     * @param reporter {@link com.sun.javadoc.DocErrorReporter} for collecting eventual errors
+     * @return if the specified options are valid
+     */
+    public static boolean validOptions(final String[][] options, final DocErrorReporter reporter) {
+        return validOption(OPTION_OUTPUT, "<path-to-file>", options, reporter)
+                && validOption(OPTION_CLASSPATH, "<path>", options, reporter);
+    }
+
+    private static boolean validOption(final String optionName,
+                                       final String reportOptionName,
+                                       final String[][] options,
+                                       final DocErrorReporter reporter) {
+        final String option = getOptionArg(options, optionName);
+
+        final boolean foundOption = option != null && !option.trim().isEmpty();
+        if (!foundOption) {
+            reporter.printError(optionName + " " + reportOptionName + " must be specified.");
+        }
+        return foundOption;
+    }
+
+    private static String getOptionArg(final String[][] options, final String option) {
+
+        for (final String[] opt : options) {
+            if (opt[0].equals(option)) {
+                return opt[1];
+            }
+        }
+
+        return null;
+    }
+
+    static class Loader extends URLClassLoader {
+
+        public Loader(final String[] paths, final ClassLoader parent) {
+            super(getURLs(paths), parent);
+        }
+
+        Loader(final String[] paths) {
+            super(getURLs(paths));
+        }
+
+        private static URL[] getURLs(final String[] paths) {
+            final List<URL> urls = new ArrayList<>();
+            for (final String path : paths) {
+                try {
+                    urls.add(new File(path).toURI().toURL());
+                } catch (final MalformedURLException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+            return urls.toArray(new URL[urls.size()]);
+        }
+
+    }
+
+}
diff --git a/ext/wadl-doclet/src/main/java/org/glassfish/jersey/wadl/doclet/package-info.java b/ext/wadl-doclet/src/main/java/org/glassfish/jersey/wadl/doclet/package-info.java
new file mode 100644
index 0000000..324b8fc
--- /dev/null
+++ b/ext/wadl-doclet/src/main/java/org/glassfish/jersey/wadl/doclet/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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 classes used for doclet generation for WADL purposes. Classes are used during the compile time
+ * when doclet is generated. Later the result is used for WADL generation.
+ *
+ */
+package org.glassfish.jersey.wadl.doclet;