Initial Contribution

Signed-off-by: Jan Supol <jan.supol@oracle.com>
diff --git a/incubator/declarative-linking/pom.xml b/incubator/declarative-linking/pom.xml
new file mode 100644
index 0000000..592cb2a
--- /dev/null
+++ b/incubator/declarative-linking/pom.xml
@@ -0,0 +1,167 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.glassfish.jersey.incubator</groupId>
+        <artifactId>project</artifactId>
+        <version>2.28-SNAPSHOT</version>
+    </parent>
+
+    <groupId>org.glassfish.jersey.ext</groupId>
+    <artifactId>jersey-declarative-linking</artifactId>
+    <packaging>jar</packaging>
+
+    <name>jersey-declarative-linking</name>
+
+    <description>
+        Jersey support for declarative hyperlinking.
+    </description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.glassfish.jersey.core</groupId>
+            <artifactId>jersey-common</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.glassfish.jersey.core</groupId>
+            <artifactId>jersey-server</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>javax.el</groupId>
+            <artifactId>javax.el-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.web</groupId>
+            <artifactId>javax.el</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </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>org.glassfish.jersey.media</groupId>
+            <artifactId>jersey-media-json-jackson</artifactId>
+            <version>${jersey.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.skyscreamer</groupId>
+            <artifactId>jsonassert</artifactId>
+            <version>1.4.0</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <profiles>
+        <profile>
+            <id>release</id>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-javadoc-plugin</artifactId>
+                        <configuration>
+                            <links>
+                                <link>https://jsr311.java.net/nonav/releases/1.1</link>
+                                <link>https://jersey.java.net/nonav/apidocs/latest/jersey/</link>
+                            </links>
+                            <notimestamp>true</notimestamp>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+        <profile>
+            <id>cobertura</id>
+            <activation>
+                <property>
+                    <name>cobertura</name>
+                </property>
+            </activation>
+            <dependencies>
+                <dependency>
+                    <groupId>org.glassfish.jersey.core</groupId>
+                    <artifactId>jersey-common</artifactId>
+                    <version>${project.version}</version>
+                    <classifier>cobertura</classifier>
+                </dependency>
+            </dependencies>
+        </profile>
+        <profile>
+            <id>default</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <dependencies>
+                <dependency>
+                    <groupId>org.glassfish.jersey.core</groupId>
+                    <artifactId>jersey-common</artifactId>
+                    <version>${project.version}</version>
+                </dependency>
+            </dependencies>
+        </profile>
+    </profiles>
+
+    <build>
+        <resources>
+            <resource>
+                <directory>${basedir}/src/main/java</directory>
+                <includes>
+                    <include>META-INF/**/*</include>
+                </includes>
+            </resource>
+        </resources>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-source-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>attach-sources</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>jar</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/Binding.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/Binding.java
new file mode 100644
index 0000000..94b5bf2
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/Binding.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.linking;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.glassfish.jersey.Beta;
+
+/**
+ * Specifies the binding between a URI template parameter and a bean property.
+ * @see org.glassfish.jersey.linking.InjectLink#bindings()
+ *
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+@Target({})
+@Retention(RetentionPolicy.RUNTIME)
+@Beta
+public @interface Binding {
+
+    /**
+     * Specifies the name of the URI template parameter, defaults to
+     * "value" for convenience.
+     */
+    String name() default "value";
+
+    /**
+     * Specifies the value of a URI template parameter. The value is an EL
+     * expression using immediate evaluation syntax. E.g.:
+     * <pre>${instance.widgetId}</pre>
+     * In the above example the value is taken from the <code>widgetId</code>
+     * property of the implicit <code>instance</code> bean.
+     * <p>Three implicit beans are supported:</p>
+     * <dl>
+     * <dt><code>instance</code></dt><dd>The object whose class contains the
+     * {@link org.glassfish.jersey.linking.InjectLink} annotation.</dd>
+     * <dt><code>entity</code></dt><dd>The entity returned by the resource
+     * class method. This is either the resource method return value
+     * or the entity property for a resource method that returns Response.</dd>
+     * <dt><code>resource</code></dt><dd>The resource class instance that
+     * returned the object that contains the {@code InjectLink} annotation.</dd>
+     * </dd>
+     * </dl>
+     */
+    String value();
+}
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/DeclarativeLinkingFeature.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/DeclarativeLinkingFeature.java
new file mode 100644
index 0000000..a215464
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/DeclarativeLinkingFeature.java
@@ -0,0 +1,71 @@
+/*
+ * 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.linking;
+
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.Feature;
+import javax.ws.rs.core.FeatureContext;
+
+import javax.inject.Singleton;
+
+import org.glassfish.jersey.Beta;
+import org.glassfish.jersey.internal.inject.AbstractBinder;
+import org.glassfish.jersey.linking.contributing.NaiveResourceLinkContributionContext;
+import org.glassfish.jersey.linking.contributing.ResourceLinkContributionContext;
+import org.glassfish.jersey.linking.mapping.NaiveResourceMappingContext;
+import org.glassfish.jersey.linking.mapping.ResourceMappingContext;
+
+/**
+ * A feature to enable the declarative linking functionality.
+ *
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+@Beta
+public class DeclarativeLinkingFeature implements Feature {
+
+    @Override
+    public boolean configure(FeatureContext context) {
+
+        Configuration config = context.getConfiguration();
+        if (!config.isRegistered(ResponseLinkFilter.class)) {
+            context.register(new AbstractBinder() {
+
+                @Override
+                protected void configure() {
+                    bindAsContract(NaiveResourceMappingContext.class)
+                            .to(ResourceMappingContext.class).in(Singleton.class);
+                }
+            });
+            context.register(new AbstractBinder() {
+
+                @Override
+                protected void configure() {
+                    bindAsContract(NaiveResourceLinkContributionContext.class)
+                            .to(ResourceLinkContributionContext.class).in(Singleton.class);
+                }
+            });
+
+            context.register(ResponseLinkFilter.class);
+
+            // TODO: map values back?
+            // context.register(RequestLinkFilter.class);
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/ELLinkBuilder.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/ELLinkBuilder.java
new file mode 100644
index 0000000..8e777be
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/ELLinkBuilder.java
@@ -0,0 +1,149 @@
+/*
+ * 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.linking;
+
+import java.net.URI;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+
+import javax.el.ExpressionFactory;
+import javax.el.ValueExpression;
+
+import org.glassfish.jersey.linking.mapping.ResourceMappingContext;
+import org.glassfish.jersey.uri.internal.UriTemplateParser;
+
+/**
+ * A helper class to build links from EL expressions.
+ *
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+final class ELLinkBuilder {
+
+    private ELLinkBuilder() {
+    }
+
+    private static final ExpressionFactory expressionFactory =
+            ExpressionFactory.newInstance();
+
+    /**
+     * Evaluates the condition
+     *
+     * @param condition the condition expression
+     * @param entity    the entity returned from the resource method
+     * @param resource  the resource class instance that returned the entity
+     * @param instance  the instance that contains the entity, e.g. the value of a field within an entity class.
+     * @return the result of the condition
+     */
+    static boolean evaluateCondition(String condition,
+                                     Object entity,
+                                     Object resource,
+                                     Object instance) {
+
+        if (condition == null || condition.isEmpty()) {
+            return true;
+        }
+        LinkELContext context = new LinkELContext(entity, resource, instance);
+        ValueExpression expr = expressionFactory.createValueExpression(context, condition, boolean.class);
+
+        Object result = expr.getValue(context).toString();
+        return "true".equals(result);
+    }
+
+    /**
+     * Creates the URI using the link descriptor.
+     *
+     * @param link the link descriptor
+     * @param entity the entity returned from the resource method
+     * @param resource the resource class instance that returned the entity
+     * @param instance the instance that contains the entity, e.g. the value of a field within an entity class.
+     * @param uriInfo JAX-RS {@link UriInfo}
+     * @param rmc the {@link ResourceMappingContext}
+     * @return the URI
+     */
+    static URI buildURI(InjectLinkDescriptor link,
+                        Object entity,
+                        Object resource,
+                        Object instance,
+                        UriInfo uriInfo,
+                        ResourceMappingContext rmc) {
+
+        String template = link.getLinkTemplate(rmc);
+
+        // first process any embedded EL expressions
+        LinkELContext context = new LinkELContext(entity, resource, instance);
+        ValueExpression expr = expressionFactory.createValueExpression(context,
+                template, String.class);
+        template = expr.getValue(context).toString();
+
+        // now process any embedded URI template parameters
+        UriBuilder ub = applyLinkStyle(template, link.getLinkStyle(), uriInfo);
+        UriTemplateParser parser = new UriTemplateParser(template);
+        List<String> parameterNames = parser.getNames();
+        Map<String, Object> valueMap = getParameterValues(parameterNames, link, context, uriInfo);
+        return ub.buildFromMap(valueMap);
+    }
+
+    private static UriBuilder applyLinkStyle(String template, InjectLink.Style style, UriInfo uriInfo) {
+        UriBuilder ub = null;
+        switch (style) {
+            case ABSOLUTE:
+                ub = uriInfo.getBaseUriBuilder().path(template);
+                break;
+            case ABSOLUTE_PATH:
+                String basePath = uriInfo.getBaseUri().getPath();
+                ub = UriBuilder.fromPath(basePath).path(template);
+                break;
+            case RELATIVE_PATH:
+                ub = UriBuilder.fromPath(template);
+                break;
+        }
+        return ub;
+    }
+
+    private static Map<String, Object> getParameterValues(List<String> parameterNames,
+                                                          InjectLinkDescriptor linkField,
+                                                          LinkELContext context,
+                                                          UriInfo uriInfo) {
+        Map<String, Object> values = new HashMap<>();
+        for (String name : parameterNames) {
+            String elExpression = linkField.getBinding(name);
+            if (elExpression == null) {
+                String value = uriInfo.getPathParameters().getFirst(name);
+                if (value == null) {
+                    value = uriInfo.getQueryParameters().getFirst(name);
+                }
+                if (value != null) {
+                    values.put(name, value);
+                    continue;
+                }
+                elExpression = "${" + ResponseContextResolver.INSTANCE_OBJECT + "." + name + "}";
+            }
+            ValueExpression expr = expressionFactory.createValueExpression(context,
+                        elExpression, String.class);
+
+            Object value = expr.getValue(context);
+            values.put(name, value != null ? value.toString() : null);
+         }
+        return values;
+    }
+
+}
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/EntityDescriptor.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/EntityDescriptor.java
new file mode 100644
index 0000000..314c839
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/EntityDescriptor.java
@@ -0,0 +1,165 @@
+/*
+ * 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.linking;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.ws.rs.core.Link;
+
+/**
+ * Describes an entity in terms of its fields, bean properties and {@link InjectLink}
+ * annotated fields.
+ *
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+class EntityDescriptor {
+
+    // Maintains an internal static cache to optimize processing
+    private static final Map<Class<?>, EntityDescriptor> descriptors = new HashMap<>();
+
+    static synchronized EntityDescriptor getInstance(Class<?> entityClass) {
+        if (descriptors.containsKey(entityClass)) {
+            return descriptors.get(entityClass);
+        } else {
+            EntityDescriptor descriptor = new EntityDescriptor(entityClass);
+            descriptors.put(entityClass, descriptor);
+            return descriptor;
+        }
+    }
+
+    // instance
+
+    private Map<String, FieldDescriptor> nonLinkFields;
+    private Map<String, FieldDescriptor> linkFields;
+    private List<LinkHeaderDescriptor> linkHeaders;
+
+    /**
+     * Construct an new descriptor by inspecting the supplied class.
+     *
+     * @param entityClass
+     */
+    private EntityDescriptor(Class<?> entityClass) {
+        // create a list of link headers
+        this.linkHeaders = new ArrayList<>();
+        findLinkHeaders(entityClass);
+        this.linkHeaders = Collections.unmodifiableList(linkHeaders);
+
+        // create a list of field names
+        this.nonLinkFields = new HashMap<>();
+        this.linkFields = new HashMap<>();
+        findFields(entityClass);
+        this.nonLinkFields = Collections.unmodifiableMap(this.nonLinkFields);
+        this.linkFields = Collections.unmodifiableMap(this.linkFields);
+    }
+
+    Collection<FieldDescriptor> getLinkFields() {
+        return linkFields.values();
+    }
+
+    Collection<FieldDescriptor> getNonLinkFields() {
+        return nonLinkFields.values();
+    }
+
+    List<LinkHeaderDescriptor> getLinkHeaders() {
+        return linkHeaders;
+    }
+
+    /**
+     * Find and cache the fields of the supplied class and its superclasses and
+     * interfaces.
+     *
+     * @param entityClass the class
+     */
+    private void findFields(Class<?> entityClass) {
+        for (Field f : entityClass.getDeclaredFields()) {
+            InjectLink a = f.getAnnotation(InjectLink.class);
+            Class<?> t = f.getType();
+            if (a != null) {
+                if (t.equals(String.class) || t.equals(URI.class) || Link.class.isAssignableFrom(t)) {
+                    if (!linkFields.containsKey(f.getName())) {
+                        linkFields.put(f.getName(), new InjectLinkFieldDescriptor(f, a, t));
+                    }
+                } else {
+                    // TODO unsupported type
+                }
+            } else if (f.isAnnotationPresent(InjectLinks.class)) {
+
+                if (List.class.isAssignableFrom(t)
+                        || t.isArray() && Link.class.isAssignableFrom(t.getComponentType())) {
+
+                    InjectLinks a2 = f.getAnnotation(InjectLinks.class);
+                    linkFields.put(f.getName(), new InjectLinksFieldDescriptor(f, a2, t));
+                } else {
+                    throw new IllegalArgumentException("Can only inject links onto a List<Link> or Link[] object");
+                }
+
+            } else {
+                // see issue http://java.net/jira/browse/JERSEY-625
+                if ((f.getModifiers() & Modifier.STATIC) > 0
+                        || f.getName().startsWith("java.")
+                        || f.getName().startsWith("javax.")) {
+                    continue;
+                }
+                nonLinkFields.put(f.getName(), new FieldDescriptor(f));
+            }
+        }
+
+        // look for nonLinkFields in superclasses
+        Class<?> sc = entityClass.getSuperclass();
+        if (sc != null && sc != Object.class) {
+            findFields(sc);
+        }
+
+        // look for nonLinkFields in interfaces
+        for (Class<?> ic : entityClass.getInterfaces()) {
+            findFields(ic);
+        }
+    }
+
+    private void findLinkHeaders(Class<?> entityClass) {
+        InjectLink linkHeaderAnnotation = entityClass.getAnnotation(InjectLink.class);
+        if (linkHeaderAnnotation != null) {
+            linkHeaders.add(new LinkHeaderDescriptor(linkHeaderAnnotation));
+        }
+        InjectLinks linkHeadersAnnotation = entityClass.getAnnotation(InjectLinks.class);
+        if (linkHeadersAnnotation != null) {
+            for (InjectLink linkHeader : linkHeadersAnnotation.value()) {
+                linkHeaders.add(new LinkHeaderDescriptor(linkHeader));
+            }
+        }
+
+        // look in superclasses
+        Class<?> sc = entityClass.getSuperclass();
+        if (sc != null && sc != Object.class) {
+            findLinkHeaders(sc);
+        }
+
+        // look in interfaces
+        for (Class<?> ic : entityClass.getInterfaces()) {
+            findLinkHeaders(ic);
+        }
+    }
+}
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/FieldDescriptor.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/FieldDescriptor.java
new file mode 100644
index 0000000..da44b68
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/FieldDescriptor.java
@@ -0,0 +1,92 @@
+/*
+ * 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.linking;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Utility class for working with class fields
+ *
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+class FieldDescriptor {
+
+    protected Field field;
+
+    FieldDescriptor(Field f) {
+        this.field = f;
+    }
+
+    Object getFieldValue(Object instance) {
+        setAccessibleField(field);
+        Object value = null;
+        try {
+            value = field.get(instance);
+        } catch (IllegalArgumentException | IllegalAccessException ex) {
+            Logger.getLogger(FieldDescriptor.class.getName()).log(Level.FINE, null, ex);
+        }
+        return value;
+    }
+
+    public String getFieldName() {
+        return field.getName();
+    }
+
+    static void setAccessibleField(final Field f) {
+        if (Modifier.isPublic(f.getModifiers())) {
+            return;
+        }
+
+        AccessController.doPrivileged(new PrivilegedAction<Object>() {
+            public Object run() {
+                if (!f.isAccessible()) {
+                    f.setAccessible(true);
+                }
+                return f;
+            }
+        });
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final FieldDescriptor other = (FieldDescriptor) obj;
+        if (this.field != other.field && (this.field == null || !this.field.equals(other.field))) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = 7;
+        hash = 83 * hash + (this.field != null ? this.field.hashCode() : 0);
+        return hash;
+    }
+
+}
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/FieldProcessor.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/FieldProcessor.java
new file mode 100644
index 0000000..db852dd
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/FieldProcessor.java
@@ -0,0 +1,178 @@
+/*
+ * 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.linking;
+
+import java.lang.reflect.Modifier;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.core.Link;
+import javax.ws.rs.core.UriInfo;
+
+import javax.xml.bind.annotation.XmlTransient;
+
+import org.glassfish.jersey.linking.contributing.ResourceLinkContributionContext;
+import org.glassfish.jersey.linking.mapping.ResourceMappingContext;
+
+/**
+ * Utility class that can inject links into {@link org.glassfish.jersey.linking.InjectLink} annotated fields in
+ * an entity.
+ *
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+class FieldProcessor<T> {
+
+    private EntityDescriptor instanceDescriptor;
+    private static final Logger log = Logger.getLogger(FieldProcessor.class.getName());
+
+    FieldProcessor(Class<T> c) {
+        instanceDescriptor = EntityDescriptor.getInstance(c);
+    }
+
+    /**
+     * Inject any {@link org.glassfish.jersey.linking.InjectLink} annotated fields in the supplied entity and
+     * recursively process its fields.
+     *
+     * @param entity  the entity object returned by the resource method
+     * @param uriInfo the uriInfo for the request
+     * @param rmc     the ResourceMappingContext used for building URIs
+     * @param rlcc    the ResourceLinkContributionContext used to find link contributors
+     */
+    void processLinks(T entity, UriInfo uriInfo, ResourceMappingContext rmc, ResourceLinkContributionContext rlcc) {
+        Set<Object> processed = new HashSet<Object>();
+        Object resource = uriInfo.getMatchedResources().get(0);
+        processLinks(entity, resource, entity, processed, uriInfo, rmc, rlcc);
+    }
+
+    /**
+     * Inject any {@link org.glassfish.jersey.linking.InjectLink} annotated fields in the supplied instance. Called
+     * once for the entity and then recursively for each member and field.
+     *
+     * @param entity    the entity object returned by the resource method
+     * @param processed a list of already processed objects, used to break
+     *                  recursion when processing circular references.
+     * @param uriInfo   the uriInfo for the request
+     * @param rmc       the ResourceMappingContext used for building URIs
+     * @param rlcc      the ResourceLinkContributionContext used to find link contributors
+     */
+    private void processLinks(Object entity, Object resource, Object instance,
+            Set<Object> processed, UriInfo uriInfo,
+            ResourceMappingContext rmc, ResourceLinkContributionContext rlcc) {
+
+        try {
+            if (instance == null || processed.contains(instance)) {
+                return; // ignore null properties and defeat circular references
+            }
+            if (instance.getClass().getName().startsWith("java.lang")) {
+                return;
+            }
+            processed.add(instance);
+        } catch (RuntimeException e) {
+            // fix for JERSEY-1656
+            log.log(Level.INFO, LinkMessages.WARNING_LINKFILTER_PROCESSING(instance.getClass().getName()), e);
+        }
+
+        // Process any @Link annotated fields in entity
+        for (FieldDescriptor field : instanceDescriptor.getLinkFields()) {
+
+            // TODO replace with properly poly-morphic code
+            if (field instanceof InjectLinkFieldDescriptor) {
+                InjectLinkFieldDescriptor linkField = (InjectLinkFieldDescriptor) field;
+                if (ELLinkBuilder.evaluateCondition(linkField.getCondition(), entity, resource, instance)) {
+                    URI uri = ELLinkBuilder.buildURI(linkField, entity, resource, instance, uriInfo, rmc);
+                    linkField.setPropertyValue(instance, uri);
+                }
+            } else if (field instanceof InjectLinksFieldDescriptor) {
+
+                InjectLinksFieldDescriptor linksField = (InjectLinksFieldDescriptor) field;
+                List<Link> list = new ArrayList<>();
+                for (InjectLinkFieldDescriptor linkField : linksField.getLinksToInject()) {
+                    if (ELLinkBuilder.evaluateCondition(linkField.getCondition(), entity, resource, instance)) {
+                       URI uri = ELLinkBuilder.buildURI(linkField, entity, resource, instance, uriInfo, rmc);
+                       Link link = linkField.getLink(uri);
+                       list.add(link);
+                    }
+                }
+                List<ProvideLinkDescriptor> linkContributors = rlcc.getContributorsFor(instance.getClass());
+                for (ProvideLinkDescriptor linkContributor : linkContributors) {
+                    if (ELLinkBuilder.evaluateCondition(linkContributor.getCondition(),
+                            entity, linkContributor.getResource(), instance)) {
+                        URI uri = ELLinkBuilder.buildURI(linkContributor, entity, resource, instance, uriInfo, rmc);
+                        Link link = linkContributor.getLink(uri);
+                        list.add(link);
+                    }
+                }
+
+                linksField.setPropertyValue(instance, list);
+            }
+        }
+
+        // If entity is an array, collection, or map then process members
+        Class<?> instanceClass = instance.getClass();
+        if (instanceClass.isArray() && Object[].class.isAssignableFrom(instanceClass)) {
+            Object array[] = (Object[]) instance;
+            for (Object member : array) {
+                processMember(entity, resource, member, processed, uriInfo, rmc, rlcc);
+            }
+        } else if (instance instanceof Iterable) {
+            Iterable iterable = (Iterable) instance;
+            for (Object member : iterable) {
+                processMember(entity, resource, member, processed, uriInfo, rmc, rlcc);
+            }
+        } else if (instance instanceof Map) {
+            Map map = (Map) instance;
+            for (Object member : map.entrySet()) {
+                processMember(entity, resource, member, processed, uriInfo, rmc, rlcc);
+            }
+        }
+
+        // Recursively process all member fields
+        for (FieldDescriptor member : instanceDescriptor.getNonLinkFields()) {
+
+            if (fieldSuitableForIntrospection(member)) {
+                processMember(entity, resource, member.getFieldValue(instance), processed, uriInfo, rmc, rlcc);
+            }
+        }
+
+    }
+
+    private boolean fieldSuitableForIntrospection(FieldDescriptor member) {
+        return member.field == null
+                || (!member.field.isSynthetic()
+                    && !Modifier.isTransient(member.field.getModifiers())
+                    && !member.field.getType().isPrimitive()
+                    && member.field.getType() != String.class
+                    && !member.field.isAnnotationPresent(InjectLinkNoFollow.class)
+                    && !member.field.isAnnotationPresent(XmlTransient.class));
+    }
+
+    private void processMember(Object entity, Object resource, Object member, Set<Object> processed, UriInfo uriInfo,
+            ResourceMappingContext rmc, ResourceLinkContributionContext rlcc) {
+        if (member != null) {
+            FieldProcessor<?> proc = new FieldProcessor(member.getClass());
+            proc.processLinks(entity, resource, member, processed, uriInfo, rmc, rlcc);
+        }
+    }
+
+}
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/HeaderProcessor.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/HeaderProcessor.java
new file mode 100644
index 0000000..770fada
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/HeaderProcessor.java
@@ -0,0 +1,86 @@
+/*
+ * 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.linking;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.UriInfo;
+
+import org.glassfish.jersey.linking.mapping.ResourceMappingContext;
+
+/**
+ * Processes @Link and @LinkHeaders annotations on entity classes and
+ * adds appropriate HTTP Link headers.
+ *
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+class HeaderProcessor<T> {
+
+    private EntityDescriptor instanceDescriptor;
+
+    HeaderProcessor(Class<T> c) {
+        instanceDescriptor = EntityDescriptor.getInstance(c);
+    }
+
+    /**
+     * Process any {@link InjectLink} annotations on the supplied entity.
+     * @param entity the entity object returned by the resource method
+     * @param uriInfo the uriInfo for the request
+     * @param headers the map into which the headers will be added
+     */
+    void processLinkHeaders(T entity,
+                            UriInfo uriInfo,
+                            ResourceMappingContext rmc,
+                            MultivaluedMap<String, Object> headers) {
+        List<String> headerValues = getLinkHeaderValues(entity, uriInfo, rmc);
+        for (String headerValue : headerValues) {
+            headers.add("Link", headerValue);
+        }
+    }
+
+    List<String> getLinkHeaderValues(Object entity, UriInfo uriInfo, ResourceMappingContext rmc) {
+        final List<Object> matchedResources = uriInfo.getMatchedResources();
+
+        if (!matchedResources.isEmpty()) {
+            final Object resource = matchedResources.get(0);
+            final List<String> headerValues = new ArrayList<>();
+
+            for (LinkHeaderDescriptor desc : instanceDescriptor.getLinkHeaders()) {
+                if (ELLinkBuilder.evaluateCondition(desc.getCondition(), entity, resource, entity)) {
+                    String headerValue = getLinkHeaderValue(desc, entity, resource, uriInfo, rmc);
+                    headerValues.add(headerValue);
+                }
+            }
+            return headerValues;
+        }
+
+        return Collections.emptyList();
+    }
+
+    private static String getLinkHeaderValue(LinkHeaderDescriptor desc, Object entity, Object resource, UriInfo uriInfo,
+                                             ResourceMappingContext rmc) {
+        URI uri = ELLinkBuilder.buildURI(desc, entity, resource, entity, uriInfo, rmc);
+        InjectLink link = desc.getLinkHeader();
+        return InjectLink.Util.buildLinkFromUri(uri, link).toString();
+    }
+
+}
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/InjectLink.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/InjectLink.java
new file mode 100644
index 0000000..a42b3e9
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/InjectLink.java
@@ -0,0 +1,224 @@
+/*
+ * 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.linking;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.net.URI;
+
+import javax.ws.rs.core.Link;
+
+import org.glassfish.jersey.Beta;
+
+/**
+ * Specifies a link injection target in a returned representation bean. May be
+ * used on fields of type String or URI. One of {@link #value()} or
+ * {@link #resource()} must be specified.
+ *
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+@Target({ElementType.FIELD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Beta
+public @interface InjectLink {
+
+    /**
+     * Styles of URI supported
+     */
+    enum Style {
+
+        /**
+         * An absolute URI. The URI template will be prefixed with the absolute
+         * base URI of the application.
+         */
+        ABSOLUTE,
+        /**
+         * An absolute path. The URI template will be prefixed with the absolute
+         * base path of the application.
+         */
+        ABSOLUTE_PATH,
+        /**
+         * A relative path. The URI template will be converted to a relative
+         * path with no prefix.
+         */
+        RELATIVE_PATH
+
+    }
+
+    /**
+     * The style of URI to inject
+     */
+    Style style() default Style.ABSOLUTE_PATH;
+
+    /**
+     * Specifies a URI template that will be used to build the injected URI. The
+     * template may contain both URI template parameters (e.g. {id}) and EL
+     * expressions (e.g. ${instance.id}) using the same implicit beans as
+     * {@link Binding#value()}. URI template parameter values are resolved as
+     * described in {@link #resource()}. E.g. the following three alternatives
+     * are equivalent:
+     * <pre>
+     * &#64;Ref("{id}")
+     * &#64;Ref(value="{id}", bindings={
+     *   &#64;Binding(name="id" value="${instance.id}"}
+     * )
+     * &#64;Ref("${instance.id}")
+     * </pre>
+     */
+    String value() default "";
+
+    /**
+     * Specifies a resource class whose @Path URI template will be used to build
+     * the injected URI. Embedded URI template parameter values are resolved as
+     * follows:
+     * <ol>
+     * <li>If the {@link #bindings()} property contains a binding specification
+     * for the parameter then that is used</li>
+     * <li>Otherwise an implicit binding is used that extracts the value of a
+     * bean property by the same name as the URI template from the implicit
+     * {@code instance} bean (see {@link Binding}).</li>
+     * </ol>
+     * <p>
+     * E.g. assuming a resource class {@code SomeResource} with the
+     * following {@code @Path("{id}")} annotation, the following two
+     * alternatives are therefore equivalent:</p>
+     * <pre>
+     * &#64;Ref(resource=SomeResource.class)
+     * &#64;Ref(resource=SomeResource.class, bindings={
+     *   &#64;Binding(name="id" value="${instance.id}"}
+     * )
+     * </pre>
+     */
+    Class<?> resource() default Class.class;
+
+    /**
+     * Used in conjunction with {@link #resource()} to specify a subresource
+     * locator or method. The value is the name of the method. The value of the
+     * method's @Path annotation will be appended to the value of the
+     * class-level @Path annotation separated by '/' if necessary.
+     */
+    String method() default "";
+
+    /**
+     * Specifies the bindings for embedded URI template parameters.
+     *
+     * @see Binding
+     */
+    Binding[] bindings() default {};
+
+    /**
+     * Specifies a boolean EL expression whose value determines whether a Ref is
+     * set (true) or not (false). Omission of a condition will always insert a
+     * ref.
+     */
+    String condition() default "";
+
+    // Link properties
+    //
+
+    /**
+     * Specifies the relationship.
+     */
+    String rel() default "";
+
+    /**
+     * Specifies the reverse relationship.
+     */
+    String rev() default "";
+
+    /**
+     * Specifies the media type.
+     */
+    String type() default "";
+
+    /**
+     * Specifies the title.
+     */
+    String title() default "";
+
+    /**
+     * Specifies the anchor
+     */
+    String anchor() default "";
+
+    /**
+     * Specifies the media
+     */
+    String media() default "";
+
+    /**
+     * Specifies the lang of the referenced resource
+     */
+    String hreflang() default "";
+
+    /**
+     * Specifies extension parameters as name-value pairs.
+     */
+    Extension[] extensions() default {};
+
+    @Target({ElementType.TYPE, ElementType.FIELD})
+    @Retention(RetentionPolicy.RUNTIME)
+    @interface Extension {
+
+        /**
+         * Specifies the name of the extension parameter
+         */
+        String name();
+
+        /**
+         * Specifies the value of the extension parameter
+         */
+        String value();
+    }
+
+    class Util {
+
+        public static Link buildLinkFromUri(URI uri, InjectLink link) {
+
+            javax.ws.rs.core.Link.Builder builder = javax.ws.rs.core.Link.fromUri(uri);
+            if (!link.rel().isEmpty()) {
+                builder = builder.rel(link.rel());
+            }
+            if (!link.rev().isEmpty()) {
+                builder = builder.param("rev", link.rev());
+            }
+            if (!link.type().isEmpty()) {
+                builder = builder.type(link.type());
+            }
+            if (!link.title().isEmpty()) {
+                builder = builder.param("title", link.title());
+            }
+            if (!link.anchor().isEmpty()) {
+                builder = builder.param("anchor", link.anchor());
+            }
+            if (!link.media().isEmpty()) {
+                builder = builder.param("media", link.media());
+            }
+            if (!link.hreflang().isEmpty()) {
+                builder = builder.param("hreflang", link.hreflang());
+            }
+            for (InjectLink.Extension ext : link.extensions()) {
+                builder = builder.param(ext.name(), ext.value());
+            }
+            return builder.build();
+        }
+    }
+
+}
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/InjectLinkDescriptor.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/InjectLinkDescriptor.java
new file mode 100644
index 0000000..4ee06ff
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/InjectLinkDescriptor.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.linking;
+
+import org.glassfish.jersey.linking.mapping.ResourceMappingContext;
+
+/**
+ * Utility for working with @Ref annotations
+ *
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+interface InjectLinkDescriptor {
+
+    /**
+     * Get the style
+     * @return the style
+     */
+    InjectLink.Style getLinkStyle();
+
+    /**
+     * Get the link template, either directly from the value() or from the
+     * @Path of the class referenced in resource()
+     * @return the link template
+     */
+    String getLinkTemplate(ResourceMappingContext rmc);
+
+    /**
+     * Get the binding as an EL expression for a particular URI template parameter
+     * @param name
+     * @return the EL binding
+     */
+    String getBinding(String name);
+
+    /**
+     * Get the condition.
+     * @return the condition
+     */
+    String getCondition();
+}
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/InjectLinkFieldDescriptor.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/InjectLinkFieldDescriptor.java
new file mode 100644
index 0000000..d6e36f6
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/InjectLinkFieldDescriptor.java
@@ -0,0 +1,241 @@
+/*
+ * 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.linking;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.ws.rs.BeanParam;
+import javax.ws.rs.HttpMethod;
+import javax.ws.rs.Path;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Link;
+
+import org.glassfish.jersey.linking.mapping.ResourceMappingContext;
+import org.glassfish.jersey.server.model.AnnotatedMethod;
+import org.glassfish.jersey.server.model.MethodList;
+
+/**
+ * Utility class for working with {@link InjectLink} annotated fields.
+ *
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+class InjectLinkFieldDescriptor extends FieldDescriptor implements InjectLinkDescriptor {
+
+    private InjectLink link;
+    private Class<?> type;
+    private Map<String, String> bindings;
+
+    /**
+     * C'tor
+     *
+     * @param f the field to inject
+     * @param l the InjectLink annotation
+     * @param t the class that contains field f
+     */
+    InjectLinkFieldDescriptor(Field f, InjectLink l, Class<?> t) {
+        super(f);
+        link = l;
+        type = t;
+        bindings = new HashMap<>();
+        for (Binding binding : l.bindings()) {
+            bindings.put(binding.name(), binding.value());
+        }
+    }
+
+    /**
+     * Injects the uri into the field.
+     *
+     * @param instance the target for the injection
+     * @param uri the value to inject
+     */
+    void setPropertyValue(Object instance, URI uri) {
+        setAccessibleField(field);
+        try {
+
+            Object value;
+            if (Objects.equals(URI.class, type)) {
+                value = uri;
+            } else if (Link.class.isAssignableFrom(type)) {
+
+                // Make a link with the correct bindings
+                value = getLink(uri);
+            } else if (Objects.equals(String.class, type)) {
+                value = uri.toString();
+            } else {
+                throw new IllegalArgumentException("Field type " + type + " not one of supported String,URI and Link");
+            }
+
+            field.set(instance, value);
+        } catch (IllegalArgumentException | IllegalAccessException ex) {
+            Logger.getLogger(InjectLinkFieldDescriptor.class.getName()).log(Level.SEVERE, null, ex);
+        }
+    }
+
+    /**
+     * Simple delegate to {@link InjectLink#style()}
+     * @return {@link InjectLink#style()}
+     */
+    @Override
+    public InjectLink.Style getLinkStyle() {
+        return link.style();
+    }
+
+    /**
+     * Returns the template based on the {@link ResourceMappingContext}
+     *
+     * @param rmc the context
+     * @return the link template
+     */
+    @Override
+    public String getLinkTemplate(ResourceMappingContext rmc) {
+        return getLinkTemplate(rmc, link);
+    }
+
+
+    /**
+     * Returns the template based on the {@link ResourceMappingContext}
+     *
+     * @param rmc the context
+     * @param link the link
+     * @return the link template
+     */
+    static String getLinkTemplate(ResourceMappingContext rmc, InjectLink link) {
+        String template = null;
+        if (Objects.equals(link.resource(), Class.class)) {
+            template = link.value();
+        } else {
+            ResourceMappingContext.Mapping map = rmc.getMapping(link.resource());
+            if (map != null) {
+                template = map.getTemplate().getTemplate();
+            } else {
+                // extract template from specified class' @Path annotation
+                Path path = link.resource().getAnnotation(Path.class);
+                template = path == null ? "" : path.value();
+            }
+
+            // extract template from specified class' @Path annotation
+            if (!link.method().isEmpty()) {
+                // append value of method's @Path annotation
+                MethodList methods = new MethodList(link.resource());
+                methods = methods.withMetaAnnotation(HttpMethod.class);
+                for (AnnotatedMethod method : methods) {
+                    if (!Objects.equals(method.getMethod().getName(), link.method())) {
+                        continue;
+                    }
+                    StringBuilder builder = new StringBuilder();
+                    builder.append(template);
+
+                    Path methodPath = method.getAnnotation(Path.class);
+                    if (methodPath != null) {
+                        String methodTemplate = methodPath.value();
+
+                        if (!(template.endsWith("/") || methodTemplate.startsWith("/"))) {
+                            builder.append("/");
+                        }
+                        builder.append(methodTemplate);
+                    }
+
+                    CharSequence querySubString = extractQueryParams(method);
+
+                    if (querySubString.length() > 0) {
+                        builder.append("{?");
+                        builder.append(querySubString);
+                        builder.append("}");
+                    }
+
+                    template = builder.toString();
+                    break;
+                }
+            }
+        }
+
+        return template;
+    }
+
+    static StringBuilder extractQueryParams(AnnotatedMethod method) throws SecurityException {
+        // append query parameters
+        StringBuilder querySubString = new StringBuilder();
+        int parameterIndex = 0;
+        for (Annotation[] paramAnns : method.getParameterAnnotations()) {
+            for (Annotation ann : paramAnns) {
+                if (Objects.equals(ann.annotationType(), QueryParam.class)) {
+                    querySubString.append(((QueryParam) ann).value());
+                    querySubString.append(',');
+                }
+                if (Objects.equals(ann.annotationType(), BeanParam.class)) {
+                    Class<?> beanParamType = method.getParameterTypes()[parameterIndex];
+                    Field[] fields = beanParamType.getFields();
+                    for (Field field : fields) {
+                        QueryParam queryParam = field.getAnnotation(QueryParam.class);
+                        if (queryParam != null) {
+                            querySubString.append(queryParam.value());
+                            querySubString.append(',');
+                        }
+                    }
+                    Method[] beanMethods = beanParamType.getMethods();
+                    for (Method beanMethod : beanMethods) {
+                        QueryParam queryParam = beanMethod.getAnnotation(QueryParam.class);
+                        if (queryParam != null) {
+                            querySubString.append(queryParam.value());
+                            querySubString.append(',');
+                        }
+                    }
+                }
+            }
+            parameterIndex++;
+        }
+
+        return querySubString;
+    }
+
+    /**
+     * Creates a link from uri combined with {@link InjectLink}.
+     *
+     * @param uri uri for the link
+     * @return Link instance
+     */
+    Link getLink(URI uri) {
+        return InjectLink.Util.buildLinkFromUri(uri, link);
+    }
+
+    /**
+     * Gets the binding by name
+     *
+     * @param name name of the binding
+     * @return the binding
+     */
+    public String getBinding(String name) {
+        return bindings.get(name);
+    }
+
+
+    /**
+     * Returns the condition of {@link InjectLink}.
+     * @return {@link InjectLink#condition()}
+     */
+    public String getCondition() {
+        return link.condition();
+    }
+}
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/InjectLinkNoFollow.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/InjectLinkNoFollow.java
new file mode 100644
index 0000000..bb83c56
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/InjectLinkNoFollow.java
@@ -0,0 +1,38 @@
+/*
+ * 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.linking;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.glassfish.jersey.Beta;
+
+/**
+ * Specifies on a field that should be ignored by Link recursive introspection.
+ *
+ * In some scenarios like with framework contexts or for more control,
+ * it is not wanted to try to find nested links into entities or collections.
+ *
+ * @author Aurelien Thieriot
+ */
+@Target({ElementType.FIELD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Beta
+public @interface InjectLinkNoFollow {
+
+}
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/InjectLinks.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/InjectLinks.java
new file mode 100644
index 0000000..6257537
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/InjectLinks.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.linking;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.glassfish.jersey.Beta;
+
+/**
+ * Used to request the addition of a set of links, can be used for both
+ * link headers on a Class or injection into a List<Link> or Link[] property.
+ *
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+@Target({ElementType.TYPE, ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Beta
+public @interface InjectLinks {
+
+    /**
+     * Container for a set of {@link org.glassfish.jersey.linking.InjectLink} annotations
+     * @return array of {@code InjectLink} elements
+     */
+    InjectLink[] value() default {};
+
+}
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/InjectLinksFieldDescriptor.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/InjectLinksFieldDescriptor.java
new file mode 100644
index 0000000..7f4925c
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/InjectLinksFieldDescriptor.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.linking;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.core.Link;
+
+/**
+ * Utility class for working with {@link InjectLinks} annotated fields.
+ *
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+class InjectLinksFieldDescriptor extends FieldDescriptor {
+
+    private final InjectLinks link;
+    private final Class<?> type;
+
+    /**
+     * C'tor
+     *
+     * @param f the field to inject
+     * @param l the InjectLinks annotation
+     * @param t the class that contains field f
+     */
+    InjectLinksFieldDescriptor(Field f, InjectLinks l, Class<?> t) {
+        super(f);
+        link = l;
+        type = t;
+    }
+
+    /**
+     * Injects the Link list into the instance.
+     *
+     * If the field is {@code null} then it is replaced with the list.
+     * If the field already contains links, then the content is merged
+     * with this list into a new list and injected.
+     *
+     * @param instance the instance that contains the field f
+     * @param list the list of links to inject
+     */
+    public void setPropertyValue(Object instance, List<Link> list) {
+        setAccessibleField(field);
+        try {
+            List<Link> merged = mergeWithExistingField(instance, list);
+
+            Object value;
+            if (Objects.equals(List.class, type)) {
+                value = merged;
+            } else if (type.isArray()) {
+                value = merged.toArray((Object[]) Array.newInstance(type.getComponentType(), merged.size()));
+            } else {
+                throw new IllegalArgumentException("Field type " + type + " not one of supported List<Link> or Link[]");
+            }
+
+            field.set(instance, value);
+
+
+        } catch (IllegalArgumentException | IllegalAccessException ex) {
+            Logger.getLogger(InjectLinksFieldDescriptor.class.getName()).log(Level.SEVERE, null, ex);
+        }
+    }
+
+    private List<Link> mergeWithExistingField(Object instance, List<Link> list) throws IllegalAccessException {
+        Object existing = field.get(instance);
+        if (existing != null) {
+            if (Collection.class.isAssignableFrom(existing.getClass()) && !((Collection) existing).isEmpty()) {
+                List<Link> merged  = new ArrayList<>(list);
+                merged.addAll((Collection<Link>) existing);
+                return merged;
+            } else if (existing.getClass().isArray() && existing.getClass().isAssignableFrom(Link[].class)) {
+                List<Link> merged = new ArrayList<>(list);
+                merged.addAll(Arrays.asList((Link[]) existing));
+                return merged;
+            }
+        }
+        return list;
+    }
+
+    /**
+     * Creates {@link InjectLinkFieldDescriptor} for each link to inject.
+     */
+    InjectLinkFieldDescriptor[] getLinksToInject() {
+        final InjectLink[] listOfLinks = link.value();
+        InjectLinkFieldDescriptor[] fields = new InjectLinkFieldDescriptor[listOfLinks.length];
+        for (int i = 0; i < fields.length; i++) {
+            fields[i] = new InjectLinkFieldDescriptor(field, listOfLinks[i], Link.class);
+        }
+        return fields;
+    }
+}
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/LinkELContext.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/LinkELContext.java
new file mode 100644
index 0000000..4af9916
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/LinkELContext.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.linking;
+
+import javax.el.BeanELResolver;
+import javax.el.CompositeELResolver;
+import javax.el.ELContext;
+import javax.el.ELResolver;
+import javax.el.FunctionMapper;
+import javax.el.VariableMapper;
+
+/**
+ * An ELContext that encapsulates the response information for use by the
+ * expression evaluator.
+ *
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+class LinkELContext extends ELContext {
+
+    private Object entity;
+    private Object resource;
+    private Object instance;
+
+    /**
+     * Convenience constructor for the common case where a context where
+     * the entity and instance are the same. Equivalent to
+     * {@link #LinkELContext(Object, Object, Object)}.
+     *
+     * @param entity
+     * @param resource
+     */
+    LinkELContext(Object entity, Object resource) {
+        this.entity = entity;
+        this.resource = resource;
+        this.instance = entity;
+    }
+
+    /**
+     * Construct a new context
+     * @param entity the entity returned from the resource method
+     * @param resource the resource class instance that returned the entity
+     * @param instance the instance that contains the entity, e.g. the value of
+     * a field within an entity class.
+     */
+    LinkELContext(Object entity, Object resource, Object instance) {
+        this.entity = entity;
+        this.resource = resource;
+        this.instance = instance;
+    }
+
+    @Override
+    public ELResolver getELResolver() {
+        CompositeELResolver resolver = new CompositeELResolver();
+        resolver.add(new ResponseContextResolver(entity, resource, instance));
+        resolver.add(new BeanELResolver(true));
+        return resolver;
+    }
+
+    @Override
+    public FunctionMapper getFunctionMapper() {
+        return null;
+    }
+
+    @Override
+    public VariableMapper getVariableMapper() {
+        return null;
+    }
+
+}
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/LinkHeaderDescriptor.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/LinkHeaderDescriptor.java
new file mode 100644
index 0000000..7e621ad
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/LinkHeaderDescriptor.java
@@ -0,0 +1,64 @@
+/*
+ * 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.linking;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.glassfish.jersey.linking.InjectLink.Style;
+import org.glassfish.jersey.linking.mapping.ResourceMappingContext;
+
+/**
+ * Utility class for working with {@link org.glassfish.jersey.linking.InjectLink} annotations.
+ *
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+class LinkHeaderDescriptor implements InjectLinkDescriptor {
+
+    private InjectLink linkHeader;
+    private Map<String, String> bindings;
+
+    LinkHeaderDescriptor(InjectLink linkHeader) {
+        this.linkHeader = linkHeader;
+        bindings = new HashMap<>();
+        for (Binding binding : linkHeader.bindings()) {
+            bindings.put(binding.name(), binding.value());
+        }
+    }
+
+    InjectLink getLinkHeader() {
+        return linkHeader;
+    }
+
+    public String getLinkTemplate(ResourceMappingContext rmc) {
+        return InjectLinkFieldDescriptor.getLinkTemplate(rmc, linkHeader);
+    }
+
+    public Style getLinkStyle() {
+        return linkHeader.style();
+    }
+
+    public String getBinding(String name) {
+        return bindings.get(name);
+    }
+
+    public String getCondition() {
+        return linkHeader.condition();
+    }
+
+}
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/LinkMessages.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/LinkMessages.java
new file mode 100644
index 0000000..78fae95
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/LinkMessages.java
@@ -0,0 +1,46 @@
+/*
+ * 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.linking;
+
+import org.glassfish.jersey.internal.l10n.Localizable;
+import org.glassfish.jersey.internal.l10n.LocalizableMessageFactory;
+import org.glassfish.jersey.internal.l10n.Localizer;
+
+/**
+ * Message for declarative linking
+ *
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+class LinkMessages {
+
+    private static final LocalizableMessageFactory messageFactory = new LocalizableMessageFactory(
+            "org.glassfish.jersey.media.linking.internal");
+    private static final Localizer localizer = new Localizer();
+
+    private static Localizable localizableWARNING_LINKFILTER_PROCESSING(Object arg0) {
+        return messageFactory.getMessage("warning.linkfilter.processing", arg0);
+    }
+
+    /**
+     * LinkFilter cannot process class {0}, exception occurred during processing. Class will be ignored in the LinkFilter.
+     *
+     */
+    static String WARNING_LINKFILTER_PROCESSING(Object arg0) {
+        return localizer.localize(localizableWARNING_LINKFILTER_PROCESSING(arg0));
+    }
+}
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/ProvideLink.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/ProvideLink.java
new file mode 100644
index 0000000..3c00ba0
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/ProvideLink.java
@@ -0,0 +1,188 @@
+/*
+ * 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.linking;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.net.URI;
+
+import javax.ws.rs.core.Link;
+
+import org.glassfish.jersey.Beta;
+
+/**
+ * Use this on resource methods to contribute links to a representation.
+ *
+ * It is the inverse of {@link InjectLink} instead of annotating the target you annotate the source of the links.
+ * The added benefit is that since you annotate the method you don't need to specify the path to it.
+ *
+ * <p>
+ * <pre>
+ * &#64;ProvideLink(value = Order.class, rel = "self", bindings = @Binding(name = "orderId", value = "${instance.id}"))
+ * &#64;ProvideLink(value = PaymentConfirmation.class, rel = "order",
+ *                  bindings = @Binding(name = "orderId", value = "${instance.orderId}"))
+ * public Response get(@PathParam("orderId") String orderId) { ...
+ * </pre>
+ * </p>
+ *
+ * It can also be used as a meta annotation, see the Javadoc of {@link InheritFromAnnotation} for details.
+ *
+ * @author Leonard Brünings
+ */
+@Target({ ElementType.METHOD, ElementType.TYPE})
+@Repeatable(ProvideLinks.class)
+@Retention(RetentionPolicy.RUNTIME)
+@Beta
+public @interface ProvideLink {
+
+    /**
+     * The style of URI to inject
+     */
+    InjectLink.Style style() default InjectLink.Style.ABSOLUTE_PATH;
+
+    /**
+     * Provide links for representation classes listed here.
+     *
+     * May use {@link InheritFromAnnotation} for Meta-Annotations
+     */
+    Class<?>[] value();
+
+    /**
+     * Specifies the bindings for embedded URI template parameters.
+     *
+     * @see Binding
+     */
+    Binding[] bindings() default {};
+
+    /**
+     * Specifies a boolean EL expression whose value determines whether a Ref is
+     * set (true) or not (false). Omission of a condition will always insert a
+     * ref.
+     */
+    String condition() default "";
+
+    //
+    // Link properties
+    //
+
+    /**
+     * Specifies the relationship.
+     */
+    String rel() default "";
+
+    /**
+     * Specifies the reverse relationship.
+     */
+    String rev() default "";
+
+    /**
+     * Specifies the media type.
+     */
+    String type() default "";
+
+    /**
+     * Specifies the title.
+     */
+    String title() default "";
+
+    /**
+     * Specifies the anchor
+     */
+    String anchor() default "";
+
+    /**
+     * Specifies the media
+     */
+    String media() default "";
+
+    /**
+     * Specifies the lang of the referenced resource
+     */
+    String hreflang() default "";
+
+    /**
+     * Specifies extension parameters as name-value pairs.
+     */
+    InjectLink.Extension[] extensions() default {};
+
+
+    class Util {
+
+        static Link buildLinkFromUri(URI uri, ProvideLink link) {
+
+            javax.ws.rs.core.Link.Builder builder = javax.ws.rs.core.Link.fromUri(uri);
+            if (!link.rel().isEmpty()) {
+                builder = builder.rel(link.rel());
+            }
+            if (!link.rev().isEmpty()) {
+                builder = builder.param("rev", link.rev());
+            }
+            if (!link.type().isEmpty()) {
+                builder = builder.type(link.type());
+            }
+            if (!link.title().isEmpty()) {
+                builder = builder.param("title", link.title());
+            }
+            if (!link.anchor().isEmpty()) {
+                builder = builder.param("anchor", link.anchor());
+            }
+            if (!link.media().isEmpty()) {
+                builder = builder.param("media", link.media());
+            }
+            if (!link.hreflang().isEmpty()) {
+                builder = builder.param("hreflang", link.hreflang());
+            }
+            for (InjectLink.Extension ext : link.extensions()) {
+                builder = builder.param(ext.name(), ext.value());
+            }
+            return builder.build();
+        }
+    }
+
+    /**
+     * Special interface to indicate that the target should be inherited from the annotated annotation.
+     * <p>
+     * <pre>
+     * &#64;ProvideLinks({
+     *   &#64;ProvideLink(value = ProvideLink.InheritFromAnnotation.class, rel = "next", bindings = {
+     *       &#64;Binding(name = "page", value = "${instance.number + 1}"),
+     *       &#64;Binding(name =&#64; "size", value = "${instance.size}"),
+     *     },
+     *     condition = "${instance.nextPageAvailable}"),
+     *   &#64;ProvideLink(value = ProvideLink.InheritFromAnnotation.class, rel = "prev", bindings = {
+     *       &#64;Binding(name = "page", value = "${instance.number - 1}"),
+     *       &#64;Binding(name = "size", value = "${instance.size}"),
+     *     },
+     *     condition = "${instance.previousPageAvailable}")
+     * })
+     * &#64;Target({ElementType.METHOD})
+     * &#64;Retention(RetentionPolicy.RUNTIME)
+     * &#64;Documented
+     * public &#64;interface PageLinks {
+     * Class<?> value();
+     * }
+     * </pre>
+     * </p>
+     * <p>
+     * In this case the value of each {@link ProvideLink} will be the same as {@code PageLinks} value.
+     * </p>
+     */
+    interface InheritFromAnnotation{}
+}
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/ProvideLinkDescriptor.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/ProvideLinkDescriptor.java
new file mode 100644
index 0000000..a4b6356
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/ProvideLinkDescriptor.java
@@ -0,0 +1,164 @@
+/*
+ * 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.linking;
+
+import java.lang.annotation.Annotation;
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.ws.rs.Path;
+import javax.ws.rs.core.Link;
+
+import org.glassfish.jersey.linking.mapping.ResourceMappingContext;
+import org.glassfish.jersey.server.model.AnnotatedMethod;
+import org.glassfish.jersey.server.model.ResourceMethod;
+
+/**
+ * Utility to work with {@link ProvideLink} annotations.
+ *
+ * @author Leonard Brünings
+ */
+public class ProvideLinkDescriptor implements InjectLinkDescriptor {
+    private final ProvideLink provideLink;
+    private final ResourceMethod resource;
+
+    private final Annotation parentAnnotation;
+
+    private final Map<String, String> bindings;
+
+    /**
+     * c'tor
+     *
+     * @param resource the annotated resource method
+     * @param provideLink the annotaion
+     * @param parentAnnotation the parent annotation if present or {@code null}
+     */
+    public ProvideLinkDescriptor(ResourceMethod resource, ProvideLink provideLink, Annotation parentAnnotation) {
+        this.provideLink = provideLink;
+        this.resource = resource;
+        this.parentAnnotation = parentAnnotation;
+        bindings = new HashMap<>();
+        for (Binding binding : provideLink.bindings()) {
+            bindings.put(binding.name(), binding.value());
+        }
+    }
+
+    /**
+     * @return the annotation
+     */
+    public ProvideLink getProvideLink() {
+        return provideLink;
+    }
+
+    /**
+     * @return the annotated resource method
+     */
+    public ResourceMethod getResource() {
+        return resource;
+    }
+
+    /**
+     * Get the style
+     *
+     * @return the style
+     */
+    public InjectLink.Style getLinkStyle() {
+        return provideLink.style();
+    }
+
+    /**
+     * Get the link template, either directly from the value() or from the
+     * {@code @Path} of the class referenced in resource()
+     *
+     * @return the link template
+     */
+    @Override
+    public String getLinkTemplate(ResourceMappingContext rmc) {
+        String template = null;
+        ResourceMappingContext.Mapping map = rmc.getMapping(resource.getInvocable().getHandler().getHandlerClass());
+        if (map != null) {
+            template = map.getTemplate().getTemplate();
+        } else {
+            // extract template from specified class' @Path annotation
+            Path path = resource.getInvocable().getHandler().getHandlerClass().getAnnotation(Path.class);
+            template = path == null ? "" : path.value();
+        }
+        StringBuilder builder = new StringBuilder(template);
+
+        Path methodPath = resource.getInvocable().getDefinitionMethod().getAnnotation(Path.class);
+        if (methodPath != null) {
+            String methodTemplate = methodPath.value();
+
+            if (!(template.endsWith("/") || methodTemplate.startsWith("/"))) {
+                builder.append("/");
+            }
+            builder.append(methodTemplate);
+        }
+
+        CharSequence querySubString = InjectLinkFieldDescriptor.extractQueryParams(
+                new AnnotatedMethod(resource.getInvocable().getDefinitionMethod()));
+
+        if (querySubString.length() > 0) {
+            builder.append("{?");
+            builder.append(querySubString);
+            builder.append("}");
+        }
+
+        template = builder.toString();
+
+        return template;
+    }
+
+    /**
+     * Get the binding as an EL expression for a particular URI template parameter
+     *
+     * @param name binding name.
+     * @return the EL binding.
+     */
+    @Override
+    public String getBinding(String name) {
+        return bindings.get(name);
+    }
+
+    /**
+     * Get the condition.
+     *
+     * @return the condition
+     */
+    @Override
+    public String getCondition() {
+        return provideLink.condition();
+    }
+
+    /**
+     * Builds a link from a {@link URI}.
+     *
+     * @param uri base URI
+     * @return the {@link Link} instance
+     */
+    public Link getLink(URI uri) {
+        return ProvideLink.Util.buildLinkFromUri(uri, provideLink);
+    }
+
+    /**
+     * @return the parent annotation or {@code null}
+     */
+    public Annotation getParentAnnotation() {
+        return parentAnnotation;
+    }
+}
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/ProvideLinks.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/ProvideLinks.java
new file mode 100644
index 0000000..9e752ee
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/ProvideLinks.java
@@ -0,0 +1,41 @@
+/*
+ * 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.linking;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.glassfish.jersey.Beta;
+
+/**
+ * Container for repeatable annotation, see {@link ProvideLink} for details.
+ *
+ * @author Leonard Brünings
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Beta
+public @interface ProvideLinks {
+
+    /**
+     * Container for a set of {@link ProvideLink} annotations
+     * @return array of {@code ProvideLink} elements
+     */
+    ProvideLink[] value() default {};
+}
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/RequestLinkFilter.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/RequestLinkFilter.java
new file mode 100644
index 0000000..0e234fa
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/RequestLinkFilter.java
@@ -0,0 +1,45 @@
+/*
+ * 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.linking;
+
+import java.io.IOException;
+
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerRequestFilter;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Link;
+
+import org.glassfish.jersey.server.ExtendedUriInfo;
+
+/**
+ * Filter that processes {@link Link} annotated fields in returned response
+ * entities.
+ *
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ * @see Link
+ */
+class RequestLinkFilter implements ContainerRequestFilter {
+
+    @Context
+    private ExtendedUriInfo uriInfo;
+
+    @Override
+    public void filter(ContainerRequestContext requestContext) throws IOException {
+
+    }
+}
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/ResponseContextResolver.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/ResponseContextResolver.java
new file mode 100644
index 0000000..48f8546
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/ResponseContextResolver.java
@@ -0,0 +1,98 @@
+/*
+ * 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.linking;
+
+import java.beans.FeatureDescriptor;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import javax.el.ELContext;
+import javax.el.ELResolver;
+import javax.el.PropertyNotWritableException;
+
+/**
+ * The initial context resolver that resolves the entity and resource
+ * objects used at the start of an EL expression.
+ *
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+class ResponseContextResolver extends ELResolver {
+
+    private Map<String, Object> responseObjects;
+    private static final String ENTITY_OBJECT = "entity";
+    private static final String RESOURCE_OBJECT = "resource";
+    static final String INSTANCE_OBJECT = "instance";
+
+    ResponseContextResolver(Object entity, Object resource, Object instance) {
+        responseObjects = new HashMap<>();
+        responseObjects.put(ENTITY_OBJECT, entity);
+        responseObjects.put(RESOURCE_OBJECT, resource);
+        responseObjects.put(INSTANCE_OBJECT, instance);
+    }
+
+    private boolean isHandled(ELContext elc, Object base, Object property) {
+        if (base != null) {
+            return false;
+        }
+        if (responseObjects.containsKey(property.toString())) {
+            elc.setPropertyResolved(true);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public Object getValue(ELContext elc, Object base, Object property) {
+        if (isHandled(elc, base, property)) {
+            return responseObjects.get(property.toString());
+        }
+        return null;
+    }
+
+    @Override
+    public Class<?> getType(ELContext elc, Object o, Object o1) {
+        if (isHandled(elc, o, o1)) {
+            return getValue(elc, o, o1).getClass();
+        }
+        return null;
+    }
+
+    @Override
+    public void setValue(ELContext elc, Object o, Object o1, Object o2) {
+        throw new PropertyNotWritableException(o2.toString());
+    }
+
+    @Override
+    public boolean isReadOnly(ELContext elc, Object o, Object o1) {
+        if (isHandled(elc, o, o1)) {
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext elc, Object o) {
+        return null;
+    }
+
+    @Override
+    public Class<?> getCommonPropertyType(ELContext elc, Object o) {
+        return Object.class;
+    }
+}
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/ResponseLinkFilter.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/ResponseLinkFilter.java
new file mode 100644
index 0000000..24d83d6
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/ResponseLinkFilter.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.linking;
+
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerResponseContext;
+import javax.ws.rs.container.ContainerResponseFilter;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Link;
+import javax.ws.rs.core.UriInfo;
+
+import org.glassfish.jersey.linking.contributing.ResourceLinkContributionContext;
+import org.glassfish.jersey.linking.mapping.ResourceMappingContext;
+
+/**
+ * Filter that processes {@link Link} annotated fields in returned response
+ * entities.
+ *
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ * @see Link
+ */
+class ResponseLinkFilter implements ContainerResponseFilter {
+
+    @Context
+    private UriInfo uriInfo;
+
+    @Context
+    private ResourceMappingContext rmc;
+
+    @Context
+    private ResourceLinkContributionContext rlcc;
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public void filter(ContainerRequestContext request, ContainerResponseContext response) {
+        final Object entity = response.getEntity();
+
+        if (entity != null && !uriInfo.getMatchedResources().isEmpty()) {
+            Class<?> entityClass = entity.getClass();
+            HeaderProcessor lhp = new HeaderProcessor(entityClass);
+            lhp.processLinkHeaders(entity, uriInfo, rmc, response.getHeaders());
+            FieldProcessor lp = new FieldProcessor(entityClass);
+            lp.processLinks(entity, uriInfo, rmc, rlcc);
+        }
+
+    }
+}
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/contributing/NaiveResourceLinkContributionContext.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/contributing/NaiveResourceLinkContributionContext.java
new file mode 100644
index 0000000..ad492b1
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/contributing/NaiveResourceLinkContributionContext.java
@@ -0,0 +1,238 @@
+/*
+ * 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.linking.contributing;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.core.Context;
+
+import org.glassfish.jersey.linking.ProvideLink;
+import org.glassfish.jersey.linking.ProvideLinkDescriptor;
+import org.glassfish.jersey.linking.ProvideLinks;
+import org.glassfish.jersey.server.ExtendedResourceContext;
+import org.glassfish.jersey.server.model.HandlerConstructor;
+import org.glassfish.jersey.server.model.Invocable;
+import org.glassfish.jersey.server.model.MethodHandler;
+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.ResourceModelComponent;
+import org.glassfish.jersey.server.model.ResourceModelVisitor;
+import org.glassfish.jersey.server.model.RuntimeResource;
+
+/**
+ * Simple map based implementation of the ResourceLinkContributionContext.
+ *
+ * @author Leonard Brünings
+ */
+public class NaiveResourceLinkContributionContext implements ResourceLinkContributionContext {
+
+    private final ExtendedResourceContext erc;
+
+    /**
+     * Mappings holds a single-level mapping between a class, and it's {@link ProvideLinkDescriptor}s
+     */
+    private Map<Class<?>, List<ProvideLinkDescriptor>> mappings;
+
+    /**
+     * Contributions holds all contributions for a class, this includes all contributions from it's ancestors.
+     */
+    private Map<Class<?>, List<ProvideLinkDescriptor>> contributions = new ConcurrentHashMap<>();
+
+    /**
+     * C'tor
+     * @param erc the ExtendedResourceContext
+     */
+    public NaiveResourceLinkContributionContext(@Context final ExtendedResourceContext erc) {
+        this.erc = erc;
+    }
+
+    @Override
+    public List<ProvideLinkDescriptor> getContributorsFor(Class<?> entityClass) {
+        buildMappings();
+        return contributions.computeIfAbsent(entityClass,
+                aClass -> Collections.unmodifiableList(collectContributors(aClass, new ArrayList<>())));
+    }
+
+    /**
+     * Collects contributors for a class recursively up the ancestors chain.
+     *
+     * @param entityClass the class to
+     * @param contributors collector list
+     * @return contributors list for easier use in lambdas
+     */
+    private List<ProvideLinkDescriptor> collectContributors(Class<?> entityClass, List<ProvideLinkDescriptor> contributors) {
+        contributors.addAll(mappings.getOrDefault(entityClass, Collections.emptyList()));
+        Class<?> sc = entityClass.getSuperclass();
+        if (sc != null && sc != Object.class) {
+            collectContributors(sc, contributors);
+        }
+        return contributors;
+    }
+
+    private void buildMappings() {
+        if (mappings != null) {
+            return;
+        }
+        final Map<Class<?>, List<ProvideLinkDescriptor>> newMappings = new HashMap<>();
+
+        erc.getResourceModel().accept(new ResourceModelVisitor() {
+
+            private void processComponents(final ResourceModelComponent component) {
+
+                final List<? extends ResourceModelComponent> components = component.getComponents();
+                if (components != null) {
+                    for (final ResourceModelComponent rc : components) {
+                        rc.accept(this);
+                    }
+                }
+            }
+
+            @Override
+            public void visitInvocable(final Invocable invocable) {
+                processComponents(invocable);
+            }
+
+            @Override
+            public void visitRuntimeResource(final RuntimeResource runtimeResource) {
+                processComponents(runtimeResource);
+            }
+
+            @Override
+            public void visitResourceModel(final ResourceModel resourceModel) {
+                processComponents(resourceModel);
+            }
+
+            @Override
+            public void visitResourceHandlerConstructor(final HandlerConstructor handlerConstructor) {
+                processComponents(handlerConstructor);
+            }
+
+            @Override
+            public void visitMethodHandler(final MethodHandler methodHandler) {
+                processComponents(methodHandler);
+            }
+
+            @Override
+            public void visitChildResource(final Resource resource) {
+                processComponents(resource);
+            }
+
+            @Override
+            public void visitResource(final Resource resource) {
+                processComponents(resource);
+            }
+
+            @Override
+            public void visitResourceMethod(final ResourceMethod resourceMethod) {
+
+                if (resourceMethod.isExtended()) {
+                    return;
+                }
+
+                if (resourceMethod.getInvocable() != null) {
+                    final Invocable i = resourceMethod.getInvocable();
+
+                    Method method = i.getDefinitionMethod();
+
+                    List<ProvideLinkDescriptor> linkDescriptors = new ArrayList<>();
+
+                    handleMetaAnnotations(resourceMethod, method, linkDescriptors);
+
+                    handleAnnotations(resourceMethod, linkDescriptors, method, null);
+
+                    for (ProvideLinkDescriptor linkDescriptor : linkDescriptors) {
+                        for (Class<?> target : linkDescriptor.getProvideLink().value()) {
+                            target = handleInheritedTarget(linkDescriptor, target);
+                            newMappings.computeIfAbsent(target, aClass -> new ArrayList<>()).add(linkDescriptor);
+                        }
+                    }
+                }
+
+                processComponents(resourceMethod);
+            }
+
+            private Class<?> handleInheritedTarget(ProvideLinkDescriptor linkDescriptor, Class<?> target) {
+                if (Objects.equals(ProvideLink.InheritFromAnnotation.class, target)) {
+                    Annotation parentAnnotation = linkDescriptor.getParentAnnotation();
+                    if (parentAnnotation == null) {
+                        throw new IllegalArgumentException("InheritFromAnnotation can only be used for Annotations");
+                    }
+                    return findTarget(parentAnnotation);
+                }
+                return target;
+            }
+
+            private Class<?> findTarget(Annotation parentAnnotation) {
+                Method[] methods = parentAnnotation.annotationType().getDeclaredMethods();
+                for (Method method : methods) {
+                    if (method.isAccessible() || Class.class.isAssignableFrom(method.getReturnType())) {
+
+                        try {
+                            return (Class<?>) method.invoke(parentAnnotation);
+                        } catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException ex) {
+                            Logger.getLogger(NaiveResourceLinkContributionContext.class.getName()).log(Level.FINE, null, ex);
+                        }
+
+                    }
+                }
+                throw new IllegalArgumentException("No suitable element of type Class<?> found on: "
+                        + parentAnnotation.getClass());
+            }
+
+            private void handleMetaAnnotations(ResourceMethod resourceMethod, Method method,
+                    List<ProvideLinkDescriptor> linkDescriptors) {
+                Annotation[] annotations = method.getDeclaredAnnotations();
+                for (Annotation annotation : annotations) {
+                    handleAnnotations(resourceMethod, linkDescriptors, annotation.annotationType(), annotation);
+                }
+            }
+
+            private void handleAnnotations(ResourceMethod resourceMethod, List<ProvideLinkDescriptor> linkDescriptors,
+                    AnnotatedElement element, Annotation parentAnnotation) {
+                if (element.isAnnotationPresent(ProvideLink.class) || element.isAnnotationPresent(ProvideLinks.class)) {
+                    ProvideLink provideLink = element.getAnnotation(ProvideLink.class);
+                    if (provideLink != null) {
+                        linkDescriptors.add(new ProvideLinkDescriptor(resourceMethod, provideLink, parentAnnotation));
+                    }
+                    ProvideLinks provideLinks = element.getAnnotation(ProvideLinks.class);
+                    if (provideLinks != null) {
+                        for (ProvideLink link : provideLinks.value()) {
+                            linkDescriptors.add(new ProvideLinkDescriptor(resourceMethod, link, parentAnnotation));
+                        }
+                    }
+
+                }
+            }
+
+        });
+
+        mappings = newMappings;
+    }
+}
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/contributing/ResourceLinkContributionContext.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/contributing/ResourceLinkContributionContext.java
new file mode 100644
index 0000000..9a4aaf5
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/contributing/ResourceLinkContributionContext.java
@@ -0,0 +1,39 @@
+/*
+ * 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.linking.contributing;
+
+import java.util.List;
+
+import org.glassfish.jersey.linking.ProvideLinkDescriptor;
+
+/**
+ * The ResourceLinkContributionContext provides access for link contributions from other sources to an entity.
+ *
+ * @author Leonard Brünings
+ */
+public interface ResourceLinkContributionContext {
+
+    /**
+     * Returns all link contributions for an entity class.
+     *
+     * It also includes contributions for every ancestor of entityClass.
+     *
+     * @param entityClass the entityClass
+     * @return list of link contributions to add to the class
+     */
+    List<ProvideLinkDescriptor> getContributorsFor(Class<?> entityClass);
+}
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/mapping/NaiveResourceMappingContext.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/mapping/NaiveResourceMappingContext.java
new file mode 100644
index 0000000..46f7617
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/mapping/NaiveResourceMappingContext.java
@@ -0,0 +1,219 @@
+/*
+ * 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.linking.mapping;
+
+import java.lang.reflect.Type;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import javax.ws.rs.core.Context;
+
+import org.glassfish.jersey.process.Inflector;
+import org.glassfish.jersey.server.ExtendedResourceContext;
+import org.glassfish.jersey.server.model.HandlerConstructor;
+import org.glassfish.jersey.server.model.Invocable;
+import org.glassfish.jersey.server.model.MethodHandler;
+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.ResourceModelComponent;
+import org.glassfish.jersey.server.model.ResourceModelVisitor;
+import org.glassfish.jersey.server.model.RuntimeResource;
+import org.glassfish.jersey.uri.PathPattern;
+import org.glassfish.jersey.uri.UriTemplate;
+
+/**
+ * This implementation of the resource mapping context assumed resources are
+ * of a simple type with a statically defined structure.
+ *
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+
+public class NaiveResourceMappingContext implements ResourceMappingContext {
+
+    private final ExtendedResourceContext erc;
+
+    private Map<Class<?>, ResourceMappingContext.Mapping> mappings;
+
+    public NaiveResourceMappingContext(@Context final ExtendedResourceContext erc) {
+        this.erc = erc;
+    }
+
+    @Override
+    public Mapping getMapping(final Class<?> resource) {
+        buildMappings();
+        return mappings.get(resource);
+    }
+
+    private void buildMappings() {
+        if (mappings != null) {
+            return;
+        }
+        mappings = new HashMap<>();
+
+        erc.getResourceModel().accept(new ResourceModelVisitor() {
+
+            Deque<PathPattern> stack = new LinkedList<>();
+
+            private void processComponents(final ResourceModelComponent component) {
+
+                final List<? extends ResourceModelComponent> components = component.getComponents();
+                if (components != null) {
+                    for (final ResourceModelComponent rc : components) {
+                        rc.accept(this);
+                    }
+                }
+            }
+
+            @Override
+            public void visitInvocable(final Invocable invocable) {
+                processComponents(invocable);
+            }
+
+            @Override
+            public void visitRuntimeResource(final RuntimeResource runtimeResource) {
+                processComponents(runtimeResource);
+            }
+
+            @Override
+            public void visitResourceModel(final ResourceModel resourceModel) {
+                processComponents(resourceModel);
+            }
+
+            @Override
+            public void visitResourceHandlerConstructor(final HandlerConstructor handlerConstructor) {
+                processComponents(handlerConstructor);
+            }
+
+            @Override
+            public void visitMethodHandler(final MethodHandler methodHandler) {
+                processComponents(methodHandler);
+            }
+
+            @Override
+            public void visitChildResource(final Resource resource) {
+                visitResourceIntl(resource, false);
+            }
+
+            @Override
+            public void visitResource(final Resource resource) {
+
+                visitResourceIntl(resource, true);
+            }
+
+            private void visitResourceIntl(final Resource resource, final boolean isRoot) {
+                try {
+                    stack.addLast(resource.getPathPattern());
+                    processComponents(resource);
+
+                    if (isRoot) {
+                        Class likelyToBeRoot = null;
+                        for (final Class next : resource.getHandlerClasses()) {
+                            if (!(Inflector.class.isAssignableFrom(next))) {
+                                likelyToBeRoot = next;
+                            }
+                        }
+
+                        if (likelyToBeRoot != null) {
+                            mappings.put(likelyToBeRoot, getMapping(getTemplate()));
+                        }
+                    }
+                } finally {
+                    stack.removeLast();
+                }
+            }
+
+            @Override
+            public void visitResourceMethod(final ResourceMethod resourceMethod) {
+
+                if (resourceMethod.isExtended()) {
+                    return;
+                }
+
+                if (ResourceMethod.JaxrsType.SUB_RESOURCE_LOCATOR.equals(resourceMethod.getType())) {
+                    if (resourceMethod.getInvocable() != null) {
+                        final Invocable i = resourceMethod.getInvocable();
+
+                        final Type type = i.getResponseType();
+                        final StringBuilder template = getTemplate();
+
+                        mappings.put((Class) type, getMapping(template));
+
+                        // Process sub resources ?
+
+                        Resource.Builder builder = Resource
+                                .builder(i.getRawResponseType());
+                        if (builder == null) {
+                            // for example in the case the return type of the sub resource locator is Object
+                            builder = Resource.builder().path(resourceMethod.getParent().getPath());
+                        }
+                        final Resource subResource = builder.build();
+
+                        visitChildResource(subResource);
+                    }
+                }
+
+                processComponents(resourceMethod);
+            }
+
+            private StringBuilder getTemplate() {
+                final StringBuilder template = new StringBuilder();
+                for (final PathPattern pp : stack) {
+                    final String ppTemplate = pp.getTemplate().getTemplate();
+
+                    final int tlength = template.length();
+                    if (tlength > 0) {
+                        if (template.charAt(tlength - 1) == '/') {
+                            if (ppTemplate.startsWith("/")) {
+                                template.append(ppTemplate, 1, ppTemplate.length());
+                            } else {
+                                template.append(ppTemplate);
+                            }
+                        } else {
+                            if (ppTemplate.startsWith("/")) {
+                                template.append(ppTemplate);
+                            } else {
+                                template.append("/");
+                                template.append(ppTemplate);
+                            }
+                        }
+                    } else {
+                        template.append(ppTemplate);
+                    }
+
+                }
+                return template;
+            }
+        });
+
+    }
+
+    private Mapping getMapping(final StringBuilder template) {
+        return new Mapping() {
+            UriTemplate uriTemplate = new UriTemplate(template.toString());
+
+            @Override
+            public UriTemplate getTemplate() {
+                return uriTemplate;
+            }
+        };
+    }
+
+}
diff --git a/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/mapping/ResourceMappingContext.java b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/mapping/ResourceMappingContext.java
new file mode 100644
index 0000000..9d04d89
--- /dev/null
+++ b/incubator/declarative-linking/src/main/java/org/glassfish/jersey/linking/mapping/ResourceMappingContext.java
@@ -0,0 +1,36 @@
+/*
+ * 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.linking.mapping;
+
+import org.glassfish.jersey.uri.UriTemplate;
+
+/**
+ * This service tries to work out the UriTemplate required for a particular
+ * resource class.
+ *
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+
+public interface ResourceMappingContext {
+
+    public interface Mapping {
+
+        public UriTemplate getTemplate();
+    }
+
+    public Mapping getMapping(Class<?> resource);
+}
diff --git a/incubator/declarative-linking/src/main/resources/org/glassfish/jersey/media/linking/internal/linkMessages.properties b/incubator/declarative-linking/src/main/resources/org/glassfish/jersey/media/linking/internal/linkMessages.properties
new file mode 100644
index 0000000..681e193
--- /dev/null
+++ b/incubator/declarative-linking/src/main/resources/org/glassfish/jersey/media/linking/internal/linkMessages.properties
@@ -0,0 +1,18 @@
+#
+# 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
+#
+
+warning.linkfilter.processing=LinkFilter cannot process class {0}, exception occurred during processing. Class will be ignored \
+  in the LinkFilter.
diff --git a/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/EntityDescriptorTest.java b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/EntityDescriptorTest.java
new file mode 100644
index 0000000..5ed63fd
--- /dev/null
+++ b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/EntityDescriptorTest.java
@@ -0,0 +1,144 @@
+/*
+ * 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.linking;
+
+import java.net.URI;
+import java.util.Iterator;
+
+import javax.ws.rs.Path;
+import javax.ws.rs.core.UriBuilder;
+
+import org.glassfish.jersey.linking.mapping.ResourceMappingContext;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+/**
+ *
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+public class EntityDescriptorTest {
+
+    public static class TestClassA {
+
+        @InjectLink
+        protected String foo;
+
+        @InjectLink
+        private String bar;
+
+        public String baz;
+    }
+
+    ResourceMappingContext mockRmc = new ResourceMappingContext() {
+
+        @Override
+        public ResourceMappingContext.Mapping getMapping(Class<?> resource) {
+            return null;
+        }
+    };
+
+    /**
+     * Test for declared properties
+     */
+    @Test
+    public void testDeclaredProperties() {
+        System.out.println("Declared properties");
+        EntityDescriptor instance = EntityDescriptor.getInstance(TestClassA.class);
+        assertEquals(2, instance.getLinkFields().size());
+        assertEquals(1, instance.getNonLinkFields().size());
+    }
+
+    public static class TestClassB extends TestClassA {
+
+        @InjectLink
+        private String bar;
+    }
+
+    /**
+     * Test for inherited properties
+     */
+    @Test
+    public void testInheritedProperties() {
+        System.out.println("Inherited properties");
+        EntityDescriptor instance = EntityDescriptor.getInstance(TestClassB.class);
+        assertEquals(2, instance.getLinkFields().size());
+        assertEquals(1, instance.getNonLinkFields().size());
+    }
+
+    private static final String TEMPLATE_A = "foo";
+
+    @Path(TEMPLATE_A)
+    public static class TestResourceA {
+    }
+
+    public static class TestClassC {
+
+        @InjectLink(resource = TestResourceA.class, bindings = {@Binding(name = "bar", value = "baz")})
+        String res;
+    }
+
+    @Test
+    public void testResourceLink() {
+        System.out.println("Resource class link");
+        EntityDescriptor instance = EntityDescriptor.getInstance(TestClassC.class);
+        assertEquals(1, instance.getLinkFields().size());
+        assertEquals(0, instance.getNonLinkFields().size());
+        InjectLinkFieldDescriptor linkDesc = (InjectLinkFieldDescriptor) instance.getLinkFields().iterator().next();
+        assertEquals(TEMPLATE_A, linkDesc.getLinkTemplate(mockRmc));
+        assertEquals("baz", linkDesc.getBinding("bar"));
+    }
+
+    public static class TestClassD {
+
+        @InjectLink(value = TEMPLATE_A, style = InjectLink.Style.RELATIVE_PATH)
+        private String res1;
+
+        @InjectLink(value = TEMPLATE_A, style = InjectLink.Style.RELATIVE_PATH)
+        private URI res2;
+    }
+
+    @Test
+    public void testStringLink() {
+        System.out.println("String link");
+        EntityDescriptor instance = EntityDescriptor.getInstance(TestClassD.class);
+        assertEquals(2, instance.getLinkFields().size());
+        assertEquals(0, instance.getNonLinkFields().size());
+        Iterator<FieldDescriptor> i = instance.getLinkFields().iterator();
+        while (i.hasNext()) {
+            InjectLinkFieldDescriptor linkDesc = (InjectLinkFieldDescriptor) i.next();
+            assertEquals(TEMPLATE_A, linkDesc.getLinkTemplate(mockRmc));
+        }
+    }
+
+    @Test
+    public void testSetLink() {
+        System.out.println("Set link");
+        EntityDescriptor instance = EntityDescriptor.getInstance(TestClassD.class);
+        Iterator<FieldDescriptor> i = instance.getLinkFields().iterator();
+        TestClassD testClass = new TestClassD();
+        while (i.hasNext()) {
+            InjectLinkFieldDescriptor linkDesc = (InjectLinkFieldDescriptor) i.next();
+            URI value = UriBuilder.fromPath(linkDesc.getLinkTemplate(mockRmc)).build();
+            linkDesc.setPropertyValue(testClass, value);
+        }
+        assertEquals(TEMPLATE_A, testClass.res1);
+        assertEquals(TEMPLATE_A, testClass.res2.toString());
+    }
+
+}
diff --git a/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/FieldProcessorTest.java b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/FieldProcessorTest.java
new file mode 100644
index 0000000..e0afeba
--- /dev/null
+++ b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/FieldProcessorTest.java
@@ -0,0 +1,853 @@
+/*
+ * 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.linking;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Filter;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+import java.util.regex.MatchResult;
+import java.util.zip.ZipEntry;
+import javax.ws.rs.BeanParam;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Link;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.PathSegment;
+import javax.ws.rs.core.UriBuilder;
+
+import javax.xml.bind.annotation.XmlTransient;
+
+import org.glassfish.jersey.internal.util.collection.MultivaluedStringMap;
+import org.glassfish.jersey.linking.contributing.ResourceLinkContributionContext;
+import org.glassfish.jersey.linking.mapping.ResourceMappingContext;
+import org.glassfish.jersey.server.ExtendedUriInfo;
+import org.glassfish.jersey.server.model.Resource;
+import org.glassfish.jersey.server.model.ResourceMethod;
+import org.glassfish.jersey.server.model.RuntimeResource;
+import org.glassfish.jersey.uri.UriTemplate;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+public class FieldProcessorTest {
+
+    private static final Logger LOG = Logger.getLogger(FieldProcessor.class.getName());
+
+    ExtendedUriInfo mockUriInfo = new ExtendedUriInfo() {
+
+        private static final String baseURI = "http://example.com/application/resources";
+
+        private MultivaluedMap queryParams = new MultivaluedStringMap();
+
+        @Override
+        public String getPath() {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        @Override
+        public String getPath(boolean decode) {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        @Override
+        public List<PathSegment> getPathSegments() {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        @Override
+        public List<PathSegment> getPathSegments(boolean decode) {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        @Override
+        public URI getRequestUri() {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        @Override
+        public UriBuilder getRequestUriBuilder() {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        @Override
+        public URI getAbsolutePath() {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        @Override
+        public UriBuilder getAbsolutePathBuilder() {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        @Override
+        public URI getBaseUri() {
+            return getBaseUriBuilder().build();
+        }
+
+        @Override
+        public UriBuilder getBaseUriBuilder() {
+            return UriBuilder.fromUri(baseURI);
+        }
+
+        @Override
+        public MultivaluedMap<String, String> getPathParameters() {
+            return new MultivaluedStringMap();
+        }
+
+        @Override
+        public MultivaluedMap<String, String> getPathParameters(boolean decode) {
+            return new MultivaluedStringMap();
+        }
+
+        @Override
+        public MultivaluedMap<String, String> getQueryParameters() {
+            return queryParams;
+        }
+
+        @Override
+        public MultivaluedMap<String, String> getQueryParameters(boolean decode) {
+            return queryParams;
+        }
+
+        @Override
+        public List<String> getMatchedURIs() {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        @Override
+        public List<String> getMatchedURIs(boolean decode) {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        @Override
+        public List<Object> getMatchedResources() {
+            Object dummyResource = new Object() {
+            };
+            return Collections.singletonList(dummyResource);
+        }
+
+        @Override
+        public Throwable getMappedThrowable() {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        @Override
+        public List<MatchResult> getMatchedResults() {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        @Override
+        public List<UriTemplate> getMatchedTemplates() {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        @Override
+        public List<PathSegment> getPathSegments(String name) {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        @Override
+        public List<PathSegment> getPathSegments(String name, boolean decode) {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        @Override
+        public List<RuntimeResource> getMatchedRuntimeResources() {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        @Override
+        public ResourceMethod getMatchedResourceMethod() {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        @Override
+        public Resource getMatchedModelResource() {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        @Override
+        public List<ResourceMethod> getMatchedResourceLocators() {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        @Override
+        public List<Resource> getLocatorSubResources() {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        @Override
+        public URI resolve(URI uri) {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        @Override
+        public URI relativize(URI uri) {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+    };
+
+    private final ResourceMappingContext mockRmc = new ResourceMappingContext() {
+
+        @Override
+        public ResourceMappingContext.Mapping getMapping(Class<?> resource) {
+            return null;
+        }
+    };
+
+    protected final ResourceLinkContributionContext mockRlcc = new ResourceLinkContributionContext() {
+        @Override
+        public List<ProvideLinkDescriptor> getContributorsFor(Class<?> entityClass) {
+            return Collections.emptyList();
+        }
+    };
+
+    private static final String TEMPLATE_A = "foo";
+
+    public static class TestClassD {
+
+        @InjectLink(value = TEMPLATE_A, style = InjectLink.Style.RELATIVE_PATH)
+        private String res1;
+
+        @InjectLink(value = TEMPLATE_A, style = InjectLink.Style.RELATIVE_PATH)
+        private URI res2;
+    }
+
+    @Test
+    public void testProcessLinks() {
+        LOG.info("Links");
+
+        FieldProcessor<TestClassD> instance = new FieldProcessor(TestClassD.class);
+        TestClassD testClass = new TestClassD();
+        instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
+        assertEquals(TEMPLATE_A, testClass.res1);
+        assertEquals(TEMPLATE_A, testClass.res2.toString());
+    }
+
+    private static final String TEMPLATE_B = "widgets/{id}";
+
+    public static class TestClassE {
+
+        @InjectLink(value = TEMPLATE_B, style = InjectLink.Style.RELATIVE_PATH)
+        private String link;
+
+        private String id;
+
+        public TestClassE(String id) {
+            this.id = id;
+        }
+
+        public String getId() {
+            return id;
+        }
+    }
+
+    @Test
+    public void testProcessLinksWithFields() {
+        LOG.info("Links from field values");
+        FieldProcessor<TestClassE> instance = new FieldProcessor(TestClassE.class);
+        TestClassE testClass = new TestClassE("10");
+        instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
+        assertEquals("widgets/10", testClass.link);
+    }
+
+    public static class TestClassF {
+
+        @InjectLink(value = TEMPLATE_B, style = InjectLink.Style.RELATIVE_PATH)
+        private String thelink;
+
+        private String id;
+        private TestClassE nested;
+
+        public TestClassF(String id, TestClassE e) {
+            this.id = id;
+            this.nested = e;
+        }
+
+        public String getId() {
+            return id;
+        }
+    }
+
+    @Test
+    public void testNesting() {
+        LOG.info("Nesting");
+        FieldProcessor<TestClassF> instance = new FieldProcessor(TestClassF.class);
+        TestClassE nested = new TestClassE("10");
+        TestClassF testClass = new TestClassF("20", nested);
+        instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
+        assertEquals("widgets/20", testClass.thelink);
+        assertEquals("widgets/10", testClass.nested.link);
+    }
+
+    @Test
+    public void testArray() {
+        LOG.info("Array");
+        FieldProcessor<TestClassE[]> instance = new FieldProcessor(TestClassE[].class);
+        TestClassE item1 = new TestClassE("10");
+        TestClassE item2 = new TestClassE("20");
+        TestClassE array[] = {item1, item2};
+        instance.processLinks(array, mockUriInfo, mockRmc, mockRlcc);
+        assertEquals("widgets/10", array[0].link);
+        assertEquals("widgets/20", array[1].link);
+    }
+
+    @Test
+    public void testCollection() {
+        LOG.info("Collection");
+        FieldProcessor<List> instance = new FieldProcessor(List.class);
+        TestClassE item1 = new TestClassE("10");
+        TestClassE item2 = new TestClassE("20");
+        List<TestClassE> list = Arrays.asList(item1, item2);
+        instance.processLinks(list, mockUriInfo, mockRmc, mockRlcc);
+        assertEquals("widgets/10", list.get(0).link);
+        assertEquals("widgets/20", list.get(1).link);
+    }
+
+    @Test
+    public void testMap() {
+        LOG.info("Map");
+        FieldProcessor<Map> instance = new FieldProcessor(Map.class);
+        TestClassE item1 = new TestClassE("10");
+        TestClassE item2 = new TestClassE("20");
+        Map<String, TestClassE> map = new HashMap<>();
+        for (TestClassE item : Arrays.asList(item1, item2)) {
+            map.put(item.getId(), item);
+        }
+        instance.processLinks(map, mockUriInfo, mockRmc, mockRlcc);
+        assertEquals("widgets/10", map.get("10").link);
+        assertEquals("widgets/20", map.get("20").link);
+    }
+
+    public static class TestClassG {
+
+        @InjectLink(value = TEMPLATE_B, style = InjectLink.Style.RELATIVE_PATH)
+        private String relativePath;
+
+        @InjectLink(value = TEMPLATE_B, style = InjectLink.Style.ABSOLUTE_PATH)
+        private String absolutePath;
+
+        @InjectLink(value = TEMPLATE_B, style = InjectLink.Style.ABSOLUTE)
+        private String absolute;
+
+        @InjectLink(TEMPLATE_B)
+        private String defaultStyle;
+
+        private String id;
+
+        public TestClassG(String id) {
+            this.id = id;
+        }
+
+        public String getId() {
+            return id;
+        }
+    }
+
+    @Test
+    public void testLinkStyles() {
+        LOG.info("Link styles");
+        FieldProcessor<TestClassG> instance = new FieldProcessor(TestClassG.class);
+        TestClassG testClass = new TestClassG("10");
+        instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
+        assertEquals("widgets/10", testClass.relativePath);
+        assertEquals("/application/resources/widgets/10", testClass.absolutePath);
+        assertEquals("/application/resources/widgets/10", testClass.defaultStyle);
+        assertEquals("http://example.com/application/resources/widgets/10", testClass.absolute);
+    }
+
+    public static class TestClassH {
+
+        @InjectLink(TEMPLATE_B)
+        private String link;
+
+        public String getId() {
+            return "10";
+        }
+    }
+
+    @Test
+    public void testComputedProperty() {
+        LOG.info("Computed property");
+        FieldProcessor<TestClassH> instance = new FieldProcessor(TestClassH.class);
+        TestClassH testClass = new TestClassH();
+        instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
+        assertEquals("/application/resources/widgets/10", testClass.link);
+    }
+
+    public static class TestClassI {
+
+        @InjectLink("widgets/${entity.id}")
+        private String link;
+
+        public String getId() {
+            return "10";
+        }
+    }
+
+    @Test
+    public void testEL() {
+        LOG.info("El link");
+        FieldProcessor<TestClassI> instance = new FieldProcessor(TestClassI.class);
+        TestClassI testClass = new TestClassI();
+        instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
+        assertEquals("/application/resources/widgets/10", testClass.link);
+    }
+
+    public static class TestClassJ {
+
+        @InjectLink("widgets/${entity.id}/widget/{id}")
+        private String link;
+
+        public String getId() {
+            return "10";
+        }
+    }
+
+    @Test
+    public void testMixed() {
+        LOG.info("Mixed EL and template vars link");
+        FieldProcessor<TestClassJ> instance = new FieldProcessor(TestClassJ.class);
+        TestClassJ testClass = new TestClassJ();
+        instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
+        assertEquals("/application/resources/widgets/10/widget/10", testClass.link);
+    }
+
+    public static class DependentInnerBean {
+
+        @InjectLink("${entity.id}")
+        public String outerUri;
+        @InjectLink("${instance.id}")
+        public String innerUri;
+
+        public String getId() {
+            return "inner";
+        }
+    }
+
+    public static class OuterBean {
+
+        public DependentInnerBean inner = new DependentInnerBean();
+
+        public String getId() {
+            return "outer";
+        }
+    }
+
+    @Test
+    public void testELScopes() {
+        LOG.info("EL scopes");
+        FieldProcessor<OuterBean> instance = new FieldProcessor(OuterBean.class);
+        OuterBean testClass = new OuterBean();
+        instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
+        assertEquals("/application/resources/inner", testClass.inner.innerUri);
+        assertEquals("/application/resources/outer", testClass.inner.outerUri);
+    }
+
+    public static class BoundLinkBean {
+
+        @InjectLink(value = "{id}", bindings = {@Binding(name = "id", value = "${instance.name}")})
+        public String uri;
+
+        public String getName() {
+            return "name";
+        }
+    }
+
+    @Test
+    public void testELBinding() {
+        LOG.info("EL binding");
+        FieldProcessor<BoundLinkBean> instance = new FieldProcessor(BoundLinkBean.class);
+        BoundLinkBean testClass = new BoundLinkBean();
+        instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
+        assertEquals("/application/resources/name", testClass.uri);
+    }
+
+    public static class BoundLinkOnLinkBean {
+
+        @InjectLink(value = "{id}",
+                bindings = {@Binding(name = "id", value = "${instance.name}")},
+                rel = "self")
+        public Link link;
+
+        public String getName() {
+            return "name";
+        }
+    }
+
+    @Test
+    public void testELBindingOnLink() {
+        LOG.info("EL binding");
+        FieldProcessor<BoundLinkOnLinkBean> instance = new FieldProcessor(BoundLinkOnLinkBean.class);
+        BoundLinkOnLinkBean testClass = new BoundLinkOnLinkBean();
+        instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
+        assertEquals("/application/resources/name", testClass.link.getUri().toString());
+        assertEquals("self", testClass.link.getRel());
+    }
+
+    public static class BoundLinkOnLinksBean {
+
+        @InjectLinks({
+                @InjectLink(value = "{id}",
+                        bindings = {@Binding(name = "id", value = "${instance.name}")},
+                        rel = "self"),
+                @InjectLink(value = "{id}",
+                        bindings = {@Binding(name = "id", value = "${instance.name}")},
+                        rel = "other"),
+
+        })
+        public List<Link> links;
+
+        @InjectLinks({
+                @InjectLink(value = "{id}",
+                        bindings = {@Binding(name = "id", value = "${instance.name}")},
+                        rel = "self"),
+                @InjectLink(value = "{id}",
+                        bindings = {@Binding(name = "id", value = "${instance.name}")},
+                        rel = "other"),
+
+        })
+        public Link[] linksArray;
+
+        public String getName() {
+            return "name";
+        }
+    }
+
+    @Test
+    public void testELBindingOnLinks() {
+        LOG.info("EL binding");
+        FieldProcessor<BoundLinkOnLinksBean> instance = new FieldProcessor(BoundLinkOnLinksBean.class);
+        BoundLinkOnLinksBean testClass = new BoundLinkOnLinksBean();
+        instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
+        assertEquals("/application/resources/name", testClass.links.get(0).getUri().toString());
+        assertEquals("self", testClass.links.get(0).getRel());
+        assertEquals("other", testClass.links.get(1).getRel());
+
+        assertEquals("/application/resources/name", testClass.linksArray[0].getUri().toString());
+        assertEquals("self", testClass.linksArray[0].getRel());
+        assertEquals("other", testClass.linksArray[1].getRel());
+
+    }
+
+    public static class ConditionalLinkBean {
+
+        @InjectLink(value = "{id}", condition = "${entity.uri1Enabled}")
+        public String uri1;
+
+        @InjectLink(value = "{id}", condition = "${entity.uri2Enabled}")
+        public String uri2;
+
+        public String getId() {
+            return "name";
+        }
+
+        public boolean isUri1Enabled() {
+            return true;
+        }
+
+        public boolean isUri2Enabled() {
+            return false;
+        }
+    }
+
+    @Test
+    public void testCondition() {
+        LOG.info("Condition");
+        FieldProcessor<ConditionalLinkBean> instance = new FieldProcessor(ConditionalLinkBean.class);
+        ConditionalLinkBean testClass = new ConditionalLinkBean();
+        instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
+        assertEquals("/application/resources/name", testClass.uri1);
+        assertEquals(null, testClass.uri2);
+    }
+
+    @Path("a")
+    public static class SubResource {
+
+        @Path("b")
+        @GET
+        public String getB() {
+            return "hello world";
+        }
+    }
+
+    public static class SubResourceBean {
+
+        @InjectLink(resource = SubResource.class, method = "getB")
+        public String uri;
+    }
+
+    @Test
+    public void testSubresource() {
+        LOG.info("Subresource");
+        FieldProcessor<SubResourceBean> instance = new FieldProcessor(SubResourceBean.class);
+        SubResourceBean testClass = new SubResourceBean();
+        instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
+        assertEquals("/application/resources/a/b", testClass.uri);
+    }
+
+    @Path("a")
+    public static class QueryResource {
+
+        @Path("b")
+        @GET
+        public String getB(@QueryParam("query") String query, @QueryParam("query2") String query2) {
+            return "hello world";
+        }
+    }
+
+    public static class QueryResourceBean {
+
+        public String getQueryParam() {
+            return queryExample;
+        }
+
+        private String queryExample;
+
+        public QueryResourceBean(String queryExample, String queryExample2) {
+            this.queryExample = queryExample;
+            this.queryExample2 = queryExample2;
+        }
+
+        public String getQueryParam2() {
+            return queryExample2;
+        }
+
+        private String queryExample2;
+
+        @InjectLink(resource = QueryResource.class, method = "getB",
+                bindings = {
+                        @Binding(name = "query", value = "${instance.queryParam}"),
+                        @Binding(name = "query2", value = "${instance.queryParam2}")
+                })
+        public String uri;
+    }
+
+    public static class QueryResourceBeanNoBindings {
+        //query parameters will be populated from uriInfo
+        //JERSEY-2863
+        @InjectLink(resource = QueryResource.class, method = "getB")
+        public String uri;
+    }
+
+    @Test
+    public void testQueryResource() {
+        LOG.info("QueryResource");
+        FieldProcessor<QueryResourceBean> instance = new FieldProcessor(QueryResourceBean.class);
+        QueryResourceBean testClass = new QueryResourceBean("queryExample", null);
+        instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
+        assertEquals("/application/resources/a/b?query=queryExample&query2=", testClass.uri);
+    }
+
+    @Test
+    public void testDoubleQueryResource() {
+        LOG.info("QueryResource");
+        FieldProcessor<QueryResourceBean> instance = new FieldProcessor(QueryResourceBean.class);
+        QueryResourceBean testClass = new QueryResourceBean("queryExample", "queryExample2");
+        instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
+        assertEquals("/application/resources/a/b?query=queryExample&query2=queryExample2", testClass.uri);
+    }
+
+    @Test
+    public void testQueryResourceWithoutBindings() {
+        LOG.info("QueryResource");
+        FieldProcessor<QueryResourceBeanNoBindings> instance = new FieldProcessor(QueryResourceBeanNoBindings.class);
+        QueryResourceBeanNoBindings testClass = new QueryResourceBeanNoBindings();
+        mockUriInfo.getQueryParameters().putSingle("query", "queryExample");
+        mockUriInfo.getQueryParameters().putSingle("query2", "queryExample2");
+        assertEquals("queryExample", mockUriInfo.getQueryParameters().getFirst("query"));
+        instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
+        assertEquals("/application/resources/a/b?query=queryExample&query2=queryExample2", testClass.uri);
+        //clean mock
+        mockUriInfo.getQueryParameters().clear();
+    }
+
+    /** Bean param with method setter QueryParam. */
+    public static class BeanParamBeanA {
+        private String qparam;
+        @QueryParam("qparam")
+        public void setQParam(String qparam) {
+            this.qparam = qparam;
+        }
+    }
+
+    /** Bean param with field QueryParam. */
+    public static class BeanParamBeanB {
+        @QueryParam("query") public String query;
+    }
+
+    @Path("a")
+    public static class BeanParamQueryResource {
+
+        @Path("b")
+        @GET
+        public String getB(@BeanParam BeanParamBeanA beanParamBeanA, @BeanParam BeanParamBeanB beanParamBeanB) {
+            return "hello world";
+        }
+    }
+
+    public static class BeanParamResourceBean {
+
+        public String getQueryParam() {
+            return queryExample;
+        }
+
+        private String queryExample;
+
+        public BeanParamResourceBean(String queryExample) {
+            this.queryExample = queryExample;
+        }
+
+        @InjectLink(resource = BeanParamQueryResource.class, method = "getB",
+                bindings = {
+                        @Binding(name = "query", value = "${instance.queryParam}"),
+                        @Binding(name = "qparam", value = "foo")
+                })
+        public String uri;
+    }
+
+    @Test
+    public void testBeanParamResource() {
+        LOG.info("BeanParamResource");
+        FieldProcessor<BeanParamResourceBean> instance = new FieldProcessor(BeanParamResourceBean.class);
+        BeanParamResourceBean testClass = new BeanParamResourceBean("queryExample");
+        instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
+        assertEquals("/application/resources/a/b?qparam=foo&query=queryExample", testClass.uri);
+    }
+
+    public static class TestClassK {
+
+        public static final ZipEntry zipEntry = new ZipEntry("entry");
+    }
+
+    public static class TestClassL {
+
+        public final ZipEntry zipEntry = new ZipEntry("entry");
+    }
+
+    private class LoggingFilter implements Filter {
+
+        private int count = 0;
+
+        @Override
+        public synchronized boolean isLoggable(LogRecord logRecord) {
+            if (logRecord.getThrown() instanceof IllegalAccessException) {
+                count++;
+                return false;
+            }
+            return true;
+        }
+
+        public int getCount() {
+            return count;
+        }
+    }
+
+    @Test
+    public void testKL() {
+        final LoggingFilter lf = new LoggingFilter();
+
+        Logger.getLogger(FieldDescriptor.class.getName()).setFilter(lf);
+        assertTrue(lf.getCount() == 0);
+
+        FieldProcessor<TestClassK> instanceK = new FieldProcessor(TestClassK.class);
+        TestClassK testClassK = new TestClassK();
+        instanceK.processLinks(testClassK, mockUriInfo, mockRmc, mockRlcc);
+
+        assertTrue(lf.getCount() == 0);
+
+        FieldProcessor<TestClassL> instanceL = new FieldProcessor(TestClassL.class);
+        TestClassL testClassL = new TestClassL();
+        instanceL.processLinks(testClassL, mockUriInfo, mockRmc, mockRlcc);
+
+        assertTrue(lf.getCount() == 0);
+
+        Logger.getLogger(FieldDescriptor.class.getName()).setFilter(null);
+
+    }
+
+    public static class TestClassM {
+
+        @InjectLink(value = TEMPLATE_B, style = InjectLink.Style.RELATIVE_PATH)
+        private String thelink;
+
+        private String id;
+
+        @InjectLinkNoFollow
+        private TestClassE nested;
+
+        @XmlTransient
+        private TestClassE transientNested;
+
+        public TestClassM(String id, TestClassE e, TestClassE transientNested) {
+            this.id = id;
+            this.nested = e;
+            this.transientNested = transientNested;
+        }
+
+        public String getId() {
+            return id;
+        }
+    }
+
+    @Test
+    public void testNoRecursiveNesting() {
+        LOG.info("No Recursive Nesting");
+        FieldProcessor<TestClassM> instance = new FieldProcessor(TestClassM.class);
+        TestClassE nested = new TestClassE("10");
+        TestClassE transientNested = new TestClassE("30");
+        TestClassM testClass = new TestClassM("20", nested, transientNested);
+        instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
+        assertEquals("widgets/20", testClass.thelink);
+        assertEquals(null, testClass.nested.link);
+        assertEquals(null, testClass.transientNested.link);
+    }
+
+    public static class TestClassN {
+        // Simulate object injected by JPA
+        // in order to test a fix for JERSEY-2625
+        private transient Iterable res1 = new Iterable() {
+            @Override
+            public Iterator iterator() {
+                throw new RuntimeException("Declarative linking feature is incorrectly processing a transient iterator");
+            }
+
+        };
+    }
+
+    @Test
+    public void testIgnoreTransient() {
+        TestClassN testClass = new TestClassN();
+        FieldProcessor<TestClassN> instance = new FieldProcessor(TestClassN.class);
+        instance.processLinks(testClass, mockUriInfo, mockRmc, mockRlcc);
+    }
+
+}
diff --git a/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/HeaderProcessorTest.java b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/HeaderProcessorTest.java
new file mode 100644
index 0000000..cf360ab
--- /dev/null
+++ b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/HeaderProcessorTest.java
@@ -0,0 +1,349 @@
+/*
+ * 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.linking;
+
+import java.net.URI;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.MatchResult;
+
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.PathSegment;
+import javax.ws.rs.core.UriBuilder;
+
+import org.glassfish.jersey.internal.util.collection.MultivaluedStringMap;
+import org.glassfish.jersey.linking.InjectLink.Extension;
+import org.glassfish.jersey.linking.mapping.ResourceMappingContext;
+import org.glassfish.jersey.server.ExtendedUriInfo;
+import org.glassfish.jersey.server.model.Resource;
+import org.glassfish.jersey.server.model.ResourceMethod;
+import org.glassfish.jersey.server.model.RuntimeResource;
+import org.glassfish.jersey.uri.UriTemplate;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ *
+ *
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+public class HeaderProcessorTest {
+
+    ExtendedUriInfo mockUriInfo = new ExtendedUriInfo() {
+
+        private static final String baseURI = "http://example.com/application/resources";
+
+        public String getPath() {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        public String getPath(boolean decode) {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        public List<PathSegment> getPathSegments() {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        public List<PathSegment> getPathSegments(boolean decode) {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        public URI getRequestUri() {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        public UriBuilder getRequestUriBuilder() {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        public URI getAbsolutePath() {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        public UriBuilder getAbsolutePathBuilder() {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        public URI getBaseUri() {
+            return getBaseUriBuilder().build();
+        }
+
+        public UriBuilder getBaseUriBuilder() {
+            return UriBuilder.fromUri(baseURI);
+        }
+
+        public MultivaluedMap<String, String> getPathParameters() {
+            return new MultivaluedStringMap();
+        }
+
+        public MultivaluedMap<String, String> getPathParameters(boolean decode) {
+            return new MultivaluedStringMap();
+        }
+
+        public MultivaluedMap<String, String> getQueryParameters() {
+            return new MultivaluedStringMap();
+        }
+
+        public MultivaluedMap<String, String> getQueryParameters(boolean decode) {
+            return new MultivaluedStringMap();
+        }
+
+        public List<String> getMatchedURIs() {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        public List<String> getMatchedURIs(boolean decode) {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        public List<Object> getMatchedResources() {
+            Object dummyResource = new Object() {};
+            return Collections.singletonList(dummyResource);
+        }
+
+        @Override
+        public URI resolve(URI uri) {
+            throw new UnsupportedOperationException(
+                    "Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+        }
+
+        @Override
+        public URI relativize(URI uri) {
+            throw new UnsupportedOperationException(
+                    "Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+        }
+
+        @Override
+        public Throwable getMappedThrowable() {
+            throw new UnsupportedOperationException(
+                    "Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+        }
+
+        @Override
+        public List<MatchResult> getMatchedResults() {
+            throw new UnsupportedOperationException(
+                    "Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+        }
+
+        @Override
+        public List<UriTemplate> getMatchedTemplates() {
+            throw new UnsupportedOperationException(
+                    "Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+        }
+
+        @Override
+        public List<PathSegment> getPathSegments(String name) {
+            throw new UnsupportedOperationException(
+                    "Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+        }
+
+        @Override
+        public List<PathSegment> getPathSegments(String name, boolean decode) {
+            throw new UnsupportedOperationException(
+                    "Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+        }
+
+        @Override
+        public List<RuntimeResource> getMatchedRuntimeResources() {
+            throw new UnsupportedOperationException(
+                    "Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+        }
+
+        @Override
+        public ResourceMethod getMatchedResourceMethod() {
+            throw new UnsupportedOperationException(
+                    "Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+        }
+
+        @Override
+        public Resource getMatchedModelResource() {
+            throw new UnsupportedOperationException(
+                    "Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+        }
+
+        @Override
+        public List<ResourceMethod> getMatchedResourceLocators() {
+            throw new UnsupportedOperationException(
+                    "Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+        }
+
+        @Override
+        public List<Resource> getLocatorSubResources() {
+            throw new UnsupportedOperationException(
+                    "Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+        }
+
+    };
+
+    ResourceMappingContext mockRmc = new ResourceMappingContext() {
+
+        @Override
+        public ResourceMappingContext.Mapping getMapping(Class<?> resource) {
+            return null;
+        }
+    };
+
+    @InjectLink(value = "A")
+    public static class EntityA {
+    }
+
+    @Test
+    public void testLiteral() {
+        System.out.println("Literal");
+        HeaderProcessor<EntityA> instance = new HeaderProcessor(EntityA.class);
+        EntityA testClass = new EntityA();
+        List<String> headerValues = instance.getLinkHeaderValues(testClass, mockUriInfo, mockRmc);
+        assertEquals(1, headerValues.size());
+        String headerValue = headerValues.get(0);
+        assertEquals("</application/resources/A>", headerValue);
+    }
+
+    @InjectLink(value = "${entity.id}")
+    public static class EntityB {
+
+        public String getId() {
+            return "B";
+        }
+    }
+
+    @Test
+    public void testEL() {
+        System.out.println("EL");
+        HeaderProcessor<EntityB> instance = new HeaderProcessor(EntityB.class);
+        EntityB testClass = new EntityB();
+        List<String> headerValues = instance.getLinkHeaderValues(testClass, mockUriInfo, mockRmc);
+        assertEquals(1, headerValues.size());
+        String headerValue = headerValues.get(0);
+        assertEquals("</application/resources/B>", headerValue);
+    }
+
+    @InjectLink(value = "{id}")
+    public static class EntityC {
+
+        public String getId() {
+            return "C";
+        }
+    }
+
+    @Test
+    public void testTemplateLiteral() {
+        System.out.println("Template Literal");
+        HeaderProcessor<EntityC> instance = new HeaderProcessor(EntityC.class);
+        EntityC testClass = new EntityC();
+        List<String> headerValues = instance.getLinkHeaderValues(testClass, mockUriInfo, mockRmc);
+        assertEquals(1, headerValues.size());
+        String headerValue = headerValues.get(0);
+        assertEquals("</application/resources/C>", headerValue);
+    }
+
+    @InjectLinks({
+            @InjectLink(value = "A"),
+            @InjectLink(value = "B")
+    })
+    public static class EntityD {
+    }
+
+    @Test
+    public void testMultiple() {
+        System.out.println("Multiple Literal");
+        HeaderProcessor<EntityD> instance = new HeaderProcessor(EntityD.class);
+        EntityD testClass = new EntityD();
+        List<String> headerValues = instance.getLinkHeaderValues(testClass, mockUriInfo, mockRmc);
+        assertEquals(2, headerValues.size());
+        // not sure if annotation order is supposed to be preserved but it seems
+        // to work as expected
+        String headerValue = headerValues.get(0);
+        assertEquals("</application/resources/A>", headerValue);
+        headerValue = headerValues.get(1);
+        assertEquals("</application/resources/B>", headerValue);
+    }
+
+    @InjectLink(value = "E",
+            rel = "relE",
+            rev = "revE",
+            type = "type/e",
+            title = "titleE",
+            anchor = "anchorE",
+            media = "mediaE",
+            hreflang = "en-E",
+            extensions = {
+                    @Extension(name = "e1", value = "v1"),
+                    @Extension(name = "e2", value = "v2")
+            }
+    )
+    public static class EntityE {
+    }
+
+    @Test
+    public void testParameters() {
+        System.out.println("Parameters");
+        HeaderProcessor<EntityE> instance = new HeaderProcessor(EntityE.class);
+        EntityE testClass = new EntityE();
+        List<String> headerValues = instance.getLinkHeaderValues(testClass, mockUriInfo, mockRmc);
+        assertEquals(1, headerValues.size());
+        String headerValue = headerValues.get(0);
+        assertTrue(headerValue.contains("</application/resources/E>"));
+        assertTrue(headerValue.contains("; rel=\"relE\""));
+        assertTrue(headerValue.contains("; rev=\"revE\""));
+        assertTrue(headerValue.contains("; type=\"type/e\""));
+        assertTrue(headerValue.contains("; title=\"titleE\""));
+        assertTrue(headerValue.contains("; anchor=\"anchorE\""));
+        assertTrue(headerValue.contains("; media=\"mediaE\""));
+        assertTrue(headerValue.contains("; hreflang=\"en-E\""));
+        assertTrue(headerValue.contains("; e1=\"v1\""));
+        assertTrue(headerValue.contains("; e2=\"v2\""));
+    }
+
+    @InjectLinks({
+            @InjectLink(value = "${entity.id1}", condition = "${entity.id1Enabled}"),
+            @InjectLink(value = "${entity.id2}", condition = "${entity.id2Enabled}")
+    })
+    public static class EntityF {
+
+        public boolean isId1Enabled() {
+            return true;
+        }
+
+        public String getId1() {
+            return "1";
+        }
+
+        public boolean isId2Enabled() {
+            return false;
+        }
+
+        public String getId2() {
+            return "2";
+        }
+    }
+
+    @Test
+    public void testConditional() {
+        System.out.println("EL");
+        HeaderProcessor<EntityF> instance = new HeaderProcessor(EntityF.class);
+        EntityF testClass = new EntityF();
+        List<String> headerValues = instance.getLinkHeaderValues(testClass, mockUriInfo, mockRmc);
+        assertEquals(1, headerValues.size());
+        String headerValue = headerValues.get(0);
+        assertEquals("</application/resources/1>", headerValue);
+    }
+
+}
diff --git a/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/LinkELContextTest.java b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/LinkELContextTest.java
new file mode 100644
index 0000000..89bf3f4
--- /dev/null
+++ b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/LinkELContextTest.java
@@ -0,0 +1,151 @@
+/*
+ * 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.linking;
+
+import javax.el.ExpressionFactory;
+import javax.el.ValueExpression;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+/**
+ *
+ * @author Mark Hadley
+ * @author Gerard Davison (gerard.davison at oracle.com)
+ */
+public class LinkELContextTest {
+
+    @Test
+    public void testExpressionFactory() {
+        System.out.println("Create expression factory");
+        ExpressionFactory factory = ExpressionFactory.newInstance();
+        assertNotNull(factory);
+    }
+
+    @Test
+    public void testLiteralExpression() {
+        System.out.println("Literal expression");
+        ExpressionFactory factory = ExpressionFactory.newInstance();
+        LinkELContext context = new LinkELContext(new BooleanBean(), null);
+        ValueExpression expr = factory.createValueExpression(context,
+                "${1+2}", int.class);
+        Object value = expr.getValue(context);
+        assertEquals(3, value);
+    }
+
+    public static final String ID = "10";
+    public static final String NAME = "TheName";
+
+    public static class EntityBean {
+
+        private String id = ID;
+        private String name = NAME;
+
+        public String getId() {
+            return id;
+        }
+
+        public String getName() {
+            return name;
+        }
+    }
+
+    @Test
+    public void testExpression() {
+        System.out.println("Raw expression");
+        ExpressionFactory factory = ExpressionFactory.newInstance();
+        LinkELContext context = new LinkELContext(new EntityBean(), null);
+        ValueExpression expr = factory.createValueExpression(context,
+                "${entity.id}", String.class);
+        Object value = expr.getValue(context);
+        assertEquals(ID, value);
+    }
+
+    @Test
+    public void testEmbeddedExpression() {
+        System.out.println("Embedded expression");
+        ExpressionFactory factory = ExpressionFactory.newInstance();
+        LinkELContext context = new LinkELContext(new EntityBean(), null);
+        ValueExpression expr = factory.createValueExpression(context,
+                "foo/${entity.id}/bar", String.class);
+        Object value = expr.getValue(context);
+        assertEquals("foo/" + ID + "/bar", value);
+    }
+
+    @Test
+    public void testMultipleExpressions() {
+        System.out.println("Multiple expressions");
+        ExpressionFactory factory = ExpressionFactory.newInstance();
+        LinkELContext context = new LinkELContext(new EntityBean(), null);
+        ValueExpression expr = factory.createValueExpression(context,
+                "foo/${entity.id}/bar/${entity.name}", String.class);
+        Object value = expr.getValue(context);
+        assertEquals("foo/" + ID + "/bar/" + NAME, value);
+    }
+
+    public static class OuterEntityBean {
+
+        private EntityBean inner = new EntityBean();
+
+        public EntityBean getInner() {
+            return inner;
+        }
+    }
+
+    @Test
+    public void testNestedExpression() {
+        System.out.println("Nested expression");
+        ExpressionFactory factory = ExpressionFactory.newInstance();
+        LinkELContext context = new LinkELContext(new OuterEntityBean(), null);
+        ValueExpression expr = factory.createValueExpression(context,
+                "${entity.inner.id}", String.class);
+        Object value = expr.getValue(context);
+        assertEquals(ID, value);
+    }
+
+    public static class BooleanBean {
+
+        public boolean getEnabled() {
+            return true;
+        }
+
+        public boolean getValue(boolean value) {
+            return value;
+        }
+    }
+
+    @Test
+    public void testBooleanExpression() {
+        System.out.println("Boolean expression");
+        ExpressionFactory factory = ExpressionFactory.newInstance();
+        LinkELContext context = new LinkELContext(new BooleanBean(), null);
+        ValueExpression expr = factory.createValueExpression(context,
+                "${entity.enabled}", boolean.class);
+        Object value = expr.getValue(context);
+        assertEquals(true, value);
+        expr = factory.createValueExpression(context,
+                "${entity.getValue(true)}", boolean.class);
+        value = expr.getValue(context);
+        assertEquals(true, value);
+        expr = factory.createValueExpression(context,
+                "${entity.getValue(false)}", boolean.class);
+        value = expr.getValue(context);
+        assertEquals(false, value);
+    }
+
+}
diff --git a/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/LinkingManualTest.java b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/LinkingManualTest.java
new file mode 100644
index 0000000..6375042
--- /dev/null
+++ b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/LinkingManualTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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.linking.integration;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.linking.integration.app.LinkingManualApplication;
+import org.glassfish.jersey.linking.integration.representations.OrderRequest;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.Test;
+import org.skyscreamer.jsonassert.JSONAssert;
+
+public class LinkingManualTest extends JerseyTest {
+
+    @Override
+    protected Application configure() {
+        return new LinkingManualApplication();
+    }
+
+    @Override
+    protected void configureClient(ClientConfig config) {
+
+    }
+
+    @Test
+    public void orderContainsManualLink() throws Exception {
+        OrderRequest request = new OrderRequest();
+        request.setDrink("Coffee");
+        Response response = target().path("/orders").request()
+                .post(Entity.entity(request, MediaType.APPLICATION_JSON_TYPE));
+
+        String order = response.readEntity(String.class);
+        JSONAssert.assertEquals("{id:'123',price:'1.99',links:["
+                        + "{uri:'/',params:{rel:'root'},uriBuilder:{absolute:false},rel:'root',rels:['root']}"
+                        + "]}",
+                order, true);
+    }
+}
diff --git a/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/LinkingTest.java b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/LinkingTest.java
new file mode 100644
index 0000000..d125faf
--- /dev/null
+++ b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/LinkingTest.java
@@ -0,0 +1,142 @@
+/*
+ * 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.linking.integration;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.linking.integration.app.LinkingApplication;
+import org.glassfish.jersey.linking.integration.representations.OrderRequest;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.Test;
+import org.skyscreamer.jsonassert.JSONAssert;
+
+public class LinkingTest extends JerseyTest {
+
+    @Override
+    protected Application configure() {
+        return new LinkingApplication();
+    }
+
+    @Override
+    protected void configureClient(ClientConfig config) {
+
+    }
+
+    @Test
+    public void orderContainsProvidedLinks() throws Exception {
+        OrderRequest request = new OrderRequest();
+        request.setDrink("Coffee");
+        Response response = target().path("/orders").request()
+                .post(Entity.entity(request, MediaType.APPLICATION_JSON_TYPE));
+
+        String order = response.readEntity(String.class);
+        JSONAssert.assertEquals("{id:'123',price:'1.99',links:["
+                    + "{uri:'/orders/123',params:{rel:'self'},uriBuilder:{absolute:false},rel:'self',rels:['self']},"
+                    + "{uri:'/payments/order/123',params:{rel:'pay'},uriBuilder:{absolute:false},rel:'pay',rels:['pay']},"
+                    + "{uri:'/',params:{rel:'root'},uriBuilder:{absolute:false},rel:'root',rels:['root']}"
+                    + "]}",
+                order, true);
+    }
+
+    @Test
+    public void providedLinksSupportConditions() throws Exception {
+        OrderRequest request = new OrderRequest();
+        request.setDrink("Water");
+        Response response = target().path("/orders").request()
+                .post(Entity.entity(request, MediaType.APPLICATION_JSON_TYPE));
+
+        String order = response.readEntity(String.class);
+        JSONAssert.assertEquals("{id:'123',price:'0.0',links:["
+                    + "{uri:'/orders/123',params:{rel:'self'},uriBuilder:{absolute:false},rel:'self',rels:['self']},"
+                    + "{uri:'/',params:{rel:'root'},uriBuilder:{absolute:false},rel:'root',rels:['root']}"
+                    + "]}",
+                order, true);
+    }
+
+    @Test
+    public void metaAnnotationsCanBeUsedToAbstractCommonBehavior_1() throws Exception {
+        Response response = target().path("/orders").request()
+                .get();
+
+        String order = response.readEntity(String.class);
+        JSONAssert.assertEquals("{number:0,size:2,totalPages:3,numberOfElements:0,totalElements:6,links:["
+                    + "{uri:'/orders',params:{rel:'create'},uriBuilder:{absolute:false},rels:['create'],rel:'create'},"
+                    + "{uri:'/orders?page=1&size=2',params:{rel:'next'},uriBuilder:{absolute:false},rels:['next'],rel:'next'}"
+                    + "],orders:["
+                    + "{id:'1',price:'1.99',links:["
+                    + "{uri:'/orders/1',params:{rel:'self'},uriBuilder:{absolute:false},rels:['self'],rel:'self'},"
+                    + "{uri:'/payments/order/1',params:{rel:'pay'},uriBuilder:{absolute:false},rels:['pay'],rel:'pay'}]},"
+                    + "{id:'2',price:'0.0',links:["
+                    + "{uri:'/orders/2',params:{rel:'self'},uriBuilder:{absolute:false},rels:['self'],rel:'self'}]}"
+                    + "],firstPage:true,previousPageAvailable:false,nextPageAvailable:true,lastPage:false}",
+                order, true);
+    }
+
+    @Test
+    public void metaAnnotationsCanBeUsedToAbstractCommonBehavior_2() throws Exception {
+        Response response = target().path("/orders").queryParam("page", "1").request()
+                .get();
+
+        String order = response.readEntity(String.class);
+        JSONAssert.assertEquals("{number:1,size:2,totalPages:3,numberOfElements:0,totalElements:6,links:["
+                    + "{uri:'/orders',params:{rel:'create'},uriBuilder:{absolute:false},rels:['create'],rel:'create'},"
+                    + "{uri:'/orders?page=2&size=2',params:{rel:'next'},uriBuilder:{absolute:false},rels:['next'],rel:'next'},"
+                    + "{uri:'/orders?page=0&size=2',params:{rel:'prev'},uriBuilder:{absolute:false},rels:['prev'],rel:'prev'}"
+                    + "],orders:["
+                    + "{id:'3',price:'1.99',links:["
+                    + "{uri:'/orders/3',params:{rel:'self'},uriBuilder:{absolute:false},rels:['self'],rel:'self'},"
+                    + "{uri:'/payments/order/3',params:{rel:'pay'},uriBuilder:{absolute:false},rels:['pay'],rel:'pay'}]},"
+                    + "{id:'4',price:'0.0',links:["
+                    + "{uri:'/orders/4',params:{rel:'self'},uriBuilder:{absolute:false},rels:['self'],rel:'self'}]}"
+                    + "],firstPage:false,previousPageAvailable:true,nextPageAvailable:true,lastPage:false}",
+                order, true);
+    }
+
+    @Test
+    public void metaAnnotationsCanBeUsedToAbstractCommonBehavior_3() throws Exception {
+        Response response = target().path("/orders").queryParam("page", "2").request()
+                .get();
+
+        String order = response.readEntity(String.class);
+        JSONAssert.assertEquals("{number:2,size:2,totalPages:3,numberOfElements:0,totalElements:6,links:["
+                    + "{uri:'/orders',params:{rel:'create'},uriBuilder:{absolute:false},rels:['create'],rel:'create'},"
+                    + "{uri:'/orders?page=1&size=2',params:{rel:'prev'},uriBuilder:{absolute:false},rels:['prev'],rel:'prev'}"
+                    + "],orders:["
+                    + "{id:'5',price:'1.99',links:["
+                    + "{uri:'/orders/5',params:{rel:'self'},uriBuilder:{absolute:false},rels:['self'],rel:'self'},"
+                    + "{uri:'/payments/order/5',params:{rel:'pay'},uriBuilder:{absolute:false},rels:['pay'],rel:'pay'}]},"
+                    + "{id:'6',price:'0.0',links:["
+                    + "{uri:'/orders/6',params:{rel:'self'},uriBuilder:{absolute:false},rels:['self'],rel:'self'}]}"
+                    + "],firstPage:false,previousPageAvailable:true,nextPageAvailable:false,lastPage:true}",
+                order, true);
+    }
+
+
+    @Test
+    public void provideCanBeUsedInConjunctionWithInject() throws Exception {
+        Response response = target().path("/payments/p-1").request().get();
+        String order = response.readEntity(String.class);
+        JSONAssert.assertEquals("{id:'p-1',orderId:'1',links:["
+                    + "{uri:'/payments/p-1',params:{rel:'self'},uriBuilder:{absolute:false},rel:'self',rels:['self']},"
+                    + "{uri:'/orders/1',params:{rel:'order'},uriBuilder:{absolute:false},rel:'order',rels:['order']}]}",
+                order, true);
+    }
+}
diff --git a/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/app/LinkingApplication.java b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/app/LinkingApplication.java
new file mode 100644
index 0000000..dc010c2
--- /dev/null
+++ b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/app/LinkingApplication.java
@@ -0,0 +1,35 @@
+/*
+ * 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.linking.integration.app;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.glassfish.jersey.linking.DeclarativeLinkingFeature;
+
+public class LinkingApplication extends LinkingManualApplication {
+
+    private static final Set<Object> singletons = new HashSet<>(Arrays.asList(
+            new DeclarativeLinkingFeature(),
+            new ObjectMapperContextResolver()));
+
+    @Override
+    public Set<Object> getSingletons() {
+        return singletons;
+    }
+}
diff --git a/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/app/LinkingManualApplication.java b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/app/LinkingManualApplication.java
new file mode 100644
index 0000000..b5a498a
--- /dev/null
+++ b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/app/LinkingManualApplication.java
@@ -0,0 +1,38 @@
+/*
+ * 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.linking.integration.app;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import javax.ws.rs.core.Application;
+
+public class LinkingManualApplication extends Application {
+
+    private static final Set<Class<?>> classes = new HashSet<>(Arrays.asList(OrdersResource.class, PaymentResource.class));
+
+    @Override
+    public Set<Class<?>> getClasses() {
+        return classes;
+    }
+
+    @Override
+    public Set<Object> getSingletons() {
+        return Collections.singleton(new ObjectMapperContextResolver());
+    }
+}
diff --git a/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/app/ObjectMapperContextResolver.java b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/app/ObjectMapperContextResolver.java
new file mode 100644
index 0000000..d12e369
--- /dev/null
+++ b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/app/ObjectMapperContextResolver.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.linking.integration.app;
+
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.ext.ContextResolver;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {
+    @Context
+    private HttpHeaders headers;
+
+    private final ObjectMapper mapper;
+
+    public ObjectMapperContextResolver() {
+        mapper = new ObjectMapper();
+
+        // serialization behaviour
+        mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
+    }
+
+    @Override
+    public ObjectMapper getContext(Class<?> type) {
+        return mapper;
+    }
+}
diff --git a/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/app/OrdersResource.java b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/app/OrdersResource.java
new file mode 100644
index 0000000..27d559d
--- /dev/null
+++ b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/app/OrdersResource.java
@@ -0,0 +1,124 @@
+/*
+ * 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.linking.integration.app;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+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.Link;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+
+import org.glassfish.jersey.linking.Binding;
+import org.glassfish.jersey.linking.ProvideLink;
+import org.glassfish.jersey.linking.integration.representations.ExtendedOrder;
+import org.glassfish.jersey.linking.integration.representations.Order;
+import org.glassfish.jersey.linking.integration.representations.OrderPage;
+import org.glassfish.jersey.linking.integration.representations.OrderRequest;
+import org.glassfish.jersey.linking.integration.representations.PageLinks;
+import org.glassfish.jersey.linking.integration.representations.PaymentConfirmation;
+
+
+@Path("/orders")
+public class OrdersResource {
+
+    @Context
+    private UriInfo uriInfo;
+
+
+    @ProvideLink(value = OrderPage.class, rel = "create")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    @POST
+    public Response create(OrderRequest request) {
+        Order order = new Order();
+        order.setId("123");
+        if ("water".equalsIgnoreCase(request.getDrink())) {
+            order.setPrice("0.0");
+        } else {
+            order.setPrice("1.99");
+        }
+
+        order.getLinks().add(Link.fromUri("/").rel("root").build());
+        return Response.ok(order).build();
+    }
+
+    @ProvideLink(value = Order.class, rel = "self", bindings = @Binding(name = "orderId", value = "${instance.id}"))
+    @ProvideLink(value = PaymentConfirmation.class, rel = "order",
+                 bindings = @Binding(name = "orderId", value = "${instance.orderId}"))
+    @Produces(MediaType.APPLICATION_JSON)
+    @GET
+    @Path("/{orderId}")
+    public Response get(@PathParam("orderId") String orderId) {
+        ExtendedOrder order = new ExtendedOrder();
+        order.setId("123");
+        order.setPrice("1.99");
+        return Response.ok(order).build();
+    }
+
+
+    @ProvideLink(value = ExtendedOrder.class, rel = "delete",
+                 bindings = @Binding(name = "orderId", value = "${instance.id}"))
+    @DELETE
+    @Path("/{orderId}")
+    public Response delete(@PathParam("orderId") String orderId) {
+        return Response.noContent().build();
+    }
+
+
+    @PageLinks(OrderPage.class)
+    @Produces(MediaType.APPLICATION_JSON)
+    @GET
+    public Response list(@QueryParam("page") @DefaultValue("0") int page, @QueryParam("size") @DefaultValue("2")  int size) {
+        OrderPage orderPage = new OrderPage();
+
+        orderPage.setFirstPage(page == 0);
+        orderPage.setLastPage(page == 2);
+        orderPage.setPreviousPageAvailable(page > 0);
+        orderPage.setNextPageAvailable(page < 2);
+        orderPage.setNumber(page);
+        orderPage.setSize(size);
+        orderPage.setTotalElements(6);
+        orderPage.setTotalPages(3);
+
+        orderPage.setOrders(generateOrders(page, size));
+
+        return Response.ok(orderPage).build();
+    }
+
+    private List<Order> generateOrders(int page, int size) {
+        final int base = page * size;
+        return IntStream.range(1, size + 1).map(x -> x + base).mapToObj(id -> {
+            Order order = new Order();
+            order.setId(Integer.toString(id));
+            order.setPrice(((id & 1) == 1) ? "1.99" : "0.0");
+            return order;
+        }).collect(Collectors.toList());
+    }
+}
diff --git a/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/app/PaymentResource.java b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/app/PaymentResource.java
new file mode 100644
index 0000000..a8d33a5
--- /dev/null
+++ b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/app/PaymentResource.java
@@ -0,0 +1,61 @@
+/*
+ * 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.linking.integration.app;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.linking.Binding;
+import org.glassfish.jersey.linking.ProvideLink;
+import org.glassfish.jersey.linking.integration.representations.Order;
+import org.glassfish.jersey.linking.integration.representations.PaymentConfirmation;
+import org.glassfish.jersey.linking.integration.representations.PaymentDetails;
+
+
+@Path("/payments")
+public class PaymentResource {
+
+
+    @ProvideLink(value = Order.class, rel = "pay", bindings = {
+            @Binding(name = "orderId", value = "${instance.id}")}, condition = "${instance.price != '0.0'}")
+    @Produces(MediaType.APPLICATION_JSON)
+    @Consumes(MediaType.APPLICATION_JSON)
+    @PUT
+    @Path("/order/{orderId}")
+    public Response pay(@PathParam("orderId") String orderId, PaymentDetails paymentDetails) {
+        PaymentConfirmation paymentConfirmation = new PaymentConfirmation();
+        paymentConfirmation.setOrderId(orderId);
+        paymentConfirmation.setId("p-" + orderId);
+        return Response.ok(paymentConfirmation).build();
+    }
+
+    @Produces(MediaType.APPLICATION_JSON)
+    @GET
+    @Path("{id}")
+    public Response getConfirmation(@PathParam("id") String id) {
+        PaymentConfirmation paymentConfirmation = new PaymentConfirmation();
+        paymentConfirmation.setId(id);
+        paymentConfirmation.setOrderId(id.substring(2));
+        return Response.ok(paymentConfirmation).build();
+    }
+}
diff --git a/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/representations/ExtendedOrder.java b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/representations/ExtendedOrder.java
new file mode 100644
index 0000000..934f18f
--- /dev/null
+++ b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/representations/ExtendedOrder.java
@@ -0,0 +1,30 @@
+/*
+ * 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.linking.integration.representations;
+
+public class ExtendedOrder extends Order {
+
+    private String details;
+
+    public String getDetails() {
+        return details;
+    }
+
+    public void setDetails(String details) {
+        this.details = details;
+    }
+}
diff --git a/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/representations/Order.java b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/representations/Order.java
new file mode 100644
index 0000000..4daf27d
--- /dev/null
+++ b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/representations/Order.java
@@ -0,0 +1,67 @@
+/*
+ * 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.linking.integration.representations;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.ws.rs.core.Link;
+
+import org.glassfish.jersey.linking.InjectLinks;
+
+public class Order {
+
+    private String id;
+
+    private String drink;
+
+    private String price;
+
+    @InjectLinks
+    private List<Link> links = new ArrayList<>();
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getDrink() {
+        return drink;
+    }
+
+    public void setDrink(String drink) {
+        this.drink = drink;
+    }
+
+    public String getPrice() {
+        return price;
+    }
+
+    public void setPrice(String price) {
+        this.price = price;
+    }
+
+    public List<Link> getLinks() {
+        return links;
+    }
+
+    public void setLinks(List<Link> links) {
+        this.links = links;
+    }
+}
diff --git a/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/representations/OrderPage.java b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/representations/OrderPage.java
new file mode 100644
index 0000000..3aef37c
--- /dev/null
+++ b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/representations/OrderPage.java
@@ -0,0 +1,33 @@
+/*
+ * 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.linking.integration.representations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class OrderPage extends Page {
+
+    private List<Order> orders = new ArrayList<>();
+
+    public List<Order> getOrders() {
+        return orders;
+    }
+
+    public void setOrders(List<Order> orders) {
+        this.orders = orders;
+    }
+}
diff --git a/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/representations/OrderRequest.java b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/representations/OrderRequest.java
new file mode 100644
index 0000000..4af0a9d
--- /dev/null
+++ b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/representations/OrderRequest.java
@@ -0,0 +1,29 @@
+/*
+ * 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.linking.integration.representations;
+
+public class OrderRequest {
+    private String drink;
+
+    public String getDrink() {
+        return drink;
+    }
+
+    public void setDrink(String drink) {
+        this.drink = drink;
+    }
+}
diff --git a/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/representations/Page.java b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/representations/Page.java
new file mode 100644
index 0000000..8e1f1ba
--- /dev/null
+++ b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/representations/Page.java
@@ -0,0 +1,154 @@
+/*
+ * 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.linking.integration.representations;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.ws.rs.core.Link;
+
+import org.glassfish.jersey.linking.InjectLinks;
+
+public class Page {
+
+    /**
+     * The number of the current page. Is always non-negative and less that {@code Page#getTotalPages()}.
+     */
+    private int number;
+
+    /**
+     * The size of the page.
+     */
+    private int size;
+
+    /**
+     * The number of total pages.
+     */
+    private int totalPages;
+
+    /**
+     * The number of elements currently on this page.
+     */
+    private int numberOfElements;
+
+    /**
+     * The total amount of elements.
+     */
+    private long totalElements;
+
+    /**
+     * If there is a previous page.
+     */
+    private boolean isPreviousPageAvailable;
+
+    /**
+     * Whether the current page is the first one.
+     */
+    private boolean isFirstPage;
+
+    /**
+     * If there is a next page.
+     */
+    private boolean isNextPageAvailable;
+
+    /**
+     * Whether the current page is the last one.
+     */
+    private boolean isLastPage;
+
+    @InjectLinks
+    private List<Link> links = new ArrayList<>();
+
+    public int getNumber() {
+        return number;
+    }
+
+    public void setNumber(int number) {
+        this.number = number;
+    }
+
+    public int getSize() {
+        return size;
+    }
+
+    public void setSize(int size) {
+        this.size = size;
+    }
+
+    public int getTotalPages() {
+        return totalPages;
+    }
+
+    public void setTotalPages(int totalPages) {
+        this.totalPages = totalPages;
+    }
+
+    public int getNumberOfElements() {
+        return numberOfElements;
+    }
+
+    public void setNumberOfElements(int numberOfElements) {
+        this.numberOfElements = numberOfElements;
+    }
+
+    public long getTotalElements() {
+        return totalElements;
+    }
+
+    public void setTotalElements(long totalElements) {
+        this.totalElements = totalElements;
+    }
+
+    public boolean isPreviousPageAvailable() {
+        return isPreviousPageAvailable;
+    }
+
+    public void setPreviousPageAvailable(boolean previousPageAvailable) {
+        isPreviousPageAvailable = previousPageAvailable;
+    }
+
+    public boolean isFirstPage() {
+        return isFirstPage;
+    }
+
+    public void setFirstPage(boolean firstPage) {
+        isFirstPage = firstPage;
+    }
+
+    public boolean isNextPageAvailable() {
+        return isNextPageAvailable;
+    }
+
+    public void setNextPageAvailable(boolean nextPageAvailable) {
+        isNextPageAvailable = nextPageAvailable;
+    }
+
+    public boolean isLastPage() {
+        return isLastPage;
+    }
+
+    public void setLastPage(boolean lastPage) {
+        isLastPage = lastPage;
+    }
+
+    public List<Link> getLinks() {
+        return links;
+    }
+
+    public void setLinks(List<Link> links) {
+        this.links = links;
+    }
+}
diff --git a/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/representations/PageLinks.java b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/representations/PageLinks.java
new file mode 100644
index 0000000..421ba52
--- /dev/null
+++ b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/representations/PageLinks.java
@@ -0,0 +1,41 @@
+/*
+ * 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.linking.integration.representations;
+
+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;
+
+import org.glassfish.jersey.linking.Binding;
+import org.glassfish.jersey.linking.ProvideLink;
+
+@ProvideLink(value = ProvideLink.InheritFromAnnotation.class, rel = "next", bindings = {
+        @Binding(name = "page", value = "${instance.number + 1}"),
+        @Binding(name = "size", value = "${instance.size}"),
+}, condition = "${instance.nextPageAvailable}")
+@ProvideLink(value = ProvideLink.InheritFromAnnotation.class, rel = "prev", bindings = {
+        @Binding(name = "page", value = "${instance.number - 1}"),
+        @Binding(name = "size", value = "${instance.size}"),
+}, condition = "${instance.previousPageAvailable}")
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface PageLinks {
+    Class<?> value();
+}
diff --git a/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/representations/PaymentConfirmation.java b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/representations/PaymentConfirmation.java
new file mode 100644
index 0000000..869dd90
--- /dev/null
+++ b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/representations/PaymentConfirmation.java
@@ -0,0 +1,61 @@
+/*
+ * 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.linking.integration.representations;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.ws.rs.core.Link;
+
+import org.glassfish.jersey.linking.InjectLink;
+import org.glassfish.jersey.linking.InjectLinks;
+import org.glassfish.jersey.linking.integration.app.PaymentResource;
+
+public class PaymentConfirmation {
+
+    private String id;
+
+    private String orderId;
+
+    @InjectLinks(
+            @InjectLink(resource = PaymentResource.class, method = "getConfirmation", rel = "self")
+    )
+    private List<Link> links = new ArrayList<>();
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getOrderId() {
+        return orderId;
+    }
+
+    public void setOrderId(String orderId) {
+        this.orderId = orderId;
+    }
+
+    public List<Link> getLinks() {
+        return links;
+    }
+
+    public void setLinks(List<Link> links) {
+        this.links = links;
+    }
+}
diff --git a/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/representations/PaymentDetails.java b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/representations/PaymentDetails.java
new file mode 100644
index 0000000..284f204
--- /dev/null
+++ b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/integration/representations/PaymentDetails.java
@@ -0,0 +1,56 @@
+/*
+ * 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.linking.integration.representations;
+
+public class PaymentDetails {
+    private String creditCardNumber;
+    private String cve;
+    private String name;
+    private String expiration;
+
+    public String getCreditCardNumber() {
+        return creditCardNumber;
+    }
+
+    public void setCreditCardNumber(String creditCardNumber) {
+        this.creditCardNumber = creditCardNumber;
+    }
+
+    public String getCve() {
+        return cve;
+    }
+
+    public void setCve(String cve) {
+        this.cve = cve;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getExpiration() {
+        return expiration;
+    }
+
+    public void setExpiration(String expiration) {
+        this.expiration = expiration;
+    }
+}
diff --git a/incubator/gae-integration/pom.xml b/incubator/gae-integration/pom.xml
new file mode 100644
index 0000000..f692c90
--- /dev/null
+++ b/incubator/gae-integration/pom.xml
@@ -0,0 +1,52 @@
+<?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.incubator</groupId>
+        <artifactId>project</artifactId>
+        <version>2.28-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>jersey-gae-integration</artifactId>
+    <name>jersey-ext-gae-integration</name>
+
+    <description>
+        Jersey extension module providing support for Google App Engine integration.
+        NOTE: IT DOES NOT WORK YET BECAUSE OF GAE SERVLET 2.5 (see https://java.net/jira/browse/JERSEY-1853)!!!
+    </description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.glassfish.jersey.core</groupId>
+            <artifactId>jersey-server</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.google.appengine</groupId>
+            <artifactId>appengine-api-1.0-sdk</artifactId>
+            <version>${gae.version}</version>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/incubator/gae-integration/src/main/java/org/glassfish/jersey/server/gae/GaeBackgroundExecutorProvider.java b/incubator/gae-integration/src/main/java/org/glassfish/jersey/server/gae/GaeBackgroundExecutorProvider.java
new file mode 100644
index 0000000..0338725
--- /dev/null
+++ b/incubator/gae-integration/src/main/java/org/glassfish/jersey/server/gae/GaeBackgroundExecutorProvider.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.server.gae;
+
+import java.util.concurrent.ThreadFactory;
+
+import org.glassfish.jersey.server.BackgroundScheduler;
+import org.glassfish.jersey.spi.ScheduledThreadPoolExecutorProvider;
+
+/**
+ * This class implements Jersey's SPI {@link org.glassfish.jersey.spi.ScheduledExecutorServiceProvider} to provide a
+ * {@link java.util.concurrent.ScheduledExecutorService} instances with a GAE specific {@link ThreadFactory} provider
+ * - {@link com.google.appengine.api.ThreadManager}.
+ *
+ * @author Libor Kramolis (libor.kramolis at oracle.com)
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+@BackgroundScheduler
+class GaeBackgroundExecutorProvider extends ScheduledThreadPoolExecutorProvider {
+
+    /**
+     * Create new instance of GAE-specific background scheduled executor service provider.
+     */
+    public GaeBackgroundExecutorProvider() {
+        super("gae-jersey-background-task-scheduler");
+    }
+
+    @Override
+    public ThreadFactory getBackingThreadFactory() {
+        return com.google.appengine.api.ThreadManager.backgroundThreadFactory();
+    }
+
+    @Override
+    protected int getCorePoolSize() {
+        return 1;
+    }
+}
diff --git a/incubator/gae-integration/src/main/java/org/glassfish/jersey/server/gae/GaeFeature.java b/incubator/gae-integration/src/main/java/org/glassfish/jersey/server/gae/GaeFeature.java
new file mode 100644
index 0000000..cc99f84
--- /dev/null
+++ b/incubator/gae-integration/src/main/java/org/glassfish/jersey/server/gae/GaeFeature.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.gae;
+
+import javax.ws.rs.ConstrainedTo;
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.core.Feature;
+import javax.ws.rs.core.FeatureContext;
+
+/**
+ * Google App Engine integration for Jersey server runtime.
+ * <p>
+ * Register this feature in your {@link org.glassfish.jersey.server.ResourceConfig} subclass in order to be able to run
+ * your Jersey application on Google App Engine.
+ * </p>
+ *
+ * @author Libor Kramolis (libor.kramolis at oracle.com)
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+@ConstrainedTo(RuntimeType.SERVER)
+public final class GaeFeature implements Feature {
+
+    @Override
+    public boolean configure(final FeatureContext context) {
+        if (!context.getConfiguration().isRegistered(GaeBackgroundExecutorProvider.class)) {
+            context.register(new GaeBackgroundExecutorProvider());
+        }
+        return true;
+    }
+
+}
diff --git a/incubator/gae-integration/src/main/java/org/glassfish/jersey/server/gae/package-info.java b/incubator/gae-integration/src/main/java/org/glassfish/jersey/server/gae/package-info.java
new file mode 100644
index 0000000..6143916
--- /dev/null
+++ b/incubator/gae-integration/src/main/java/org/glassfish/jersey/server/gae/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+/**
+ * Public API of Google App Engine integration support.
+ *
+ * Register {@link org.glassfish.jersey.server.gae.GaeFeature} in your {@link org.glassfish.jersey.server.ResourceConfig}
+ * subclass to enable the support.
+ */
+package org.glassfish.jersey.server.gae;
diff --git a/incubator/html-json/pom.xml b/incubator/html-json/pom.xml
new file mode 100644
index 0000000..882bb46
--- /dev/null
+++ b/incubator/html-json/pom.xml
@@ -0,0 +1,117 @@
+<?xml version="1.0"?>
+<!--
+
+    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/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.glassfish.jersey.incubator</groupId>
+        <artifactId>project</artifactId>
+        <version>2.26-SNAPSHOT</version>
+    </parent>
+
+    <groupId>org.glassfish.jersey.media</groupId>
+    <artifactId>html-json</artifactId>
+
+    <name>jersey-media-html-json</name>
+
+    <description>
+        JAX-RS Reader/Writer via net.java.html.json library that provides
+        JSON serialization and deserialization using lightweight (no reflection)
+        net.java.html.json library. Define your objects via @Model annotation.
+        Use them in Jersey.
+    </description>
+
+    <properties>
+        <net.java.html.version>1.0</net.java.html.version>
+    </properties>
+
+    <repositories>
+        <repository>
+            <id>netbeans</id>
+            <name>NetBeans</name>
+            <url>http://bits.netbeans.org/maven2/</url>
+        </repository>
+    </repositories>
+
+    <dependencies>
+        <dependency>
+            <groupId>javax.ws.rs</groupId>
+            <artifactId>javax.ws.rs-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.netbeans.html</groupId>
+            <artifactId>net.java.html.json</artifactId>
+            <version>${net.java.html.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.netbeans.api</groupId>
+            <artifactId>org-openide-util-lookup</artifactId>
+            <version>RELEASE80</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.jersey.core</groupId>
+            <artifactId>jersey-client</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>javax.annotation</groupId>
+            <artifactId>javax.annotation-api</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</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.glassfish.jersey.connectors</groupId>
+            <artifactId>jersey-apache-connector</artifactId>
+            <version>${project.version}</version>
+            <scope>test</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>
+        <dependency>
+            <groupId>org.netbeans.html</groupId>
+            <artifactId>ko-ws-tyrus</artifactId>
+            <version>${net.java.html.version}</version>
+            <scope>runtime</scope>
+        </dependency>
+        <!-- Overwrite Grizzly dependency defined in ko-ws-tyrus -->
+        <dependency>
+            <groupId>org.glassfish.grizzly</groupId>
+            <artifactId>grizzly-framework</artifactId>
+            <version>${grizzly2.version}</version>
+            <scope>runtime</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/incubator/html-json/src/main/java/org/glassfish/jersey/media/htmljson/HtmlJsonProvider.java b/incubator/html-json/src/main/java/org/glassfish/jersey/media/htmljson/HtmlJsonProvider.java
new file mode 100644
index 0000000..b220eda
--- /dev/null
+++ b/incubator/html-json/src/main/java/org/glassfish/jersey/media/htmljson/HtmlJsonProvider.java
@@ -0,0 +1,162 @@
+/*
+ * 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.media.htmljson;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Array;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyReader;
+import javax.ws.rs.ext.MessageBodyWriter;
+
+import org.openide.util.lookup.ServiceProvider;
+import org.openide.util.lookup.ServiceProviders;
+
+import net.java.html.BrwsrCtx;
+import net.java.html.json.Model;
+import net.java.html.json.Models;
+import net.java.html.json.Property;
+
+/**
+ * Implementation of Jersey's message body reader and writer that
+ * can handle reading and writing of JSON models generated by {@link Model}
+ * annotation provided by
+ * <a target="_blank" href="http://bck2brwsr.apidesign.org/javadoc/net.java.html.json/">net.java.html.json</a>
+ * library. Include
+ * this JAR in your project and you can then use your
+ * model classes as Jersey's entities.
+ * <p>
+ * <pre>
+ * {@link Model @Model}(className="Query", properties={
+ *   {@link Property @Property}(name="items", type=Item.<b>class</b>, array=true)
+ * })
+ * <b>class</b> QueryImpl {
+ *
+ *   {@link Model @Model}(className="Item", properties={
+ *     {@link Property @Property}(name="id", type=String.<b>class</b>),
+ *     {@link Property @Property}(name="kind", type=Kind.<b>class</b>)
+ *   })
+ *   <b>class</b> ItemImpl {
+ *   }
+ *
+ *   <b>enum</b> Kind {
+ *     GOOD, BAD
+ *   }
+ *
+ *   <b>public static</b> List{@code <Item>} doQuery() {
+ *     {@link WebTarget} target = ...;
+ *     Query q = target.request(MediaType.APPLICATION_JSON).get().readEntity(Query.<b>class</b>);
+ *     return q.getItems();
+ *   }
+ * }
+ * </pre>
+ *
+ * @author Jaroslav Tulach (jtulach at netbeans.org)
+ */
+@ServiceProviders({
+        @ServiceProvider(service = MessageBodyWriter.class),
+        @ServiceProvider(service = MessageBodyReader.class)
+})
+public final class HtmlJsonProvider implements MessageBodyWriter<Object>, MessageBodyReader<Object> {
+
+    @Override
+    public boolean isWriteable(Class clazz, Type type, Annotation[] antns, MediaType mt) {
+        if (!mt.isCompatible(MediaType.APPLICATION_JSON_TYPE)) {
+            return false;
+        }
+        if (clazz.isArray()) {
+            return Models.isModel(clazz.getComponentType());
+        }
+        if (java.util.List.class.isAssignableFrom(clazz)) {
+            if (type instanceof ParameterizedType) {
+                ParameterizedType pt = (ParameterizedType) type;
+                Type[] args = pt.getActualTypeArguments();
+                if (args.length == 1 && args[0] instanceof Class) {
+                    return Models.isModel((Class<?>) args[0]);
+                }
+            }
+        }
+        return Models.isModel(clazz);
+    }
+
+    @Override
+    public long getSize(Object t, Class type, Type type1, Annotation[] antns, MediaType mt) {
+        return -1;
+    }
+
+    @Override
+    public void writeTo(Object t, Class type, Type type1, Annotation[] antns, MediaType mt, MultivaluedMap mm, OutputStream out)
+            throws IOException, WebApplicationException {
+        dump(t, out);
+    }
+
+    private void dump(Object t, OutputStream out) throws IOException {
+        if (t instanceof Object[]) {
+            Object[] arr = (Object[]) t;
+            out.write('[');
+            for (int i = 0; i < arr.length; i++) {
+                if (i > 0) {
+                    out.write(',');
+                }
+                dump(arr[i], out);
+            }
+            out.write(']');
+        } else {
+            out.write(t.toString().getBytes("UTF-8"));
+        }
+    }
+
+    @Override
+    public boolean isReadable(Class<?> type, Type type1, Annotation[] antns, MediaType mt) {
+        return isWriteable(type, type1, antns, mt);
+    }
+
+    @Override
+    public Object readFrom(Class<Object> clazz,
+                           Type type, Annotation[] antns, MediaType mt,
+                           MultivaluedMap<String, String> mm,
+                           InputStream in) throws IOException, WebApplicationException {
+        BrwsrCtx def = BrwsrCtx.findDefault(HtmlJsonProvider.class);
+        if (clazz.isArray()) {
+            List<Object> res = new ArrayList<>();
+            final Class<?> cmp = clazz.getComponentType();
+            Models.parse(def, cmp, in, res);
+            Object[] arr = (Object[]) Array.newInstance(cmp, res.size());
+            return res.toArray(arr);
+        }
+        if (clazz.isAssignableFrom(java.util.List.class)
+                && type instanceof ParameterizedType
+                && ((ParameterizedType) type).getActualTypeArguments().length == 1
+                && ((ParameterizedType) type).getActualTypeArguments()[0] instanceof Class) {
+            List<Object> res = new ArrayList<>();
+            final Class<?> cmp = (Class<?>) ((ParameterizedType) type).getActualTypeArguments()[0];
+            Models.parse(def, cmp, in, res);
+            return res;
+        }
+        return Models.parse(def, clazz, in);
+    }
+}
diff --git a/incubator/html-json/src/main/java/org/glassfish/jersey/media/htmljson/internal/HtmlJsonAutoDiscoverable.java b/incubator/html-json/src/main/java/org/glassfish/jersey/media/htmljson/internal/HtmlJsonAutoDiscoverable.java
new file mode 100644
index 0000000..7b3b56a
--- /dev/null
+++ b/incubator/html-json/src/main/java/org/glassfish/jersey/media/htmljson/internal/HtmlJsonAutoDiscoverable.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.media.htmljson.internal;
+
+import javax.annotation.Priority;
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.FeatureContext;
+
+import org.glassfish.jersey.internal.spi.AutoDiscoverable;
+import org.glassfish.jersey.media.htmljson.HtmlJsonProvider;
+
+/**
+ * @author Michal Gajdos
+ */
+@Priority(AutoDiscoverable.DEFAULT_PRIORITY)
+public class HtmlJsonAutoDiscoverable implements AutoDiscoverable {
+
+    @Override
+    public void configure(final FeatureContext context) {
+        final Configuration config = context.getConfiguration();
+
+        if (!config.isRegistered(HtmlJsonProvider.class)) {
+            context.register(HtmlJsonProvider.class);
+        }
+    }
+}
diff --git a/incubator/html-json/src/main/java/org/glassfish/jersey/media/htmljson/package-info.java b/incubator/html-json/src/main/java/org/glassfish/jersey/media/htmljson/package-info.java
new file mode 100644
index 0000000..5a8cf74
--- /dev/null
+++ b/incubator/html-json/src/main/java/org/glassfish/jersey/media/htmljson/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 package with entity provider that allows usage of {@code net.java.html.json}.
+ */
+package org.glassfish.jersey.media.htmljson;
diff --git a/incubator/html-json/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.AutoDiscoverable b/incubator/html-json/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.AutoDiscoverable
new file mode 100644
index 0000000..4709b74
--- /dev/null
+++ b/incubator/html-json/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.AutoDiscoverable
@@ -0,0 +1 @@
+org.glassfish.jersey.media.htmljson.internal.HtmlJsonAutoDiscoverable
\ No newline at end of file
diff --git a/incubator/html-json/src/test/java/org/glassfish/jersey/media/htmljson/AbstractTypeTester.java b/incubator/html-json/src/test/java/org/glassfish/jersey/media/htmljson/AbstractTypeTester.java
new file mode 100644
index 0000000..5213a80
--- /dev/null
+++ b/incubator/html-json/src/test/java/org/glassfish/jersey/media/htmljson/AbstractTypeTester.java
@@ -0,0 +1,162 @@
+/*
+ * 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.media.htmljson;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.HashSet;
+
+import javax.ws.rs.ConstrainedTo;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.Provider;
+import javax.ws.rs.ext.WriterInterceptor;
+import javax.ws.rs.ext.WriterInterceptorContext;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.message.internal.ReaderWriter;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Abstract entity type tester base class.
+ *
+ * @author Paul Sandoz
+ * @author Martin Matula
+ * @author Marek Potociar (marek.potociar at oracle.com)
+ */
+public abstract class AbstractTypeTester extends JerseyTest {
+
+    protected static byte[] requestEntity;
+
+    public abstract static class AResource<T> {
+
+        @POST
+        public T post(T t) {
+            return t;
+        }
+    }
+
+    public static class RequestEntityInterceptor implements WriterInterceptor {
+
+        @Override
+        public void aroundWriteTo(WriterInterceptorContext writerInterceptorContext) throws IOException, WebApplicationException {
+            OutputStream original = writerInterceptorContext.getOutputStream();
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            writerInterceptorContext.setOutputStream(baos);
+            writerInterceptorContext.proceed();
+            requestEntity = baos.toByteArray();
+            original.write(requestEntity);
+        }
+    }
+
+    /**
+     * Looks for all resources and providers declared as inner classes of the subclass of this class
+     * and adds them to the returned ResourceConfig (unless constrained to client side).
+     *
+     * @return ResourceConfig instance
+     */
+    @Override
+    protected Application configure() {
+        HashSet<Class<?>> classes = new HashSet<Class<?>>();
+
+        for (Class<?> cls : getClass().getDeclaredClasses()) {
+            if (cls.getAnnotation(Path.class) != null) {
+                classes.add(cls);
+            } else if (cls.getAnnotation(Provider.class) != null) {
+                final ConstrainedTo constrainedTo = cls.getAnnotation(ConstrainedTo.class);
+                if (constrainedTo == null || constrainedTo.value() == RuntimeType.SERVER) {
+                    classes.add(cls);
+                }
+            }
+        }
+
+        return new ResourceConfig(classes);
+    }
+
+    /**
+     * Looks for all providers declared as inner classes of the subclass of this class
+     * and adds them to the client configuration (unless constrained to server side).
+     */
+    @Override
+    protected void configureClient(ClientConfig config) {
+        config.register(RequestEntityInterceptor.class);
+
+        for (Class<?> cls : getClass().getDeclaredClasses()) {
+            if (cls.getAnnotation(Provider.class) != null) {
+                final ConstrainedTo constrainedTo = cls.getAnnotation(ConstrainedTo.class);
+                if (constrainedTo == null || constrainedTo.value() == RuntimeType.CLIENT) {
+                    config.register(cls);
+                }
+            }
+        }
+    }
+
+    protected <T> void _test(T in, Class resource) {
+        _test(in, resource, true);
+    }
+
+    protected <T> void _test(T in, Class resource, MediaType m) {
+        _test(in, resource, m, true);
+    }
+
+    protected <T> void _test(T in, Class resource, boolean verify) {
+        _test(in, resource, MediaType.TEXT_PLAIN_TYPE, verify);
+    }
+
+    protected <T> void _test(T in, Class resource, MediaType m, boolean verify) {
+        WebTarget target = target(resource.getSimpleName());
+        Response response = target.request().post(Entity.entity(in, m));
+
+        byte[] inBytes = requestEntity;
+        byte[] outBytes = getEntityAsByteArray(response);
+
+        if (verify) {
+            _verify(inBytes, outBytes);
+        }
+    }
+
+    protected static void _verify(byte[] in, byte[] out) {
+        assertEquals(in.length, out.length);
+        for (int i = 0; i < in.length; i++) {
+            if (in[i] != out[i]) {
+                assertEquals("Index: " + i, in[i], out[i]);
+            }
+        }
+    }
+
+    protected static byte[] getEntityAsByteArray(Response r) {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        try {
+            ReaderWriter.writeTo(r.readEntity(InputStream.class), baos);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return baos.toByteArray();
+    }
+}
diff --git a/incubator/html-json/src/test/java/org/glassfish/jersey/media/htmljson/ComputedPropertyTest.java b/incubator/html-json/src/test/java/org/glassfish/jersey/media/htmljson/ComputedPropertyTest.java
new file mode 100644
index 0000000..eb5a839
--- /dev/null
+++ b/incubator/html-json/src/test/java/org/glassfish/jersey/media/htmljson/ComputedPropertyTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.media.htmljson;
+
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+import net.java.html.json.ComputedProperty;
+import net.java.html.json.Model;
+import net.java.html.json.Property;
+
+/** Can use the same code on server as well as client.
+ *
+ * @author Jaroslav Tulach
+ */
+@Model(className = "Person", properties = {
+        @Property(name = "firstName", type = String.class),
+        @Property(name = "lastName", type = String.class)
+})
+public class ComputedPropertyTest extends AbstractTypeTester {
+
+    @ComputedProperty
+    static String fullName(String firstName, String lastName) {
+        return firstName + " " + lastName;
+    }
+
+    //
+    // server side
+    //
+    @Path("empty")
+    public static class TestResource {
+
+        @PUT
+        @Path("fullName")
+        public String myBean(Person p) {
+            return p.getFullName();
+        }
+    }
+
+    //
+    // Client using the model to connect to server
+    //
+
+    @Test
+    public void askForFullName() {
+        WebTarget target = target("empty/fullName");
+
+        Person p = new Person();
+        p.setFirstName("Jaroslav");
+        p.setLastName("Tulach");
+
+        final Response response = target.request().put(Entity.entity(p, MediaType.APPLICATION_JSON_TYPE));
+
+        assertEquals(200, response.getStatus());
+        assertEquals("Jaroslav Tulach", response.readEntity(String.class));
+    }
+}
diff --git a/incubator/html-json/src/test/java/org/glassfish/jersey/media/htmljson/ModelEntityOnArrayTest.java b/incubator/html-json/src/test/java/org/glassfish/jersey/media/htmljson/ModelEntityOnArrayTest.java
new file mode 100644
index 0000000..a1e01c7
--- /dev/null
+++ b/incubator/html-json/src/test/java/org/glassfish/jersey/media/htmljson/ModelEntityOnArrayTest.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.media.htmljson;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.test.TestProperties;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import net.java.html.json.Model;
+
+/**
+ * Reading and writing class generated by {@link Model} as
+ * arrays.
+ *
+ * @author Jaroslav Tulach
+ */
+public class ModelEntityOnArrayTest extends AbstractTypeTester {
+
+    @Path("empty")
+    public static class TestResource {
+
+        @POST
+        @Path("mybean")
+        public String myBean(MyBean[] bean) {
+            if (bean.length != 2) {
+                return "ERROR, length: " + bean.length;
+            }
+            if (!bean[0].getValue().equals("Hello")) {
+                return "ERROR, [0].value = " + bean[0].getValue();
+            }
+            if (!bean[1].getValue().equals("Ahoy")) {
+                return "ERROR, [1].value = " + bean[1].getValue();
+            }
+            return "PASSED";
+        }
+
+        @GET
+        @Path("getbean")
+        public Response getBean(@Context HttpHeaders headers) {
+            MyBean teb = new MyBean();
+            teb.setValue("hello");
+            return Response.ok().type(MediaType.APPLICATION_JSON_TYPE).entity(new MyBean[] {teb}).build();
+        }
+    }
+
+    public ModelEntityOnArrayTest() {
+        enable(TestProperties.LOG_TRAFFIC);
+    }
+
+    @Test
+    public void myBeanAndPut() {
+        WebTarget target = target("empty/mybean");
+
+        MyBean mb = new MyBean();
+        mb.setValue("Hello");
+        MyBean ah = new MyBean();
+        ah.setValue("Ahoy");
+        MyBean[] arr = new MyBean[] {mb, ah};
+
+        final Response response = target.request().post(Entity.entity(arr, MediaType.APPLICATION_JSON_TYPE));
+
+        assertEquals(200, response.getStatus());
+        assertEquals("PASSED", response.readEntity(String.class));
+    }
+
+    @Test
+    public void doReadWrite() throws Exception {
+        MyBean mb = new MyBean();
+        mb.setValue("Hello");
+        MyBean ah = new MyBean();
+        ah.setValue("Ahoy");
+        MyBean[] arr = new MyBean[] {mb, ah};
+
+        ByteArrayOutputStream os = new ByteArrayOutputStream();
+        os.write('[');
+        os.write(arr[0].toString().getBytes("UTF-8"));
+        os.write(',');
+        os.write(arr[1].toString().getBytes("UTF-8"));
+        os.write(']');
+        os.close();
+
+        ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
+
+        final Class c = arr.getClass();
+        Object ret = new HtmlJsonProvider().readFrom(c, null, null, MediaType.APPLICATION_JSON_TYPE, null, is);
+
+        assertTrue("It is array: " + ret, ret instanceof MyBean[]);
+        MyBean[] res = (MyBean[]) ret;
+        assertEquals("Two items: ", 2, res.length);
+        assertEquals(arr[0], res[0]);
+        assertEquals(arr[1], res[1]);
+    }
+
+    @Test
+    public void myBeanAndGet() {
+        WebTarget target = target("empty/getbean");
+        final Response response = target.request(MediaType.APPLICATION_JSON).get();
+        assertEquals(200, response.getStatus());
+        final MyBean[] teb = response.readEntity(MyBean[].class);
+
+        assertEquals("value", "hello", teb[0].getValue());
+    }
+}
diff --git a/incubator/html-json/src/test/java/org/glassfish/jersey/media/htmljson/ModelEntityOnListTest.java b/incubator/html-json/src/test/java/org/glassfish/jersey/media/htmljson/ModelEntityOnListTest.java
new file mode 100644
index 0000000..2dffdb7
--- /dev/null
+++ b/incubator/html-json/src/test/java/org/glassfish/jersey/media/htmljson/ModelEntityOnListTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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.media.htmljson;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Context;
+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.Response;
+
+import org.glassfish.jersey.test.TestProperties;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+import net.java.html.json.Model;
+
+/**
+ * Reading and writing class generated by {@link Model} as
+ * {@link List}.
+ *
+ * @author Jaroslav Tulach
+ */
+public class ModelEntityOnListTest extends AbstractTypeTester {
+
+    @Path("empty")
+    public static class TestResource {
+
+        @POST
+        @Path("mybean")
+        public String myBean(List<MyBean> bean) {
+            if (bean.size() != 2) {
+                return "ERROR, length: " + bean.size();
+            }
+            if (!bean.get(0).getValue().equals("Hello")) {
+                return "ERROR, [0].value = " + bean.get(0).getValue();
+            }
+            if (!bean.get(1).getValue().equals("Ahoy")) {
+                return "ERROR, [1].value = " + bean.get(1).getValue();
+            }
+            return "PASSED";
+        }
+
+        @GET
+        @Path("getbean")
+        public Response getBean(@Context HttpHeaders headers) {
+            MyBean teb = new MyBean();
+            teb.setValue("hello");
+            List<MyBean> l = new ArrayList<MyBean>();
+            l.add(teb);
+            GenericEntity<List<MyBean>> ge = new GenericEntity<List<MyBean>>(l) {};
+            return Response.ok().type(MediaType.APPLICATION_JSON_TYPE).entity(ge).build();
+        }
+    }
+
+    public ModelEntityOnListTest() {
+        enable(TestProperties.LOG_TRAFFIC);
+    }
+
+    @Test
+    public void myBeanAndPut() {
+        WebTarget target = target("empty/mybean");
+
+        MyBean mb = new MyBean();
+        mb.setValue("Hello");
+        MyBean ah = new MyBean();
+        ah.setValue("Ahoy");
+        GenericEntity<List<MyBean>> ge = new GenericEntity<List<MyBean>>(Arrays.asList(mb, ah)) {};
+        final Response response = target.request().post(Entity.entity(ge, MediaType.APPLICATION_JSON_TYPE));
+
+        assertEquals(200, response.getStatus());
+        assertEquals("PASSED", response.readEntity(String.class));
+    }
+
+    @Test
+    public void myBeanAndGet() {
+        WebTarget target = target("empty/getbean");
+        final Response response = target.request(MediaType.APPLICATION_JSON).get();
+        assertEquals(200, response.getStatus());
+        GenericType<List<MyBean>> ge = new GenericType<List<MyBean>>() {};
+        List<MyBean> teb = response.readEntity(ge);
+
+        assertEquals("one element in list: " + teb, 1, teb.size());
+        assertEquals("value", "hello", teb.get(0).getValue());
+    }
+}
diff --git a/incubator/html-json/src/test/java/org/glassfish/jersey/media/htmljson/ModelEntityTest.java b/incubator/html-json/src/test/java/org/glassfish/jersey/media/htmljson/ModelEntityTest.java
new file mode 100644
index 0000000..dfc996f
--- /dev/null
+++ b/incubator/html-json/src/test/java/org/glassfish/jersey/media/htmljson/ModelEntityTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.media.htmljson;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.test.TestProperties;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+import net.java.html.json.Model;
+import net.java.html.json.Property;
+
+/**
+ * Reading and writing class generated by {@link Model}.
+ *
+ * @author Jaroslav Tulach
+ */
+public class ModelEntityTest extends AbstractTypeTester {
+
+    @Model(className = "MyBean", properties = {
+            @Property(name = "value", type = String.class)
+    })
+    static class MB {
+    }
+
+    @Path("empty")
+    public static class TestResource {
+        @POST
+        @Path("mybean")
+        public String myBean(MyBean bean) {
+            return (bean.getValue().equals("Hello")) ? "PASSED" : "ERROR";
+        }
+
+        @GET
+        @Path("getbean")
+        public Response getBean(@Context HttpHeaders headers) {
+            MyBean teb = new MyBean();
+            teb.setValue("hello");
+            return Response.ok().type(MediaType.APPLICATION_JSON_TYPE).entity(teb).build();
+        }
+    }
+
+    public ModelEntityTest() {
+        enable(TestProperties.LOG_TRAFFIC);
+    }
+
+    @Test
+    public void myBeanAndPut() {
+        WebTarget target = target("empty/mybean");
+
+        MyBean mb = new MyBean();
+        mb.setValue("Hello");
+
+        final Response response = target.request().post(Entity.entity(mb, MediaType.APPLICATION_JSON_TYPE));
+
+        assertEquals(200, response.getStatus());
+        assertEquals("PASSED", response.readEntity(String.class));
+    }
+
+    @Test
+    public void myBeanAndGet() {
+        WebTarget target = target("empty/getbean");
+        final Response response = target.request(MediaType.APPLICATION_JSON).get();
+        assertEquals(200, response.getStatus());
+        final MyBean teb = response.readEntity(MyBean.class);
+
+        assertEquals("value", "hello", teb.getValue());
+    }
+}
diff --git a/incubator/kryo/README.md b/incubator/kryo/README.md
new file mode 100644
index 0000000..4e10678
--- /dev/null
+++ b/incubator/kryo/README.md
@@ -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 "
+
+This is experimental module that provides JAX-RS Message Body Writer & Reader using Kryo serialization framework.
+
+# How to use it
+
+Add dependency to the module:
+
+```xml
+<dependency>
+    <groupId>org.glassfish.jersey.media</groupId>
+    <artifactId>jersey-media-kryo</artifactId>
+    <version>${jersey.version}</version>
+</dependency>
+```
+
+And now you can consume or produce entities (de)serialized by Kryo. Just use `application/x-kryo` MIME type, e.g.:
+
+```java
+@Path("/rest")
+@Consumes("application/x-kryo")
+@Produces("application/x-kryo")
+public class MyResource { ... }
+```
diff --git a/incubator/kryo/pom.xml b/incubator/kryo/pom.xml
new file mode 100644
index 0000000..a6a7757
--- /dev/null
+++ b/incubator/kryo/pom.xml
@@ -0,0 +1,60 @@
+<?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/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.glassfish.jersey.incubator</groupId>
+        <artifactId>project</artifactId>
+        <version>2.28-SNAPSHOT</version>
+    </parent>
+
+    <groupId>org.glassfish.jersey.media</groupId>
+    <artifactId>jersey-media-kryo</artifactId>
+    <name>jersey-media-kryo</name>
+
+    <description>
+        Jersey/JAX-RS Message Body Writer and Reader using Kryo serialization framework
+    </description>
+
+    <dependencies>
+        <dependency>
+            <groupId>javax.ws.rs</groupId>
+            <artifactId>javax.ws.rs-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.jersey.core</groupId>
+            <artifactId>jersey-common</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.esotericsoftware</groupId>
+            <artifactId>kryo</artifactId>
+        </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>
+    </dependencies>
+</project>
diff --git a/incubator/kryo/src/main/java/org/glassfish/jersey/kryo/KryoFeature.java b/incubator/kryo/src/main/java/org/glassfish/jersey/kryo/KryoFeature.java
new file mode 100644
index 0000000..63545fa
--- /dev/null
+++ b/incubator/kryo/src/main/java/org/glassfish/jersey/kryo/KryoFeature.java
@@ -0,0 +1,45 @@
+/*
+ * 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.kryo;
+
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.Feature;
+import javax.ws.rs.core.FeatureContext;
+
+import org.glassfish.jersey.Beta;
+import org.glassfish.jersey.kryo.internal.KryoMessageBodyProvider;
+
+/**
+ * Feature used to register Kryo providers.
+ *
+ * @author Libor Kramolis (libor.kramolis at oracle.com)
+ */
+@Beta
+public class KryoFeature implements Feature {
+
+    @Override
+    public boolean configure(final FeatureContext context) {
+        final Configuration config = context.getConfiguration();
+
+        if (!config.isRegistered(KryoMessageBodyProvider.class)) {
+            context.register(KryoMessageBodyProvider.class);
+        }
+
+        return true;
+    }
+
+}
diff --git a/incubator/kryo/src/main/java/org/glassfish/jersey/kryo/internal/KryoAutoDiscoverable.java b/incubator/kryo/src/main/java/org/glassfish/jersey/kryo/internal/KryoAutoDiscoverable.java
new file mode 100644
index 0000000..73107ca
--- /dev/null
+++ b/incubator/kryo/src/main/java/org/glassfish/jersey/kryo/internal/KryoAutoDiscoverable.java
@@ -0,0 +1,41 @@
+/*
+ * 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.kryo.internal;
+
+import javax.ws.rs.core.FeatureContext;
+
+import javax.annotation.Priority;
+
+import org.glassfish.jersey.internal.spi.AutoDiscoverable;
+import org.glassfish.jersey.kryo.KryoFeature;
+
+/**
+ * {@link AutoDiscoverable} registering {@link KryoFeature} if the feature is not already registered.
+ *
+ * @author Libor Kramolis (libor.kramolis at oracle.com)
+ */
+@Priority(AutoDiscoverable.DEFAULT_PRIORITY)
+public class KryoAutoDiscoverable implements AutoDiscoverable {
+
+    @Override
+    public void configure(final FeatureContext context) {
+        if (!context.getConfiguration().isRegistered(KryoFeature.class)) {
+            context.register(KryoFeature.class);
+        }
+    }
+
+}
diff --git a/incubator/kryo/src/main/java/org/glassfish/jersey/kryo/internal/KryoMessageBodyProvider.java b/incubator/kryo/src/main/java/org/glassfish/jersey/kryo/internal/KryoMessageBodyProvider.java
new file mode 100644
index 0000000..755c290
--- /dev/null
+++ b/incubator/kryo/src/main/java/org/glassfish/jersey/kryo/internal/KryoMessageBodyProvider.java
@@ -0,0 +1,118 @@
+/*
+ * 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.kryo.internal;
+
+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.Consumes;
+import javax.ws.rs.Produces;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyReader;
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.Provider;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+import com.esotericsoftware.kryo.pool.KryoCallback;
+import com.esotericsoftware.kryo.pool.KryoFactory;
+import com.esotericsoftware.kryo.pool.KryoPool;
+
+/**
+ * @author Libor Kramolis (libor.kramolis at oracle.com)
+ */
+@Provider
+@Consumes("application/x-kryo")
+@Produces("application/x-kryo")
+public class KryoMessageBodyProvider implements MessageBodyWriter<Object>, MessageBodyReader<Object> {
+
+    private final KryoPool kryoPool;
+
+    public KryoMessageBodyProvider() {
+        final KryoFactory kryoFactory = new KryoFactory() {
+            public Kryo create() {
+                final Kryo kryo = new Kryo();
+                //TODO: configure kryo instance, customize settings
+                //TODO: e.g. looking for Kryo via ContextResolver (like Jackson)
+                return kryo;
+            }
+        };
+        kryoPool = new KryoPool.Builder(kryoFactory).softReferences().build();
+    }
+
+    //
+    // MessageBodyWriter
+    //
+
+    @Override
+    public long getSize(final Object object, final Class<?> type, final Type genericType,
+                        final Annotation[] annotations, final MediaType mediaType) {
+        return -1;
+    }
+
+    @Override
+    public boolean isWriteable(final Class<?> type, final Type genericType,
+                               final Annotation[] annotations, final MediaType mediaType) {
+        return true;
+    }
+
+    @Override
+    public void writeTo(final Object object, final Class<?> type, final Type genericType,
+                        final Annotation[] annotations, final MediaType mediaType,
+                        final MultivaluedMap<String, Object> httpHeaders, final OutputStream entityStream)
+            throws IOException, WebApplicationException {
+        final Output output = new Output(entityStream);
+        kryoPool.run(new KryoCallback() {
+            public Object execute(Kryo kryo) {
+                kryo.writeObject(output, object);
+                return null;
+            }
+        });
+        output.flush();
+    }
+
+    //
+    // 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<Object> type, final Type genericType,
+                           final Annotation[] annotations, final MediaType mediaType,
+                           final MultivaluedMap<String, String> httpHeaders,
+                           final InputStream entityStream) throws IOException, WebApplicationException {
+        final Input input = new Input(entityStream);
+
+        return kryoPool.run(new KryoCallback() {
+            public Object execute(Kryo kryo) {
+                return kryo.readObject(input, type);
+            }
+        });
+    }
+
+}
diff --git a/incubator/kryo/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.AutoDiscoverable b/incubator/kryo/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.AutoDiscoverable
new file mode 100644
index 0000000..870630d
--- /dev/null
+++ b/incubator/kryo/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.AutoDiscoverable
@@ -0,0 +1 @@
+org.glassfish.jersey.kryo.internal.KryoAutoDiscoverable
diff --git a/incubator/kryo/src/test/java/org/glassfish/jersey/kryo/JaxRsApplication.java b/incubator/kryo/src/test/java/org/glassfish/jersey/kryo/JaxRsApplication.java
new file mode 100644
index 0000000..fae6a4c
--- /dev/null
+++ b/incubator/kryo/src/test/java/org/glassfish/jersey/kryo/JaxRsApplication.java
@@ -0,0 +1,40 @@
+/*
+ * 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.kryo;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.ws.rs.core.Application;
+
+/**
+ * Test case JAX-RS application.
+ *
+ * @author Libor Kramolis (libor.kramolis at oracle.com)
+ */
+public class JaxRsApplication extends Application {
+
+    static final Set<Class<?>> APP_CLASSES = new HashSet<Class<?>>() {{
+        add(PersonResource.class);
+    }};
+
+    @Override
+    public Set<Class<?>> getClasses() {
+        return APP_CLASSES;
+    }
+
+}
diff --git a/incubator/kryo/src/test/java/org/glassfish/jersey/kryo/Person.java b/incubator/kryo/src/test/java/org/glassfish/jersey/kryo/Person.java
new file mode 100644
index 0000000..ac17889
--- /dev/null
+++ b/incubator/kryo/src/test/java/org/glassfish/jersey/kryo/Person.java
@@ -0,0 +1,67 @@
+/*
+ * 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.kryo;
+
+import java.util.Objects;
+
+/**
+ * Test data bean.
+ *
+ * @author Libor Kramolis (libor.kramolis at oracle.com)
+ */
+public class Person {
+
+    public String name;
+    public int age;
+    public String address;
+
+    public Person(String name, int age, String address) {
+        this.name = name;
+        this.age = age;
+        this.address = address;
+    }
+
+    public Person() {
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = 7;
+        hash = 19 * hash + Objects.hashCode(this.name);
+        hash = 19 * hash + this.age;
+        hash = 19 * hash + Objects.hashCode(this.address);
+        return hash;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final Person other = (Person) obj;
+        if (!Objects.equals(this.name, other.name)) {
+            return false;
+        }
+        if (this.age != other.age) {
+            return false;
+        }
+        return (Objects.equals(this.address, other.address));
+    }
+}
diff --git a/incubator/kryo/src/test/java/org/glassfish/jersey/kryo/PersonResource.java b/incubator/kryo/src/test/java/org/glassfish/jersey/kryo/PersonResource.java
new file mode 100644
index 0000000..58981a8
--- /dev/null
+++ b/incubator/kryo/src/test/java/org/glassfish/jersey/kryo/PersonResource.java
@@ -0,0 +1,50 @@
+/*
+ * 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.kryo;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+
+/**
+ * Test resource.
+ *
+ * @author Libor Kramolis (libor.kramolis at oracle.com)
+ */
+@Path("/")
+@Consumes("application/x-kryo")
+@Produces("application/x-kryo")
+public class PersonResource {
+
+    @POST
+    public Person echo(final Person person) {
+        return person;
+    }
+
+    @PUT
+    public void put(final Person person) {
+    }
+
+    @GET
+    public Person get() {
+        return new Person("Wolfgang", 21, "Salzburg");
+    }
+
+}
diff --git a/incubator/kryo/src/test/java/org/glassfish/jersey/kryo/PersonResourceTest.java b/incubator/kryo/src/test/java/org/glassfish/jersey/kryo/PersonResourceTest.java
new file mode 100644
index 0000000..4d4eaea
--- /dev/null
+++ b/incubator/kryo/src/test/java/org/glassfish/jersey/kryo/PersonResourceTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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.kryo;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.TestProperties;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test for kryo resource.
+ *
+ * @author Libor Kramolis (libor.kramolis at oracle.com)
+ */
+public class PersonResourceTest extends JerseyTest {
+
+    @Override
+    protected Application configure() {
+        enable(TestProperties.LOG_TRAFFIC);
+        enable(TestProperties.DUMP_ENTITY);
+        return new JaxRsApplication();
+    }
+
+    @Override
+    protected void configureClient(final ClientConfig config) {
+    }
+
+    @Test
+    public void testGet() {
+        final Person getResponse = target().request().get(Person.class);
+        assertEquals("Wolfgang", getResponse.name);
+        assertEquals(21, getResponse.age);
+        assertEquals("Salzburg", getResponse.address);
+    }
+
+    @Test
+    public void testPost() {
+        final Person[] testData = new Person[] {new Person("Joseph", 23, "Nazareth"), new Person("Mary", 18, "Nazareth")};
+        for (Person original : testData) {
+            final Person postResponse = target().request()
+                    .post(Entity.entity(original, "application/x-kryo"), Person.class);
+            assertEquals(original, postResponse);
+        }
+    }
+
+    @Test
+    public void testPut() {
+        final Response putResponse = target().request().put(Entity.entity(new Person("Jules", 12, "Paris"),
+                "application/x-kryo"));
+        assertEquals(204, putResponse.getStatus());
+    }
+
+}
diff --git a/incubator/open-tracing/pom.xml b/incubator/open-tracing/pom.xml
new file mode 100644
index 0000000..f96771e
--- /dev/null
+++ b/incubator/open-tracing/pom.xml
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.glassfish.jersey.incubator</groupId>
+        <artifactId>project</artifactId>
+        <version>2.28-SNAPSHOT</version>
+    </parent>
+
+    <groupId>org.glassfish.jersey.incubator</groupId>
+    <artifactId>jersey-open-tracing</artifactId>
+    <packaging>jar</packaging>
+
+    <name>jersey-open-tracing</name>
+
+    <description>
+        Jersey support for OpenTracing.
+    </description>
+
+    <dependencies>
+        <dependency>
+            <groupId>io.opentracing</groupId>
+            <artifactId>opentracing-api</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>io.opentracing</groupId>
+            <artifactId>opentracing-util</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>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </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>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>com.sun.istack</groupId>
+                <artifactId>maven-istack-commons-plugin</artifactId>
+                <inherited>true</inherited>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>build-helper-maven-plugin</artifactId>
+                <inherited>true</inherited>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <inherited>true</inherited>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingApplicationEventListener.java b/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingApplicationEventListener.java
new file mode 100644
index 0000000..d155760
--- /dev/null
+++ b/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingApplicationEventListener.java
@@ -0,0 +1,272 @@
+/*
+ * 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.opentracing;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import javax.ws.rs.container.ContainerRequestFilter;
+import javax.ws.rs.container.ContainerResponseFilter;
+
+import org.glassfish.jersey.server.ContainerRequest;
+import org.glassfish.jersey.server.ContainerResponse;
+import org.glassfish.jersey.server.monitoring.ApplicationEvent;
+import org.glassfish.jersey.server.monitoring.ApplicationEventListener;
+import org.glassfish.jersey.server.monitoring.RequestEvent;
+import org.glassfish.jersey.server.monitoring.RequestEventListener;
+
+import io.opentracing.Span;
+import io.opentracing.SpanContext;
+import io.opentracing.Tracer;
+import io.opentracing.propagation.Format;
+import io.opentracing.propagation.TextMapExtractAdapter;
+import io.opentracing.tag.Tags;
+import io.opentracing.util.GlobalTracer;
+
+/**
+ * Application event listener responsible for creating and propagating server-side request {@link io.opentracing.Span}.
+ *
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ * @since 2.26
+ */
+class OpenTracingApplicationEventListener implements ApplicationEventListener {
+    private final Tracer globalTracer = GlobalTracer.get();
+    private final OpenTracingFeature.Verbosity verbosity;
+
+    /**
+     * Creates event listener instance with given {@link org.glassfish.jersey.opentracing.OpenTracingFeature.Verbosity}.
+     *
+     * @param verbosity desired verbosity level
+     */
+    public OpenTracingApplicationEventListener(OpenTracingFeature.Verbosity verbosity) {
+        this.verbosity = verbosity;
+    }
+
+    @Override
+    public void onEvent(ApplicationEvent event) {
+        // we don't care about the server lifecycle
+    }
+
+    @Override
+    public RequestEventListener onRequest(RequestEvent requestEvent) {
+        if (requestEvent.getType() == RequestEvent.Type.START) {
+            Span requestSpan = handleRequestStart(requestEvent.getContainerRequest());
+            return new OpenTracingRequestEventListener(requestSpan);
+        }
+        return null;
+    }
+
+    private Span handleRequestStart(ContainerRequest request) {
+
+        final Map<String, String> mappedHeaders = request
+                .getHeaders()
+                .entrySet()
+                .stream()
+                .collect(Collectors.toMap(
+                        Map.Entry::getKey,
+                        (entry) -> OpenTracingUtils.formatList(entry.getValue())));
+
+        final SpanContext extractedContext =
+                globalTracer.extract(Format.Builtin.HTTP_HEADERS, new TextMapExtractAdapter(mappedHeaders));
+
+        Tracer.SpanBuilder spanBuilder = globalTracer
+                .buildSpan(OpenTracingFeature.DEFAULT_REQUEST_SPAN_NAME)
+                .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_SERVER)
+                .withTag(Tags.HTTP_METHOD.getKey(), request.getMethod())
+                .withTag(Tags.HTTP_URL.getKey(), request.getRequestUri().toASCIIString())
+                .withTag(LocalizationMessages.OPENTRACING_TAG_REQUEST_HEADERS(),
+                        OpenTracingUtils.headersAsString(request.getHeaders()))
+                .withTag(LocalizationMessages.OPENTRACING_TAG_HAS_REQUEST_ENTITY(), request.hasEntity());
+
+        if (extractedContext != null) {
+            spanBuilder = spanBuilder.asChildOf(extractedContext);
+        }
+
+        final Span span = spanBuilder.startManual();
+        request.setProperty(OpenTracingFeature.SPAN_CONTEXT_PROPERTY, span);
+        span.log(LocalizationMessages.OPENTRACING_LOG_REQUEST_STARTED());
+        return span;
+    }
+
+    class OpenTracingRequestEventListener implements RequestEventListener {
+        private Span requestSpan;
+        private Span resourceSpan = null;
+
+        OpenTracingRequestEventListener(final Span requestSpan) {
+            this.requestSpan = requestSpan;
+        }
+
+        @Override
+        public void onEvent(RequestEvent event) {
+
+            switch (event.getType()) {
+                case MATCHING_START:
+                    logVerbose(LocalizationMessages.OPENTRACING_LOG_MATCHING_STARTED());
+                    break;
+
+                case LOCATOR_MATCHED:
+                    logVerbose(LocalizationMessages.OPENTRACING_LOG_LOCATOR_MATCHED(
+                            OpenTracingUtils.formatList(event.getUriInfo().getMatchedResourceLocators())));
+                    break;
+
+                case SUBRESOURCE_LOCATED:
+                    logVerbose(LocalizationMessages.OPENTRACING_LOG_SUBRESOURCE_LOCATED(
+                            OpenTracingUtils.formatList(event.getUriInfo().getLocatorSubResources())));
+                    break;
+
+
+                case REQUEST_MATCHED:
+                    logVerbose(LocalizationMessages.OPENTRACING_LOG_REQUEST_MATCHED(event.getUriInfo()
+                            .getMatchedResourceMethod()
+                            .getInvocable()
+                            .getDefinitionMethod()));
+                    log(LocalizationMessages.OPENTRACING_LOG_REQUEST_FILTERING_STARTED());
+                    break;
+
+                case REQUEST_FILTERED:
+                    List<ContainerRequestFilter> requestFilters = new ArrayList<>();
+                    event.getContainerRequestFilters().forEach(requestFilters::add);
+
+                    logVerbose(LocalizationMessages.OPENTRACING_LOG_REQUEST_FILTERING_FINISHED(requestFilters.size()));
+                    if (requestFilters.size() > 0) {
+                        log(LocalizationMessages.OPENTRACING_LOG_APPLIED_REQUEST_FILTERS(
+                                OpenTracingUtils.formatProviders(requestFilters)));
+                    }
+                    break;
+
+                case RESOURCE_METHOD_START:
+                    logVerbose(LocalizationMessages.OPENTRACING_LOG_RESOURCE_METHOD_STARTED(
+                            event.getUriInfo().getMatchedResourceMethod().getInvocable().getDefinitionMethod()));
+
+                    resourceSpan = globalTracer.buildSpan(OpenTracingFeature.DEFAULT_RESOURCE_SPAN_NAME)
+                                               .asChildOf(requestSpan)
+                                               .startManual();
+
+                    event.getContainerRequest().setProperty(OpenTracingFeature.SPAN_CONTEXT_PROPERTY, resourceSpan);
+                    break;
+
+                case RESOURCE_METHOD_FINISHED:
+                    log(LocalizationMessages.OPENTRACING_LOG_RESOURCE_METHOD_FINISHED());
+                    break;
+
+                case RESP_FILTERS_START:
+                    // this is the first event after resource method is guaranteed to have finished, even for asynchronous
+                    // processing; resourceSpan will be finished and the span in the context will be switched back to the
+                    // resource span before any further tracing can occur.
+                    event.getContainerRequest().setProperty(OpenTracingFeature.SPAN_CONTEXT_PROPERTY, requestSpan);
+                    resourceSpan.finish();
+                    logVerbose(LocalizationMessages.OPENTRACING_LOG_RESPONSE_FILTERING_STARTED());
+                    break;
+
+                case RESP_FILTERS_FINISHED:
+                    List<ContainerResponseFilter> responseFilters = new ArrayList<>();
+                    event.getContainerResponseFilters().forEach(responseFilters::add);
+                    logVerbose(LocalizationMessages.OPENTRACING_LOG_RESPONSE_FILTERING_FINISHED(responseFilters.size()));
+                    if (responseFilters.size() > 0) {
+                        log(LocalizationMessages.OPENTRACING_LOG_APPLIED_RESPONSE_FILTERS(
+                                OpenTracingUtils.formatProviders(responseFilters)));
+                    }
+                    break;
+
+                case ON_EXCEPTION:
+                    if (resourceSpan != null) {
+                        resourceSpan.setTag(Tags.ERROR.getKey(), true);
+                        resourceSpan.finish();
+                    }
+                    requestSpan.setTag(Tags.ERROR.getKey(), true);
+                    logError(event.getException());
+                    break;
+
+                case EXCEPTION_MAPPER_FOUND:
+                    log(LocalizationMessages.OPENTRACING_LOG_EXCEPTION_MAPPER_FOUND(
+                            event.getExceptionMapper().getClass().getName()));
+                    break;
+
+                case EXCEPTION_MAPPING_FINISHED:
+                    log(LocalizationMessages.OPENTRACING_LOG_EXCEPTION_MAPPING_FINISHED()
+                            + (event.isResponseSuccessfullyMapped()
+                            ? LocalizationMessages.OPENTRACING_LOG_EXCEPTION_MAPPING_SUCCESS()
+                            : LocalizationMessages.OPENTRACING_LOG_EXCEPTION_MAPPING_NOEXCEPTION_OR_FAILED()));
+
+                    break;
+
+
+                case FINISHED:
+                    if (requestSpan != null) {
+                        ContainerResponse response = event.getContainerResponse();
+                        if (response != null) {
+                            int status = response.getStatus();
+                            requestSpan
+                                    .setTag(Tags.HTTP_STATUS.getKey(), status)
+                                    .setTag(LocalizationMessages.OPENTRACING_TAG_HAS_RESPONSE_ENTITY(), response.hasEntity())
+                                    .setTag(LocalizationMessages.OPENTRACING_TAG_RESPONSE_LENGTH(), response.getLength());
+
+                            if (400 <= status) {
+                                requestSpan.setTag(Tags.ERROR.getKey(), true);
+                            }
+                        }
+                        requestSpan.finish();
+                    }
+                    break;
+            }
+        }
+
+        /**
+         * Adds a {@link OpenTracingFeature.Verbosity#TRACE}-level log entry into the request span.
+         * @param s log message
+         */
+        private void logVerbose(String s) {
+            log(OpenTracingFeature.Verbosity.TRACE, s);
+        }
+
+        /**
+         * Adds a {@link OpenTracingFeature.Verbosity#INFO}-level log entry into the request span.
+         * @param s log message
+         */
+        private void log(String s) {
+            log(OpenTracingFeature.Verbosity.INFO, s);
+        }
+
+        /**
+         * Adds a log entry with given {@link org.glassfish.jersey.opentracing.OpenTracingFeature.Verbosity}-level into the
+         * request span.
+         *
+         * @param level desired verbosity level
+         * @param s log message
+         */
+        private void log(OpenTracingFeature.Verbosity level, String s) {
+            if (level.ordinal() <= verbosity.ordinal()) {
+                requestSpan.log(s);
+            }
+        }
+
+        /**
+         * Adds an error log into the request span.
+         * @param t exception to be logged.
+         */
+        private void logError(final Throwable t) {
+            Map<String, Object> errorMap = new HashMap<>(2);
+            errorMap.put("event", "error");
+            errorMap.put("error.object", t);
+            requestSpan.log(errorMap);
+        }
+    }
+
+}
diff --git a/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingClientRequestFilter.java b/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingClientRequestFilter.java
new file mode 100644
index 0000000..7934488
--- /dev/null
+++ b/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingClientRequestFilter.java
@@ -0,0 +1,75 @@
+/*
+ * 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.opentracing;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import javax.ws.rs.client.ClientRequestContext;
+import javax.ws.rs.client.ClientRequestFilter;
+import javax.ws.rs.core.MediaType;
+
+import io.opentracing.Span;
+import io.opentracing.SpanContext;
+import io.opentracing.Tracer;
+import io.opentracing.propagation.Format;
+import io.opentracing.propagation.TextMapInjectAdapter;
+import io.opentracing.tag.Tags;
+import io.opentracing.util.GlobalTracer;
+
+/**
+ * Client-side request filter, that creates the client request {@code Span}.
+ * <p>
+ * Stores request-related metadata into the {@code Span} as {@code Tags}
+ * and {@link GlobalTracer#inject(SpanContext, Format, Object) injects} it into http headers.
+ *
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ * @since 2.26
+ */
+class OpenTracingClientRequestFilter implements ClientRequestFilter {
+
+    @Override
+    public void filter(ClientRequestContext requestContext) throws IOException {
+        Tracer.SpanBuilder spanBuilder = GlobalTracer.get()
+                .buildSpan(LocalizationMessages.OPENTRACING_SPAN_PREFIX_CLIENT() + requestContext.getMethod())
+                .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT)
+                .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT)
+                .withTag(Tags.HTTP_URL.getKey(), requestContext.getUri().toASCIIString())
+                .withTag(Tags.HTTP_METHOD.getKey(), requestContext.getMethod())
+                .withTag(LocalizationMessages.OPENTRACING_TAG_HAS_REQUEST_ENTITY(), requestContext.hasEntity())
+                .withTag(LocalizationMessages.OPENTRACING_TAG_ACCEPTABLE_MEDIA_TYPES(), requestContext.getAcceptableMediaTypes()
+                        .stream()
+                        .map(MediaType::toString)
+                        .collect(Collectors.joining(", ")))
+                .withTag(LocalizationMessages.OPENTRACING_TAG_REQUEST_HEADERS(),
+                        OpenTracingUtils.headersAsString(requestContext.getHeaders()));
+
+        // if pre-stored "span" property is found, propagate the stored context
+        final Object property = requestContext.getProperty(OpenTracingFeature.SPAN_CONTEXT_PROPERTY);
+        if (property != null && property instanceof SpanContext) {
+            spanBuilder = spanBuilder.asChildOf((SpanContext) property);
+        }
+        Span span = spanBuilder.startManual();
+
+        requestContext.setProperty(OpenTracingFeature.SPAN_CONTEXT_PROPERTY, span);
+        Map<String, String> addedHeaders = new HashMap<>();
+        GlobalTracer.get().inject(span.context(), Format.Builtin.HTTP_HEADERS, new TextMapInjectAdapter(addedHeaders));
+        addedHeaders.forEach((key, value) -> requestContext.getHeaders().add(key, value));
+    }
+}
diff --git a/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingClientResponseFilter.java b/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingClientResponseFilter.java
new file mode 100644
index 0000000..0dae48f
--- /dev/null
+++ b/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingClientResponseFilter.java
@@ -0,0 +1,49 @@
+/*
+ * 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.opentracing;
+
+import java.io.IOException;
+
+import javax.ws.rs.client.ClientRequestContext;
+import javax.ws.rs.client.ClientResponseContext;
+import javax.ws.rs.client.ClientResponseFilter;
+
+import io.opentracing.Span;
+import io.opentracing.tag.Tags;
+
+/**
+ * Retrieves stored span from the {@link ClientRequestContext} and adds response details to the tracing info.
+ *
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ * @since 2.26
+ */
+public class OpenTracingClientResponseFilter implements ClientResponseFilter {
+
+    @Override
+    public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) throws IOException {
+        final Object spanProperty = requestContext.getProperty(OpenTracingFeature.SPAN_CONTEXT_PROPERTY);
+        if (spanProperty != null && spanProperty instanceof Span) {
+            ((Span) spanProperty)
+                    .setTag(Tags.HTTP_STATUS.getKey(), responseContext.getStatus())
+                    .setTag(LocalizationMessages.OPENTRACING_TAG_HAS_RESPONSE_ENTITY(), responseContext.hasEntity())
+                    .setTag(LocalizationMessages.OPENTRACING_TAG_RESPONSE_LENGTH(), responseContext.getLength())
+                    .setTag(LocalizationMessages.OPENTRACING_TAG_RESPONSE_HEADERS(),
+                            OpenTracingUtils.headersAsString(responseContext.getHeaders()))
+                    .finish();
+        }
+    }
+}
diff --git a/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingFeature.java b/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingFeature.java
new file mode 100644
index 0000000..6afb1f9
--- /dev/null
+++ b/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingFeature.java
@@ -0,0 +1,106 @@
+/*
+ * 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.opentracing;
+
+import java.util.logging.Logger;
+
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.core.Feature;
+import javax.ws.rs.core.FeatureContext;
+
+import org.glassfish.jersey.Beta;
+
+import io.opentracing.util.GlobalTracer;
+
+/**
+ * A feature that enables OpenTracing support on server and client.
+ *
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ * @since 2.26
+ */
+@Beta
+public class OpenTracingFeature implements Feature {
+    private static final Logger LOGGER = Logger.getLogger(OpenTracingFeature.class.getName());
+    private final Verbosity verbosity;
+
+    /**
+     * Creates feature instance with default ({@link Verbosity#INFO} verbosity level.
+     */
+    public OpenTracingFeature() {
+        verbosity = Verbosity.INFO;
+    }
+
+    /**
+     * Creates feature instance with given ({@link Verbosity} level.
+     * @param verbosity desired level of logging verbosity
+     */
+    public OpenTracingFeature(Verbosity verbosity) {
+        this.verbosity = verbosity;
+    }
+
+    /**
+     * Stored span's {@link ContainerRequestContext} property key.
+     */
+    public static final String SPAN_CONTEXT_PROPERTY = "span";
+
+    /**
+     * Default resource span name.
+     */
+    public static final String DEFAULT_RESOURCE_SPAN_NAME = "jersey-resource";
+
+    /**
+     * Default child span name.
+     */
+    public static final String DEFAULT_CHILD_SPAN_NAME = "jersey-resource-app";
+
+    /**
+     * Default request "root" span name.
+     */
+    public static final String DEFAULT_REQUEST_SPAN_NAME = "jersey-server";
+
+    @Override
+    public boolean configure(FeatureContext context) {
+        if (!GlobalTracer.isRegistered()) {
+            LOGGER.warning(LocalizationMessages.OPENTRACING_TRACER_NOT_REGISTERED());
+        }
+
+        switch (context.getConfiguration().getRuntimeType()) {
+            case CLIENT:
+                context.register(OpenTracingClientRequestFilter.class).register(OpenTracingClientResponseFilter.class);
+                break;
+            case SERVER:
+                context.register(new OpenTracingApplicationEventListener(verbosity));
+        }
+        return true;
+    }
+
+    /**
+     * OpenTracing Jersey event logging verbosity.
+     */
+    public enum Verbosity {
+        /**
+         * Only logs basic Jersey processing related events.
+         */
+        INFO,
+
+        /**
+         * Logs more fine grained events related to Jersey processing.
+         */
+        TRACE
+    }
+
+}
diff --git a/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingUtils.java b/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingUtils.java
new file mode 100644
index 0000000..4bc3ad0
--- /dev/null
+++ b/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingUtils.java
@@ -0,0 +1,123 @@
+/*
+ * 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.opentracing;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.core.MultivaluedMap;
+
+import io.opentracing.Span;
+import io.opentracing.Tracer;
+import io.opentracing.util.GlobalTracer;
+
+/**
+ * Utility methods for Jersey OpenTracing integration.
+ *
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ * @since 2.26
+ */
+public class OpenTracingUtils {
+
+    private OpenTracingUtils() {
+    }
+
+    /**
+     * Resolve resource-level span.
+     * <p>
+     * If open tracing is enabled and {@link GlobalTracer} is registered, resource-level span should be stored in the
+     * {@link OpenTracingFeature#SPAN_CONTEXT_PROPERTY}. This span is resolved and returned as an {@link Optional}.
+     *
+     * @param context {@link ContainerRequestContext} instance, can be obtained via {@code @Context} injection
+     * @return {@link Optional} of the resolved span, if found; empty optional if not
+     */
+    public static Optional<Span> getRequestSpan(final ContainerRequestContext context) {
+        if (context != null) {
+            final Object spanProperty = context.getProperty(OpenTracingFeature.SPAN_CONTEXT_PROPERTY);
+            if (spanProperty != null && spanProperty instanceof Span) {
+                return Optional.of((Span) spanProperty);
+            }
+        }
+        return Optional.empty();
+    }
+
+    /**
+     * Create and start ad-hoc custom span with the default name as a child span of the request span (if available).
+     *
+     * @param context {@link ContainerRequestContext} instance, can be obtained via {@code @Context} injection
+     * @return If parent span ("request span") instance is stored in the {@code ContainerRequestContext}, new span is created
+     * as a child span of the found span. If no parent span found, new "root" span is created. In both cases, the returned span
+     * is already started. In order to successfully store the tracing, {@link Span#finish()} needs to be invoked explicitly,
+     * after the traced code finishes.
+     */
+    public static Span getRequestChildSpan(final ContainerRequestContext context) {
+        return getRequestChildSpan(context, OpenTracingFeature.DEFAULT_CHILD_SPAN_NAME);
+    }
+
+    /**
+     * Create and start ad-hoc custom span with a custom name as a child span of the request span (if available).
+     *
+     * @param context  {@link ContainerRequestContext} instance, can be obtained via {@code @Context} injection
+     * @param spanName name to be used for the created span
+     * @return If parent span ("request span") instance is stored in the {@code ContainerRequestContext}, new span is created
+     * as a child span of the found span. If no parent span found, new "root" span is created. In both cases, the returned span
+     * is already started. In order to successfully store the tracing, {@link Span#finish()} needs to be invoked explicitly,
+     * after the traced code finishes.
+     */
+    public static Span getRequestChildSpan(final ContainerRequestContext context, final String spanName) {
+        Tracer.SpanBuilder spanBuilder = GlobalTracer.get().buildSpan(spanName);
+        if (context != null) {
+            final Object spanProperty = context.getProperty(OpenTracingFeature.SPAN_CONTEXT_PROPERTY);
+            if (spanProperty != null && spanProperty instanceof Span) {
+                spanBuilder = spanBuilder.asChildOf((Span) spanProperty);
+            }
+        }
+        return spanBuilder.startManual();
+    }
+
+    /**
+     * Convert request/response headers from {@link MultivaluedMap} into printable form.
+     *
+     * @param headers multi-valued map of request or response headers
+     * @return {@code String} representation, e.g. "[header1=foo]; [header2=bar, baz]"
+     */
+    static String headersAsString(final MultivaluedMap<String, ?> headers) {
+        return headers.entrySet()
+                .stream()
+                .map((entry) -> "["
+                        + entry.getKey() + "="
+                        + entry.getValue()
+                        .stream()
+                        .map(Object::toString)
+                        .collect(Collectors.joining(", "))
+                        + "]")
+                .collect(Collectors.joining("; "));
+    }
+
+    static String formatList(List<?> list) {
+        return list.stream().map(Object::toString).collect(Collectors.joining(", "));
+    }
+
+    static String formatProviders(Iterable<?> providers) {
+        return StreamSupport.stream(providers.spliterator(), false)
+                .map((provider) -> provider.getClass().getName())
+                .collect(Collectors.joining(", "));
+    }
+}
diff --git a/incubator/open-tracing/src/main/resources/org/glassfish/jersey/opentracing/localization.properties b/incubator/open-tracing/src/main/resources/org/glassfish/jersey/opentracing/localization.properties
new file mode 100644
index 0000000..67f8508
--- /dev/null
+++ b/incubator/open-tracing/src/main/resources/org/glassfish/jersey/opentracing/localization.properties
@@ -0,0 +1,44 @@
+#
+# 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
+#
+
+opentracing.tracer.not.registered=OpenTracing feature is enabled, but no Tracer has been registered. Default no-op tracer will \
+  be used.
+opentracing.log.request.started=Request started.
+opentracing.log.matching.started=Resource matching started.
+opentracing.log.request.matched=Request matched, method: {0}
+opentracing.log.locator.matched=Locator matched. Matched locators: {0}
+opentracing.log.subresource.located=Subresource located: {0}
+opentracing.log.request.filtering.started=Request filtering started.
+opentracing.log.request.filtering.finished=Request filtering finished, {0} filter(s) applied.
+opentracing.log.applied.request.filters=Applied request filters: {0}.
+opentracing.log.resource.method.started=Resource method {0} started.
+opentracing.log.response.filtering.started=Response filtering started.
+opentracing.log.resource.method.finished=Resource method finished.
+opentracing.log.response.filtering.finished=Response filtering finished, {0} filter(s) applied.
+opentracing.log.applied.response.filters=Applied reqsponse filters: {0}.
+opentracing.log.exception=Exception while processing the request: {0}.
+opentracing.log.exception.mapper.found=Exception mapper found: {0}.
+opentracing.log.exception.mapping.finished=Exception mapping finished: 
+opentracing.log.exception.mapping.success=successfully mapped
+opentracing.log.exception.mapping.noexception.or.failed=no exception or mapping failed.
+opentracing.tag.request.headers=requestHeaders
+opentracing.tag.response.headers=responseHeaders
+opentracing.tag.has.request.entity=hasRequestEntity
+opentracing.tag.has.response.entity=hasResponseEntity
+opentracing.tag.response.length=responseLength
+opentracing.tag.acceptable.media.types=acceptableMediaTypes
+opentracing.span.prefix.client=jersey-client-
+
diff --git a/incubator/pom.xml b/incubator/pom.xml
new file mode 100644
index 0000000..484edcb
--- /dev/null
+++ b/incubator/pom.xml
@@ -0,0 +1,54 @@
+<?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.incubator</groupId>
+    <artifactId>project</artifactId>
+    <packaging>pom</packaging>
+    <name>jersey-incubator</name>
+
+    <description>
+        Jersey incubator for experimental modules that are in a prototyping or research stage,
+        not ready to be released as part of Jersey (yet).
+    </description>
+
+    <modules>
+        <module>declarative-linking</module>
+        <module>gae-integration</module>
+        <!--<module>html-json</module>-->
+        <module>kryo</module>
+        <module>open-tracing</module>
+    </modules>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.glassfish.jersey.inject</groupId>
+            <artifactId>jersey-hk2</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>