Rest client implementation

Signed-off-by: David Kral <david.k.kral@oracle.com>
diff --git a/core-client/src/main/java/org/glassfish/jersey/client/JerseyWebTarget.java b/core-client/src/main/java/org/glassfish/jersey/client/JerseyWebTarget.java
index c7d54be..26df84f 100644
--- a/core-client/src/main/java/org/glassfish/jersey/client/JerseyWebTarget.java
+++ b/core-client/src/main/java/org/glassfish/jersey/client/JerseyWebTarget.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2019 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -26,6 +26,7 @@
 import javax.ws.rs.core.UriBuilder;
 
 import org.glassfish.jersey.internal.guava.Preconditions;
+import org.glassfish.jersey.internal.inject.InjectionManager;
 
 /**
  * Jersey implementation of {@link javax.ws.rs.client.WebTarget JAX-RS client target}
diff --git a/ext/microprofile/mp-rest-client/pom.xml b/ext/microprofile/mp-rest-client/pom.xml
new file mode 100644
index 0000000..dc47382
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/pom.xml
@@ -0,0 +1,202 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>jersey-microprofile</artifactId>
+        <groupId>org.glassfish.jersey.ext</groupId>
+        <version>2.29-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>jersey-mp-rest-client</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.eclipse.microprofile.rest.client</groupId>
+            <artifactId>microprofile-rest-client-api</artifactId>
+            <version>1.2.1</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.microprofile.config</groupId>
+            <artifactId>microprofile-config-api</artifactId>
+            <version>1.3</version>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.jersey.core</groupId>
+            <artifactId>jersey-client</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.jersey.core</groupId>
+            <artifactId>jersey-server</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.jersey.inject</groupId>
+            <artifactId>jersey-hk2</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.jersey.media</groupId>
+            <artifactId>jersey-media-json-binding</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>jakarta.json</groupId>
+            <artifactId>jakarta.json-api</artifactId>
+            <version>1.1.5</version>
+        </dependency>
+        <dependency>
+            <groupId>javax.enterprise</groupId>
+            <artifactId>cdi-api</artifactId>
+            <version>2.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish</groupId>
+            <artifactId>jsonp-jaxrs</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.weld.se</groupId>
+            <artifactId>weld-se-core</artifactId>
+            <version>3.0.3.Final</version>
+            <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.test-framework.providers</groupId>
+            <artifactId>jersey-test-framework-provider-bundle</artifactId>
+            <version>${project.version}</version>
+            <type>pom</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>io.smallrye</groupId>
+            <artifactId>smallrye-config</artifactId>
+            <version>1.3.6</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.microprofile.rest.client</groupId>
+            <artifactId>microprofile-rest-client-tck</artifactId>
+            <version>1.2.1</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.testng</groupId>
+            <artifactId>testng</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.github.tomakehurst</groupId>
+            <artifactId>wiremock</artifactId>
+            <version>2.21.0</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.arquillian.testng</groupId>
+            <artifactId>arquillian-testng-container</artifactId>
+            <version>1.4.1.Final</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.arquillian.container</groupId>
+            <artifactId>arquillian-container-test-spi</artifactId>
+            <version>1.4.1.Final</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.arquillian.container</groupId>
+            <artifactId>arquillian-weld-embedded</artifactId>
+            <version>2.0.1.Final</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>io.opentracing</groupId>
+            <artifactId>opentracing-noop</artifactId>
+            <version>0.30.0</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <!--<parallel>classes</parallel>
+                    <perCoreThreadCount>true</perCoreThreadCount>
+                    <threadCount>1</threadCount>
+                    <forkCount>1C</forkCount>
+                    <reuseForks>true</reuseForks>-->
+                    <suiteXmlFiles>
+                        <suiteXmlFile>tck-suite.xml</suiteXmlFile>
+                    </suiteXmlFiles>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>uk.co.deliverymind</groupId>
+                <artifactId>wiremock-maven-plugin</artifactId>
+                <version>2.7.0</version>
+                <executions>
+                    <execution>
+                        <phase>test-compile</phase>
+                        <goals>
+                            <goal>run</goal>
+                        </goals>
+                        <configuration>
+                            <dir>target/classes</dir>
+                            <params>--port=8765 --verbose</params>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <inherited>true</inherited>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Export-Package>
+                            org.glassfish.jersey.restclient.*;version=${project.version}
+                        </Export-Package>
+                        <Import-Package>
+                            *
+                        </Import-Package>
+                    </instructions>
+                    <unpackBundle>true</unpackBundle>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+<!--    <properties>-->
+<!--        <surefire.security.argline>-Djava.security.manager -Djava.security.policy=${project.build.directory}/test-classes/server.policy</surefire.security.argline>-->
+<!--    </properties>-->
+
+
+</project>
\ No newline at end of file
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/BeanClassModel.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/BeanClassModel.java
new file mode 100644
index 0000000..d5c4d74
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/BeanClassModel.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.microprofile.restclient;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.ws.rs.BeanParam;
+import javax.ws.rs.CookieParam;
+import javax.ws.rs.FormParam;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.MatrixParam;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Form;
+import javax.ws.rs.core.MultivaluedMap;
+
+import org.glassfish.jersey.model.Parameter;
+
+/**
+ * Model of method parameter annotated by {@link BeanParam} annotation.
+ *
+ * @author David Kral
+ */
+class BeanClassModel {
+
+    private final Class<?> beanClass;
+    private final List<ParamModel> parameterModels;
+
+    /**
+     * Create new instance of bean annotated parameter.
+     *
+     * @param interfaceModel rest client interface model
+     * @param beanClass bean annotated parameter class
+     * @return new instance
+     */
+    static BeanClassModel fromClass(InterfaceModel interfaceModel, Class<?> beanClass) {
+        return new Builder(interfaceModel, beanClass)
+                .processPathFields()
+                .processHeaderFields()
+                .processCookieFields()
+                .processQueryFields()
+                .processMatrixFields()
+                .build();
+    }
+
+    private BeanClassModel(Builder builder) {
+        this.beanClass = builder.beanClass;
+        this.parameterModels = builder.parameterModels;
+    }
+
+    /**
+     * List of all class fields annotated with supported parameter annotation
+     *
+     * @return parameter model list
+     */
+    List<ParamModel> getParameterModels() {
+        return parameterModels;
+    }
+
+    /**
+     * Resolves bean path parameters.
+     *
+     * @param webTarget web target path
+     * @param instance actual method parameter value
+     * @return updated web target path
+     */
+    @SuppressWarnings("unchecked")
+    WebTarget resolvePath(WebTarget webTarget, Object instance) {
+        AtomicReference<WebTarget> toReturn = new AtomicReference<>(webTarget);
+        parameterModels.stream()
+                .filter(paramModel -> paramModel.handles(PathParam.class))
+                .forEach(parameterModel -> {
+                    Field field = (Field) parameterModel.getAnnotatedElement();
+                    toReturn.set((WebTarget) parameterModel.handleParameter(webTarget,
+                                                                            PathParam.class,
+                                                                            resolveValueFromField(field, instance)));
+                });
+        return toReturn.get();
+    }
+
+    /**
+     * Resolves bean header parameters.
+     *
+     * @param headers headers
+     * @param instance actual method parameter value
+     * @return updated headers
+     */
+    @SuppressWarnings("unchecked")
+    MultivaluedMap<String, Object> resolveHeaders(MultivaluedMap<String, Object> headers,
+                                                  Object instance) {
+        parameterModels.stream()
+                .filter(paramModel -> paramModel.handles(HeaderParam.class))
+                .forEach(parameterModel -> {
+                    Field field = (Field) parameterModel.getAnnotatedElement();
+                    parameterModel.handleParameter(headers,
+                                                   HeaderParam.class,
+                                                   resolveValueFromField(field, instance));
+                });
+        return headers;
+    }
+
+    /**
+     * Resolves bean cookie parameters.
+     *
+     * @param cookies cookies
+     * @param instance actual method parameter value
+     * @return updated cookies
+     */
+    @SuppressWarnings("unchecked")
+    Map<String, String> resolveCookies(Map<String, String> cookies,
+                                       Object instance) {
+        parameterModels.stream()
+                .filter(paramModel -> paramModel.handles(CookieParam.class))
+                .forEach(parameterModel -> {
+                    Field field = (Field) parameterModel.getAnnotatedElement();
+                    parameterModel.handleParameter(cookies,
+                                                   CookieParam.class,
+                                                   resolveValueFromField(field, instance));
+                });
+        return cookies;
+    }
+
+    /**
+     * Resolves bean query parameters.
+     *
+     * @param query queries
+     * @param instance actual method parameter value
+     * @return updated queries
+     */
+    @SuppressWarnings("unchecked")
+    Map<String, Object[]> resolveQuery(Map<String, Object[]> query,
+                                              Object instance) {
+        parameterModels.stream()
+                .filter(paramModel -> paramModel.handles(QueryParam.class))
+                .forEach(parameterModel -> {
+                    Field field = (Field) parameterModel.getAnnotatedElement();
+                    parameterModel.handleParameter(query,
+                                                   QueryParam.class,
+                                                   resolveValueFromField(field, instance));
+                });
+        return query;
+    }
+
+    /**
+     * Resolves bean matrix parameters.
+     *
+     * @param webTarget web target path
+     * @param instance actual method parameter value
+     * @return updated web target path
+     */
+    @SuppressWarnings("unchecked")
+    WebTarget resolveMatrix(WebTarget webTarget,
+                                       Object instance) {
+        AtomicReference<WebTarget> toReturn = new AtomicReference<>(webTarget);
+        parameterModels.stream()
+                .filter(paramModel -> paramModel.handles(MatrixParam.class))
+                .forEach(parameterModel -> {
+                    Field field = (Field) parameterModel.getAnnotatedElement();
+                    toReturn.set((WebTarget) parameterModel.handleParameter(webTarget,
+                                                                            MatrixParam.class,
+                                                                            resolveValueFromField(field, instance)));
+                });
+        return toReturn.get();
+    }
+
+
+    /**
+     * Resolves bean form parameters.
+     *
+     * @param form web form
+     * @param instance actual method parameter value
+     * @return updated web form
+     */
+    @SuppressWarnings("unchecked")
+    Form resolveForm(Form form,
+                          Object instance) {
+        parameterModels.stream()
+                .filter(paramModel -> paramModel.handles(FormParam.class))
+                .forEach(parameterModel -> {
+                    Field field = (Field) parameterModel.getAnnotatedElement();
+                    parameterModel.handleParameter(form,
+                                                   FormParam.class,
+                                                   resolveValueFromField(field, instance));
+                });
+        return form;
+    }
+
+    private Object resolveValueFromField(Field field, Object instance) {
+        return AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
+            try {
+                Object toReturn;
+                if (field.isAccessible()) {
+                    return field.get(instance);
+                }
+                field.setAccessible(true);
+                toReturn = field.get(instance);
+                field.setAccessible(false);
+                return toReturn;
+            } catch (IllegalAccessException e) {
+                throw new RuntimeException(e);
+            }
+        });
+    }
+
+    private static class Builder {
+
+        private final InterfaceModel interfaceModel;
+        private final Class<?> beanClass;
+        private ArrayList<ParamModel> parameterModels = new ArrayList<>();
+
+        private Builder(InterfaceModel interfaceModel, Class<?> beanClass) {
+            this.interfaceModel = interfaceModel;
+            this.beanClass = beanClass;
+        }
+
+        /**
+         * Parses all {@link PathParam} annotated fields from bean class.
+         *
+         * @return updated builder instance
+         */
+        Builder processPathFields() {
+            return processFieldsByParameterClass(PathParam.class);
+        }
+
+        /**
+         * Parses all {@link HeaderParam} annotated fields from bean class.
+         *
+         * @return updated builder instance
+         */
+        Builder processHeaderFields() {
+            return processFieldsByParameterClass(HeaderParam.class);
+        }
+
+        /**
+         * Parses all {@link CookieParam} annotated fields from bean class.
+         *
+         * @return updated builder instance
+         */
+        Builder processCookieFields() {
+            return processFieldsByParameterClass(CookieParam.class);
+        }
+
+        /**
+         * Parses all {@link QueryParam} annotated fields from bean class.
+         *
+         * @return updated builder instance
+         */
+        Builder processQueryFields() {
+            return processFieldsByParameterClass(QueryParam.class);
+        }
+
+        /**
+         * Parses all {@link MatrixParam} annotated fields from bean class.
+         *
+         * @return updated builder instance
+         */
+        Builder processMatrixFields() {
+            return processFieldsByParameterClass(MatrixParam.class);
+        }
+
+        private Builder processFieldsByParameterClass(Class<? extends Annotation> parameterClass) {
+            for (Field field : beanClass.getDeclaredFields()) {
+                if (field.isAnnotationPresent(parameterClass)) {
+                    Parameter parameter = Parameter.create(parameterClass, parameterClass, false,
+                                                           field.getType(), field.getGenericType(),
+                                                           field.getDeclaredAnnotations());
+                    parameterModels.add(ParamModel.from(interfaceModel, field.getType(), field,
+                                                        parameter, -1));
+                }
+            }
+            return this;
+        }
+
+        /**
+         * Creates new BeanClassModel instance.
+         *
+         * @return new instance
+         */
+        BeanClassModel build() {
+            return new BeanClassModel(this);
+        }
+    }
+
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/BeanParamModel.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/BeanParamModel.java
new file mode 100644
index 0000000..348382e
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/BeanParamModel.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.microprofile.restclient;
+
+import java.lang.annotation.Annotation;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import javax.ws.rs.BeanParam;
+import javax.ws.rs.CookieParam;
+import javax.ws.rs.FormParam;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.MatrixParam;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Form;
+import javax.ws.rs.core.MultivaluedMap;
+
+/**
+ * Contains information about method parameter or class field which is annotated by {@link BeanParam}.
+ *
+ * @author David Kral
+ */
+class BeanParamModel extends ParamModel<Object> {
+
+    private BeanClassModel beanClassModel;
+
+    BeanParamModel(Builder builder) {
+        super(builder);
+        beanClassModel = BeanClassModel.fromClass(interfaceModel, (Class<?>) getType());
+    }
+
+    @Override
+    public Object handleParameter(Object requestPart, Class<?> annotationClass, Object instance) {
+        if (PathParam.class.equals(annotationClass)) {
+            return beanClassModel.resolvePath((WebTarget) requestPart, instance);
+        } else if (HeaderParam.class.equals(annotationClass)) {
+            return beanClassModel.resolveHeaders((MultivaluedMap<String, Object>) requestPart, instance);
+        } else if (CookieParam.class.equals(annotationClass)) {
+            return beanClassModel.resolveCookies((Map<String, String>) requestPart, instance);
+        } else if (QueryParam.class.equals(annotationClass)) {
+            return beanClassModel.resolveQuery((Map<String, Object[]>) requestPart, instance);
+        } else if (MatrixParam.class.equals(annotationClass)) {
+            return beanClassModel.resolveMatrix((WebTarget) requestPart, instance);
+        } else if (FormParam.class.equals(annotationClass)) {
+            return beanClassModel.resolveForm((Form) requestPart, instance);
+        }
+        throw new UnsupportedOperationException(annotationClass.getName() + " is not supported!");
+    }
+
+    @Override
+    public boolean handles(Class<Annotation> annotation) {
+        return PathParam.class.equals(annotation)
+                || HeaderParam.class.equals(annotation)
+                || CookieParam.class.equals(annotation)
+                || QueryParam.class.equals(annotation)
+                || MatrixParam.class.equals(annotation)
+                || FormParam.class.equals(annotation);
+    }
+
+    /**
+     * Returns {@link List} of all parameters annotated by searched annotation.
+     *
+     * @param paramAnnotation searched annotation
+     * @return filtered list
+     */
+    List<ParamModel> getAllParamsWithType(Class<? extends Annotation> paramAnnotation) {
+        return beanClassModel.getParameterModels().stream()
+                .filter(paramModel -> paramModel.handles(paramAnnotation))
+                .collect(Collectors.toList());
+    }
+
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/ClientHeaderParamModel.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/ClientHeaderParamModel.java
new file mode 100644
index 0000000..fa5d3af
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/ClientHeaderParamModel.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.microprofile.restclient;
+
+import java.lang.reflect.Method;
+
+import org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam;
+
+/**
+ * Contains information about method annotation {@link ClientHeaderParam}.
+ *
+ * @author David Kral
+ */
+class ClientHeaderParamModel {
+
+    private final String headerName;
+    private final String[] headerValue;
+    private final Method computeMethod;
+    private final boolean required;
+
+    ClientHeaderParamModel(Class<?> iClass, ClientHeaderParam clientHeaderParam) {
+        headerName = clientHeaderParam.name();
+        headerValue = clientHeaderParam.value();
+        computeMethod = InterfaceUtil.parseComputeMethod(iClass, headerValue);
+        required = clientHeaderParam.required();
+    }
+
+    /**
+     * Returns header name.
+     *
+     * @return header name
+     */
+    String getHeaderName() {
+        return headerName;
+    }
+
+    /**
+     * Returns header value.
+     *
+     * @return header value
+     */
+    String[] getHeaderValue() {
+        return headerValue;
+    }
+
+    /**
+     * Returns method which is used to compute header value.
+     *
+     * @return compute method
+     */
+    Method getComputeMethod() {
+        return computeMethod;
+    }
+
+    /**
+     * Returns true if header is required and false if not. It header is not required and exception
+     * is thrown during compute method invocation, this header will be ignored and not included to request.
+     *
+     * @return if header is required
+     */
+    boolean isRequired() {
+        return required;
+    }
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/ConfigWrapper.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/ConfigWrapper.java
new file mode 100644
index 0000000..466ab38
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/ConfigWrapper.java
@@ -0,0 +1,91 @@
+package org.glassfish.jersey.microprofile.restclient;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.Feature;
+
+/**
+ * Configuration wrapper for {@link Configuration}. This class is needed due to custom provider registrations.
+ *
+ * @author David Kral
+ */
+class ConfigWrapper implements Configuration {
+
+    private final Configuration jerseyBuilderConfig;
+    private final Map<Class<?>, Map<Class<?>, Integer>> customProviders;
+
+    ConfigWrapper(Configuration jerseyBuilderConfig) {
+        this.jerseyBuilderConfig = jerseyBuilderConfig;
+        this.customProviders = new HashMap<>();
+    }
+
+    void addCustomProvider(Class<?> provider, Map<Class<?>, Integer> contracts) {
+        if (customProviders.containsKey(provider)) {
+            customProviders.get(provider).putAll(contracts);
+        } else {
+            customProviders.put(provider, contracts);
+        }
+    }
+
+    @Override
+    public RuntimeType getRuntimeType() {
+        return jerseyBuilderConfig.getRuntimeType();
+    }
+
+    @Override
+    public Map<String, Object> getProperties() {
+        return jerseyBuilderConfig.getProperties();
+    }
+
+    @Override
+    public Object getProperty(String name) {
+        return jerseyBuilderConfig.getProperty(name);
+    }
+
+    @Override
+    public Collection<String> getPropertyNames() {
+        return jerseyBuilderConfig.getPropertyNames();
+    }
+
+    @Override
+    public boolean isEnabled(Feature feature) {
+        return jerseyBuilderConfig.isEnabled(feature);
+    }
+
+    @Override
+    public boolean isEnabled(Class<? extends Feature> featureClass) {
+        return jerseyBuilderConfig.isEnabled(featureClass);
+    }
+
+    @Override
+    public boolean isRegistered(Object component) {
+        return jerseyBuilderConfig.isRegistered(component);
+    }
+
+    @Override
+    public boolean isRegistered(Class<?> componentClass) {
+        return jerseyBuilderConfig.isRegistered(componentClass);
+    }
+
+    @Override
+    public Map<Class<?>, Integer> getContracts(Class<?> componentClass) {
+        Map<Class<?>, Integer> map = new HashMap<>(jerseyBuilderConfig.getContracts(componentClass));
+        if (customProviders.containsKey(componentClass)) map.putAll(customProviders.get(componentClass));
+        return map;
+    }
+
+    @Override
+    public Set<Class<?>> getClasses() {
+        return jerseyBuilderConfig.getClasses();
+    }
+
+    @Override
+    public Set<Object> getInstances() {
+        return jerseyBuilderConfig.getInstances();
+    }
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/CookieParamModel.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/CookieParamModel.java
new file mode 100644
index 0000000..9477c34
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/CookieParamModel.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.microprofile.restclient;
+
+import java.lang.annotation.Annotation;
+import java.util.Map;
+
+import javax.ws.rs.CookieParam;
+
+/**
+ * Contains information about method parameter or class field which is annotated by {@link CookieParam}.
+ *
+ * @author David Kral
+ */
+class CookieParamModel extends ParamModel<Map<String, String>> {
+
+    private final String cookieParamName;
+
+    CookieParamModel(Builder builder) {
+        super(builder);
+        cookieParamName = builder.cookieParamName();
+    }
+
+    @Override
+    Map<String, String> handleParameter(Map<String, String> requestPart, Class<?> annotationClass, Object instance) {
+        Object resolvedValue = interfaceModel.resolveParamValue(instance, parameter);
+        requestPart.put(cookieParamName, (String) resolvedValue);
+        return requestPart;
+    }
+
+    @Override
+    boolean handles(Class<Annotation> annotation) {
+        return CookieParam.class.equals(annotation);
+    }
+
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/DefaultResponseExceptionMapper.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/DefaultResponseExceptionMapper.java
new file mode 100644
index 0000000..b699182
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/DefaultResponseExceptionMapper.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.microprofile.restclient;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Response;
+
+import org.eclipse.microprofile.rest.client.ext.ResponseExceptionMapper;
+
+/**
+ * Default {@link ResponseExceptionMapper} implementation
+ *
+ * @author David Kral
+ */
+public class DefaultResponseExceptionMapper implements ResponseExceptionMapper {
+    @Override
+    public Throwable toThrowable(Response response) {
+        return new WebApplicationException("Unknown error, status code " + response.getStatus(), response);
+    }
+
+    @Override
+    public int getPriority() {
+        return Integer.MAX_VALUE;
+    }
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/ExecutorServiceWrapper.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/ExecutorServiceWrapper.java
new file mode 100644
index 0000000..c3a8dd6
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/ExecutorServiceWrapper.java
@@ -0,0 +1,118 @@
+package org.glassfish.jersey.microprofile.restclient;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.stream.Collectors;
+
+import org.eclipse.microprofile.rest.client.ext.AsyncInvocationInterceptor;
+
+/**
+ * Invokes all {@link AsyncInvocationInterceptor} for every new thread.
+ *
+ * @author David Kral
+ */
+class ExecutorServiceWrapper implements ExecutorService {
+
+    private final ExecutorService wrapped;
+    private final List<AsyncInvocationInterceptor> asyncInterceptors;
+
+    ExecutorServiceWrapper(ExecutorService wrapped,
+                           List<AsyncInvocationInterceptor> asyncInterceptors) {
+        this.wrapped = wrapped;
+        this.asyncInterceptors = asyncInterceptors;
+    }
+
+    @Override
+    public void shutdown() {
+        wrapped.shutdown();
+    }
+
+    @Override
+    public List<Runnable> shutdownNow() {
+        return wrapped.shutdownNow();
+    }
+
+    @Override
+    public boolean isShutdown() {
+        return wrapped.isShutdown();
+    }
+
+    @Override
+    public boolean isTerminated() {
+        return wrapped.isTerminated();
+    }
+
+    @Override
+    public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
+        return wrapped.awaitTermination(timeout, unit);
+    }
+
+    @Override
+    public <T> Future<T> submit(Callable<T> task) {
+        return wrapped.submit(wrap(task));
+    }
+
+    @Override
+    public <T> Future<T> submit(Runnable task, T result) {
+        return wrapped.submit(wrap(task), result);
+    }
+
+    @Override
+    public Future<?> submit(Runnable task) {
+        return wrapped.submit(wrap(task));
+    }
+
+    @Override
+    public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException {
+        return wrapped.invokeAll(wrap(tasks));
+    }
+
+    @Override
+    public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
+            throws InterruptedException {
+        return wrapped.invokeAll(wrap(tasks), timeout, unit);
+    }
+
+    @Override
+    public <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException {
+        return wrapped.invokeAny(wrap(tasks));
+    }
+
+    @Override
+    public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
+            throws InterruptedException, ExecutionException, TimeoutException {
+        return wrapped.invokeAny(wrap(tasks), timeout, unit);
+    }
+
+    @Override
+    public void execute(Runnable command) {
+        wrapped.execute(wrap(command));
+    }
+
+    private <T> Callable<T> wrap(Callable<T> task) {
+        return () -> {
+            asyncInterceptors.forEach(AsyncInvocationInterceptor::applyContext);
+            return task.call();
+        };
+    }
+
+    private Runnable wrap(Runnable task) {
+        return () -> {
+            asyncInterceptors.forEach(AsyncInvocationInterceptor::applyContext);
+            task.run();
+        };
+    }
+
+
+    private <T> Collection<? extends Callable<T>> wrap(Collection<? extends Callable<T>> tasks) {
+        return tasks.stream()
+                .map(this::wrap)
+                .collect(Collectors.toList());
+    }
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/FormParamModel.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/FormParamModel.java
new file mode 100644
index 0000000..7a8f738
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/FormParamModel.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.microprofile.restclient;
+
+import java.lang.annotation.Annotation;
+import java.util.Collection;
+
+import javax.ws.rs.FormParam;
+import javax.ws.rs.core.Form;
+
+/**
+ * Contains information about method parameter or class field which is annotated by {@link FormParam}.
+ *
+ * @author David Kral
+ */
+class FormParamModel extends ParamModel<Form> {
+
+    private final String formParamName;
+
+    FormParamModel(Builder builder) {
+        super(builder);
+        formParamName = builder.formParamName();
+    }
+
+    @Override
+    Form handleParameter(Form form, Class<?> annotationClass, Object instance) {
+        Object resolvedValue = interfaceModel.resolveParamValue(instance, parameter);
+        if (resolvedValue instanceof Collection) {
+            for (final Object v : ((Collection) resolvedValue)) {
+                form.param(formParamName, v.toString());
+            }
+        } else {
+            form.param(formParamName, resolvedValue.toString());
+        }
+        return form;
+    }
+
+    @Override
+    boolean handles(Class<Annotation> annotation) {
+        return FormParam.class.equals(annotation);
+    }
+
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/HeaderParamModel.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/HeaderParamModel.java
new file mode 100644
index 0000000..3417ada
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/HeaderParamModel.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.microprofile.restclient;
+
+import java.lang.annotation.Annotation;
+import java.util.Collections;
+
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.core.MultivaluedMap;
+
+/**
+ * Contains information about method parameter or class field which is annotated by {@link HeaderParam}.
+ *
+ * @author David Kral
+ */
+class HeaderParamModel extends ParamModel<MultivaluedMap<String, Object>> {
+
+    private String headerParamName;
+
+    HeaderParamModel(Builder builder) {
+        super(builder);
+        this.headerParamName = builder.headerParamName();
+    }
+
+    @Override
+    MultivaluedMap<String, Object> handleParameter(MultivaluedMap<String, Object> requestPart,
+                                                          Class<?> annotationClass, Object instance) {
+        Object resolvedValue = interfaceModel.resolveParamValue(instance, parameter);
+        requestPart.put(headerParamName, Collections.singletonList(resolvedValue));
+        return requestPart;
+    }
+
+    @Override
+    boolean handles(Class<Annotation> annotation) {
+        return HeaderParam.class.equals(annotation);
+    }
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/HeadersContext.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/HeadersContext.java
new file mode 100644
index 0000000..77bcffe
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/HeadersContext.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.microprofile.restclient;
+
+import java.util.Optional;
+import java.util.function.Supplier;
+
+import javax.ws.rs.core.MultivaluedMap;
+
+/**
+ *
+ * @author David Kral
+ */
+public final class HeadersContext {
+
+    /**
+     * Headers context thread local, used by internal implementations of header filters.
+     */
+    private static final ThreadLocal<HeadersContext> HEADERS_CONTEXT = new ThreadLocal<>();
+
+    private final MultivaluedMap<String, String> inboundHeaders;
+
+    /**
+     * The instance associated with the current thread.
+     * @return context for current thread or {@code empty} if none associated
+     */
+    public static Optional<HeadersContext> get() {
+        return Optional.ofNullable(HEADERS_CONTEXT.get());
+    }
+
+    /**
+     * Computes the instance and associates it with current thread if none
+     * associated, or returns the instance already associated.
+     *
+     * @param contextSupplier supplier for header context to be associated with the thread if none is
+     * @return an instance associated with the current context, either from other provider, or from contextSupplier
+     */
+    public static HeadersContext compute(Supplier<HeadersContext> contextSupplier) {
+        HeadersContext headersContext = HEADERS_CONTEXT.get();
+        if (null == headersContext) {
+            set(contextSupplier.get());
+        }
+
+        return get().orElseThrow(() -> new IllegalStateException("Computed result was null"));
+    }
+
+    /**
+     * Set the header context to be associated with current thread.
+     *
+     * @param context context to associate
+     */
+    public static void set(HeadersContext context) {
+        HEADERS_CONTEXT.set(context);
+    }
+
+    /**
+     * Remove the header context associated with current thread.
+     */
+    public static void remove() {
+        HEADERS_CONTEXT.remove();
+    }
+
+    /**
+     * Create a new header context with client tracing enabled.
+     *
+     * @param inboundHeaders inbound header to be used for context propagation
+     * @return a new header context (not associated with current thread)
+     * @see #set(HeadersContext)
+     */
+    public static HeadersContext create(MultivaluedMap<String, String> inboundHeaders) {
+        return new HeadersContext(inboundHeaders);
+    }
+
+    public HeadersContext(MultivaluedMap<String, String> inboundHeaders) {
+        this.inboundHeaders = inboundHeaders;
+    }
+
+    /**
+     * Map of headers that were received by server for an inbound call,
+     * may be used to propagate additional headers fro outbound request.
+     *
+     * @return map of inbound headers
+     */
+    public MultivaluedMap<String, String> inboundHeaders() {
+        return inboundHeaders;
+    }
+
+
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/HeadersRequestFilter.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/HeadersRequestFilter.java
new file mode 100644
index 0000000..64ece19
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/HeadersRequestFilter.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.microprofile.restclient;
+
+import javax.ws.rs.ConstrainedTo;
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerRequestFilter;
+
+/**
+ * Server side request filter used for propagation of request headers to server client request.
+ *
+ * @author David Kral
+ */
+@ConstrainedTo(RuntimeType.SERVER)
+public class HeadersRequestFilter implements ContainerRequestFilter {
+
+    @Override
+    public void filter(ContainerRequestContext requestContext) {
+        HeadersContext.compute(() -> HeadersContext.create(requestContext.getHeaders()));
+    }
+
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/InterceptorInvocationContext.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/InterceptorInvocationContext.java
new file mode 100644
index 0000000..d559fb7
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/InterceptorInvocationContext.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.microprofile.restclient;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.enterprise.inject.spi.InterceptionType;
+import javax.enterprise.inject.spi.Interceptor;
+import javax.interceptor.InvocationContext;
+import javax.ws.rs.client.WebTarget;
+
+/**
+ * Invokes all interceptors bound to the target.
+ *
+ * This approach needs to be used due to CDI does not handle properly interceptor invocation
+ * on proxy instances.
+ *
+ * @author David Kral
+ */
+class InterceptorInvocationContext implements InvocationContext {
+
+    private final MethodModel methodModel;
+    private final Method method;
+    private final Map<String, Object> contextData;
+    private final List<InvocationInterceptor> interceptors;
+    private final WebTarget classLevelWebTarget;
+    private Object[] args;
+    private int currentPosition;
+
+    /**
+     * Creates new instance of InterceptorInvocationContext.
+     *
+     * @param classLevelWebTarget class level web target
+     * @param methodModel method model
+     * @param method reflection method
+     * @param args actual method arguments
+     */
+    InterceptorInvocationContext(WebTarget classLevelWebTarget,
+                                 MethodModel methodModel,
+                                 Method method,
+                                 Object[] args) {
+        this.contextData = new HashMap<>();
+        this.currentPosition = 0;
+        this.methodModel = methodModel;
+        this.method = method;
+        this.args = args;
+        this.classLevelWebTarget = classLevelWebTarget;
+        this.interceptors = methodModel.getInvocationInterceptors();
+
+    }
+
+    @Override
+    public Object getTarget() {
+        return methodModel;
+    }
+
+    @Override
+    public Object getTimer() {
+        return null;
+    }
+
+    @Override
+    public Method getMethod() {
+        return method;
+    }
+
+    @Override
+    public Constructor<?> getConstructor() {
+        return null;
+    }
+
+    @Override
+    public Object[] getParameters() {
+        return args;
+    }
+
+    @Override
+    public void setParameters(Object[] params) {
+        this.args = params;
+    }
+
+    @Override
+    public Map<String, Object> getContextData() {
+        return contextData;
+    }
+
+    @Override
+    public Object proceed() {
+        if (currentPosition < interceptors.size()) {
+            return interceptors.get(currentPosition++).intercept(this);
+        } else {
+            return methodModel.invokeMethod(classLevelWebTarget, method, args);
+        }
+    }
+
+    /**
+     * Contains actual interceptor instance and interceptor itself.
+     */
+    static class InvocationInterceptor {
+
+        private final Object interceptorInstance;
+        private final Interceptor interceptor;
+
+        InvocationInterceptor(Object interceptorInstance, Interceptor interceptor) {
+            this.interceptorInstance = interceptorInstance;
+            this.interceptor = interceptor;
+        }
+
+        /**
+         * Invokes interceptor with interception type AROUND_INVOKE.
+         *
+         * @param ctx invocation context
+         * @return interception result
+         */
+        @SuppressWarnings("unchecked")
+        Object intercept(InvocationContext ctx) {
+            try {
+                return interceptor.intercept(InterceptionType.AROUND_INVOKE, interceptorInstance, ctx);
+            } catch (Exception e) {
+                if (e instanceof RuntimeException) {
+                    throw (RuntimeException) e;
+                }
+                throw new RuntimeException(e);
+            }
+        }
+    }
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/InterfaceModel.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/InterfaceModel.java
new file mode 100644
index 0000000..dad9c5e
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/InterfaceModel.java
@@ -0,0 +1,366 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.microprofile.restclient;
+
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import javax.enterprise.context.spi.CreationalContext;
+import javax.enterprise.inject.spi.BeanManager;
+import javax.enterprise.inject.spi.CDI;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.ext.ParamConverterProvider;
+
+import org.eclipse.microprofile.rest.client.RestClientDefinitionException;
+import org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam;
+import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
+import org.eclipse.microprofile.rest.client.ext.AsyncInvocationInterceptor;
+import org.eclipse.microprofile.rest.client.ext.ClientHeadersFactory;
+import org.eclipse.microprofile.rest.client.ext.ResponseExceptionMapper;
+import org.glassfish.jersey.client.inject.ParameterInserter;
+import org.glassfish.jersey.client.inject.ParameterInserterProvider;
+import org.glassfish.jersey.internal.inject.InjectionManager;
+import org.glassfish.jersey.internal.inject.Providers;
+import org.glassfish.jersey.model.Parameter;
+
+/**
+ * Model of interface and its annotation.
+ *
+ * @author David Kral
+ */
+class InterfaceModel {
+
+    private final InjectionManager injectionManager;
+    private final Class<?> restClientClass;
+    private final String[] produces;
+    private final String[] consumes;
+    private final String path;
+    private final ClientHeadersFactory clientHeadersFactory;
+    private final CreationalContext<?> creationalContext;
+
+    private final List<ClientHeaderParamModel> clientHeaders;
+    private final List<AsyncInvocationInterceptor> asyncInterceptors;
+    private final Set<ResponseExceptionMapper> responseExceptionMappers;
+    private final Set<ParamConverterProvider> paramConverterProviders;
+    private final Set<Annotation> interceptorAnnotations;
+
+    /**
+     * Creates new model based on interface class. Interface is parsed according to specific annotations.
+     *
+     * @param restClientClass interface class
+     * @param responseExceptionMappers registered exception mappers
+     * @param paramConverterProviders registered parameter providers
+     * @param asyncInterceptors async interceptors
+     * @param injectionManager
+     * @return new model instance
+     */
+    static InterfaceModel from(Class<?> restClientClass,
+                               Set<ResponseExceptionMapper> responseExceptionMappers,
+                               Set<ParamConverterProvider> paramConverterProviders,
+                               List<AsyncInvocationInterceptor> asyncInterceptors,
+                               InjectionManager injectionManager) {
+        return new Builder(restClientClass,
+                           responseExceptionMappers,
+                           paramConverterProviders,
+                           asyncInterceptors,
+                           injectionManager)
+                .pathValue(restClientClass.getAnnotation(Path.class))
+                .produces(restClientClass.getAnnotation(Produces.class))
+                .consumes(restClientClass.getAnnotation(Consumes.class))
+                .clientHeaders(restClientClass.getAnnotationsByType(ClientHeaderParam.class))
+                .clientHeadersFactory(restClientClass.getAnnotation(RegisterClientHeaders.class))
+                .build();
+    }
+
+    private InterfaceModel(Builder builder) {
+        this.injectionManager = builder.injectionManager;
+        this.restClientClass = builder.restClientClass;
+        this.path = builder.pathValue;
+        this.produces = builder.produces;
+        this.consumes = builder.consumes;
+        this.clientHeaders = builder.clientHeaders;
+        this.clientHeadersFactory = builder.clientHeadersFactory;
+        this.responseExceptionMappers = builder.responseExceptionMappers;
+        this.paramConverterProviders = builder.paramConverterProviders;
+        this.interceptorAnnotations = builder.interceptorAnnotations;
+        this.creationalContext = builder.creationalContext;
+        this.asyncInterceptors = builder.asyncInterceptors;
+    }
+
+    /**
+     * Returns rest client interface class.
+     *
+     * @return interface class
+     */
+    Class<?> getRestClientClass() {
+        return restClientClass;
+    }
+
+    /**
+     * Returns defined produces media types.
+     *
+     * @return produces
+     */
+    String[] getProduces() {
+        return produces;
+    }
+
+    /**
+     * Returns defined consumes media types.
+     *
+     * @return consumes
+     */
+    String[] getConsumes() {
+        return consumes;
+    }
+
+    /**
+     * Returns path value defined on interface level.
+     *
+     * @return path value
+     */
+    String getPath() {
+        return path;
+    }
+
+    /**
+     * Returns registered instance of {@link ClientHeadersFactory}.
+     *
+     * @return registered factory
+     */
+    Optional<ClientHeadersFactory> getClientHeadersFactory() {
+        return Optional.ofNullable(clientHeadersFactory);
+    }
+
+    /**
+     * Returns {@link List} of processed annotation {@link ClientHeaderParam} to {@link ClientHeaderParamModel}
+     *
+     * @return registered factories
+     */
+    List<ClientHeaderParamModel> getClientHeaders() {
+        return clientHeaders;
+    }
+
+    /**
+     * Returns {@link List} of registered {@link AsyncInvocationInterceptor}
+     *
+     * @return registered async interceptors
+     */
+    List<AsyncInvocationInterceptor> getAsyncInterceptors() {
+        return asyncInterceptors;
+    }
+
+    /**
+     * Returns {@link Set} of registered {@link ResponseExceptionMapper}
+     *
+     * @return registered exception mappers
+     */
+    Set<ResponseExceptionMapper> getResponseExceptionMappers() {
+        return responseExceptionMappers;
+    }
+
+    /**
+     * Returns {@link Set} of registered {@link ParamConverterProvider}
+     *
+     * @return registered param converter providers
+     */
+    Set<ParamConverterProvider> getParamConverterProviders() {
+        return paramConverterProviders;
+    }
+
+    /**
+     * Returns {@link Set} of interceptor annotations
+     *
+     * @return interceptor annotations
+     */
+    Set<Annotation> getInterceptorAnnotations() {
+        return interceptorAnnotations;
+    }
+
+    /**
+     * Context bound to this model.
+     *
+     * @return context
+     */
+    CreationalContext<?> getCreationalContext() {
+        return creationalContext;
+    }
+
+    /**
+     *
+     *
+     * @return
+     */
+    public InjectionManager getInjectionManager() {
+        return injectionManager;
+    }
+
+    /**
+     * Resolves value of the method argument.
+     *
+     * @param arg actual argument value
+     * @return converted value of argument
+     */
+    Object resolveParamValue(Object arg, Parameter parameter) {
+        final Iterable<ParameterInserterProvider> parameterInserterProviders
+                = Providers.getAllProviders(injectionManager, ParameterInserterProvider.class);
+        for (final ParameterInserterProvider parameterInserterProvider : parameterInserterProviders) {
+            if (parameterInserterProvider != null) {
+                ParameterInserter<Object, Object> inserter =
+                        (ParameterInserter<Object, Object>) parameterInserterProvider.get(parameter);
+                return inserter.insert(arg);
+            }
+        }
+        return arg;
+    }
+
+    private static class Builder {
+
+        private final Class<?> restClientClass;
+
+        private final InjectionManager injectionManager;
+        private String pathValue;
+        private String[] produces;
+        private String[] consumes;
+        private ClientHeadersFactory clientHeadersFactory;
+        private CreationalContext<?> creationalContext;
+        private List<ClientHeaderParamModel> clientHeaders;
+        private List<AsyncInvocationInterceptor> asyncInterceptors;
+        private Set<ResponseExceptionMapper> responseExceptionMappers;
+        private Set<ParamConverterProvider> paramConverterProviders;
+        private Set<Annotation> interceptorAnnotations;
+
+        private Builder(Class<?> restClientClass,
+                        Set<ResponseExceptionMapper> responseExceptionMappers,
+                        Set<ParamConverterProvider> paramConverterProviders,
+                        List<AsyncInvocationInterceptor> asyncInterceptors,
+                        InjectionManager injectionManager) {
+            this.injectionManager = injectionManager;
+            this.restClientClass = restClientClass;
+            this.responseExceptionMappers = responseExceptionMappers;
+            this.paramConverterProviders = paramConverterProviders;
+            this.asyncInterceptors = asyncInterceptors;
+            filterAllInterceptorAnnotations();
+        }
+
+        private void filterAllInterceptorAnnotations() {
+            creationalContext = null;
+            interceptorAnnotations = new HashSet<>();
+            try {
+                if (CDI.current() != null) {
+                    BeanManager beanManager = CDI.current().getBeanManager();
+                    creationalContext = beanManager.createCreationalContext(null);
+                    for (Annotation annotation : restClientClass.getAnnotations()) {
+                        if (beanManager.isInterceptorBinding(annotation.annotationType())) {
+                            interceptorAnnotations.add(annotation);
+                        }
+                    }
+                }
+            } catch (IllegalStateException ignored) {
+                //CDI not present. Ignore.
+            }
+        }
+
+        /**
+         * Path value from {@link Path} annotation. If annotation is null, empty String is set as path.
+         *
+         * @param path {@link Path} annotation
+         * @return updated Builder instance
+         */
+        Builder pathValue(Path path) {
+            this.pathValue = path != null ? path.value() : "";
+            //if only / is added to path like this "localhost:80/test" it makes invalid path "localhost:80/test/"
+            this.pathValue = pathValue.equals("/") ? "" : pathValue;
+            return this;
+        }
+
+        /**
+         * Extracts MediaTypes from {@link Produces} annotation.
+         * If annotation is null, new String array with {@link MediaType#WILDCARD} is set.
+         *
+         * @param produces {@link Produces} annotation
+         * @return updated Builder instance
+         */
+        Builder produces(Produces produces) {
+            this.produces = produces != null ? produces.value() : new String[] {MediaType.WILDCARD};
+            return this;
+        }
+
+        /**
+         * Extracts MediaTypes from {@link Consumes} annotation.
+         * If annotation is null, new String array with {@link MediaType#WILDCARD} is set.
+         *
+         * @param consumes {@link Consumes} annotation
+         * @return updated Builder instance
+         */
+        Builder consumes(Consumes consumes) {
+            this.consumes = consumes != null ? consumes.value() : new String[] {MediaType.WILDCARD};
+            return this;
+        }
+
+        /**
+         * Process data from {@link ClientHeaderParam} annotation to extract methods and values.
+         *
+         * @param clientHeaderParams {@link ClientHeaderParam} annotations
+         * @return updated Builder instance
+         */
+        Builder clientHeaders(ClientHeaderParam[] clientHeaderParams) {
+            clientHeaders = Arrays.stream(clientHeaderParams)
+                    .map(clientHeaderParam -> new ClientHeaderParamModel(restClientClass, clientHeaderParam))
+                    .collect(Collectors.toList());
+            return this;
+        }
+
+        Builder clientHeadersFactory(RegisterClientHeaders registerClientHeaders) {
+            clientHeadersFactory = registerClientHeaders != null
+                    ? ReflectionUtil.createInstance(registerClientHeaders.value())
+                    : null;
+            return this;
+        }
+
+        /**
+         * Creates new InterfaceModel instance.
+         *
+         * @return new instance
+         */
+        InterfaceModel build() {
+            validateHeaderDuplicityNames();
+            return new InterfaceModel(this);
+        }
+
+        private void validateHeaderDuplicityNames() {
+            ArrayList<String> names = new ArrayList<>();
+            for (ClientHeaderParamModel clientHeaderParamModel : clientHeaders) {
+                String headerName = clientHeaderParamModel.getHeaderName();
+                if (names.contains(headerName)) {
+                    throw new RestClientDefinitionException("Header name cannot be registered more then once on the same target."
+                                                                    + "See " + restClientClass.getName());
+                }
+                names.add(headerName);
+            }
+        }
+    }
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/InterfaceUtil.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/InterfaceUtil.java
new file mode 100644
index 0000000..83aa97a
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/InterfaceUtil.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.microprofile.restclient;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.security.AccessController;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import javax.ws.rs.HttpMethod;
+
+import org.eclipse.microprofile.rest.client.RestClientDefinitionException;
+import org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam;
+import org.glassfish.jersey.internal.util.ReflectionHelper;
+
+/**
+ * Utils for interface handling.
+ *
+ * @author David Kral
+ */
+class InterfaceUtil {
+
+    private static final String PARAMETER_PARSE_REGEXP = "(?<=\\{).+?(?=\\})";
+    private static final Pattern PATTERN = Pattern.compile(PARAMETER_PARSE_REGEXP);
+
+    /**
+     * Parses all required parameters from template string.
+     *
+     * @param template template string
+     * @return parsed parameters
+     */
+    static List<String> parseParameters(String template) {
+        List<String> allMatches = new ArrayList<>();
+        Matcher m = PATTERN.matcher(template);
+        while (m.find()) {
+            allMatches.add(m.group());
+        }
+        return allMatches;
+    }
+
+    /**
+     * Validates and returns proper compute method defined in {@link ClientHeaderParam}.
+     *
+     * @param iClass interface class
+     * @param headerValue value of the header
+     * @return parsed method
+     */
+    static Method parseComputeMethod(Class<?> iClass, String[] headerValue) {
+        List<String> computeMethodNames = InterfaceUtil.parseParameters(Arrays.toString(headerValue));
+        /*if more than one string is specified as the value attribute, and one of the strings is a
+          compute method (surrounded by curly braces), then the implementation will throw a
+          RestClientDefinitionException*/
+        if (headerValue.length > 1 && computeMethodNames.size() > 0) {
+            throw new RestClientDefinitionException("@ClientHeaderParam annotation should not contain compute method "
+                                                            + "when multiple values are present in value attribute. "
+                                                            + "See " + iClass.getName());
+        }
+        if (computeMethodNames.size() == 1) {
+            String methodName = computeMethodNames.get(0);
+            List<Method> computeMethods = getAnnotationComputeMethod(iClass, methodName);
+            if (computeMethods.size() != 1) {
+                throw new RestClientDefinitionException("No valid compute method found for name: " + methodName);
+            }
+            return computeMethods.get(0);
+        }
+        return null;
+    }
+
+    private static List<Method> getAnnotationComputeMethod(Class<?> iClass, String methodName) {
+        if (methodName.contains(".")) {
+            return getStaticComputeMethod(methodName);
+        }
+        return getComputeMethod(iClass, methodName);
+    }
+
+    private static List<Method> getStaticComputeMethod(String methodName) {
+        int lastIndex = methodName.lastIndexOf(".");
+        String className = methodName.substring(0, lastIndex);
+        String staticMethodName = methodName.substring(lastIndex + 1);
+        Class<?> classWithStaticMethod = AccessController.doPrivileged(ReflectionHelper.classForNamePA(className));
+        if (classWithStaticMethod == null) {
+            throw new IllegalStateException("No class with following name found: " + className);
+        }
+        return getComputeMethod(classWithStaticMethod, staticMethodName);
+    }
+
+    private static List<Method> getComputeMethod(Class<?> iClass, String methodName) {
+        return Arrays.stream(iClass.getMethods())
+                // filter out methods with specified name only
+                .filter(method -> method.getName().equals(methodName))
+                // filter out other methods than default and static
+                .filter(method -> method.isDefault() || Modifier.isStatic(method.getModifiers()))
+                // filter out methods without required return type
+                .filter(method -> method.getReturnType().equals(String.class)
+                        || method.getReturnType().equals(String[].class))
+                // filter out methods without required parameter types
+                .filter(method -> method.getParameterTypes().length == 0 || (
+                        method.getParameterTypes().length == 1
+                                && method.getParameterTypes()[0].equals(String.class)))
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * Returns {@link List} of annotations which are type of {@link HttpMethod}.
+     *
+     * @param annotatedElement element with annotations
+     * @return annotations of given type
+     */
+    static List<Class<?>> getHttpAnnotations(AnnotatedElement annotatedElement) {
+        return Arrays.stream(annotatedElement.getDeclaredAnnotations())
+                .filter(annotation -> annotation.annotationType().getAnnotation(HttpMethod.class) != null)
+                .map(Annotation::annotationType)
+                .collect(Collectors.toList());
+    }
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/JerseyRestClientBuilderResolver.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/JerseyRestClientBuilderResolver.java
new file mode 100644
index 0000000..db8e918
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/JerseyRestClientBuilderResolver.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.microprofile.restclient;
+
+import org.eclipse.microprofile.rest.client.RestClientBuilder;
+import org.eclipse.microprofile.rest.client.spi.RestClientBuilderResolver;
+
+/**
+ *
+ *
+ * @author David Kral
+ */
+public class JerseyRestClientBuilderResolver extends RestClientBuilderResolver {
+    @Override
+    public RestClientBuilder newBuilder() {
+        return new RestClientBuilderImpl();
+    }
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/MatrixParamModel.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/MatrixParamModel.java
new file mode 100644
index 0000000..440a414
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/MatrixParamModel.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.microprofile.restclient;
+
+import java.lang.annotation.Annotation;
+import java.util.Collection;
+
+import javax.ws.rs.MatrixParam;
+import javax.ws.rs.client.WebTarget;
+
+/**
+ * Contains information to method parameter which is annotated by {@link MatrixParam}.
+ *
+ * @author David Kral
+ */
+class MatrixParamModel extends ParamModel<WebTarget> {
+
+    private final String matrixParamName;
+
+    /**
+     * Creates new matrix model.
+     *
+     * @param builder
+     */
+    MatrixParamModel(Builder builder) {
+        super(builder);
+        matrixParamName = builder.matrixParamName();
+    }
+
+    @Override
+    public WebTarget handleParameter(WebTarget requestPart, Class<?> annotationClass, Object instance) {
+        Object resolvedValue = interfaceModel.resolveParamValue(instance, parameter);
+        if (resolvedValue instanceof Collection) {
+            return requestPart.matrixParam(matrixParamName, ((Collection) resolvedValue).toArray());
+        } else {
+            return requestPart.matrixParam(matrixParamName, resolvedValue);
+        }
+    }
+
+    @Override
+    public boolean handles(Class<Annotation> annotation) {
+        return MatrixParam.class.equals(annotation);
+    }
+
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/MethodModel.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/MethodModel.java
new file mode 100644
index 0000000..52079eb
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/MethodModel.java
@@ -0,0 +1,670 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.microprofile.restclient;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Parameter;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Proxy;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
+
+import javax.enterprise.inject.spi.BeanManager;
+import javax.enterprise.inject.spi.CDI;
+import javax.enterprise.inject.spi.InterceptionType;
+import javax.enterprise.inject.spi.Interceptor;
+import javax.json.JsonValue;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.CookieParam;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.MatrixParam;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.Invocation;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedHashMap;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+
+import org.eclipse.microprofile.rest.client.RestClientDefinitionException;
+import org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam;
+import org.eclipse.microprofile.rest.client.ext.AsyncInvocationInterceptor;
+import org.eclipse.microprofile.rest.client.ext.ResponseExceptionMapper;
+
+/**
+ * Method model contains all information about method defined in rest client interface.
+ *
+ * @author David Kral
+ */
+class MethodModel {
+
+    private static final String INVOKED_METHOD = "org.eclipse.microprofile.rest.client.invokedMethod";
+
+    private final InterfaceModel interfaceModel;
+
+    private final Method method;
+    private final Class<?> returnType;
+    private final String httpMethod;
+    private final String path;
+    private final String[] produces;
+    private final String[] consumes;
+    private final List<ParamModel> parameterModels;
+    private final List<ClientHeaderParamModel> clientHeaders;
+    private final List<InterceptorInvocationContext.InvocationInterceptor> invocationInterceptors;
+    private final RestClientModel subResourceModel;
+
+    /**
+     * Processes interface method and creates new instance of the model.
+     *
+     * @param interfaceModel
+     * @param method
+     * @return
+     */
+    static MethodModel from(InterfaceModel interfaceModel, Method method) {
+        return new Builder(interfaceModel, method)
+                .returnType(method.getGenericReturnType())
+                .httpMethod(parseHttpMethod(interfaceModel, method))
+                .pathValue(method.getAnnotation(Path.class))
+                .produces(method.getAnnotation(Produces.class))
+                .consumes(method.getAnnotation(Consumes.class))
+                .parameters(parameterModels(interfaceModel, method))
+                .clientHeaders(method.getAnnotationsByType(ClientHeaderParam.class))
+                .build();
+    }
+
+    private MethodModel(Builder builder) {
+        this.method = builder.method;
+        this.interfaceModel = builder.interfaceModel;
+        this.returnType = builder.returnType;
+        this.httpMethod = builder.httpMethod;
+        this.path = builder.pathValue;
+        this.produces = builder.produces;
+        this.consumes = builder.consumes;
+        this.parameterModels = builder.parameterModels;
+        this.clientHeaders = builder.clientHeaders;
+        this.invocationInterceptors = builder.invocationInterceptors;
+        if (httpMethod.isEmpty()) {
+            subResourceModel = RestClientModel.from(returnType,
+                                                    interfaceModel.getResponseExceptionMappers(),
+                                                    interfaceModel.getParamConverterProviders(),
+                                                    interfaceModel.getAsyncInterceptors(),
+                                                    interfaceModel.getInjectionManager());
+        } else {
+            subResourceModel = null;
+        }
+    }
+
+    /**
+     * Returns all registered cdi interceptors to this method.
+     *
+     * @return registered interceptors
+     */
+    List<InterceptorInvocationContext.InvocationInterceptor> getInvocationInterceptors() {
+        return invocationInterceptors;
+    }
+
+    /**
+     * Invokes corresponding method according to
+     *
+     * @param classLevelTarget
+     * @param method
+     * @param args
+     * @return
+     */
+    @SuppressWarnings("unchecked")
+    //I am checking the type of parameter and I know it should handle instance I am sending
+    Object invokeMethod(WebTarget classLevelTarget, Method method, Object[] args) {
+        WebTarget methodLevelTarget = classLevelTarget.path(path);
+
+        AtomicReference<Object> entity = new AtomicReference<>();
+        AtomicReference<WebTarget> webTargetAtomicReference = new AtomicReference<>(methodLevelTarget);
+        parameterModels.stream()
+                .filter(parameterModel -> parameterModel.handles(PathParam.class))
+                .forEach(parameterModel ->
+                                 webTargetAtomicReference.set((WebTarget)
+                                                                      parameterModel
+                                                                              .handleParameter(webTargetAtomicReference.get(),
+                                                                                               PathParam.class,
+                                                                                               args[parameterModel
+                                                                                                       .getParamPosition()])));
+
+        parameterModels.stream()
+                .filter(ParamModel::isEntity)
+                .findFirst()
+                .ifPresent(parameterModel -> entity.set(args[parameterModel.getParamPosition()]));
+
+        WebTarget webTarget = webTargetAtomicReference.get();
+        if (httpMethod.isEmpty()) {
+            //sub resource method
+            return subResourceProxy(webTarget, returnType);
+        }
+        webTarget = addQueryParams(webTarget, args);
+        webTarget = addMatrixParams(webTarget, args);
+
+        Invocation.Builder builder = webTarget
+                .request(produces)
+                .property(INVOKED_METHOD, method)
+                .headers(addCustomHeaders(args));
+        builder = addCookies(builder, args);
+
+        Object response;
+
+        if (CompletionStage.class.isAssignableFrom(method.getReturnType())) {
+            response = asynchronousCall(builder, entity.get(), method);
+        } else {
+            response = synchronousCall(builder, entity.get(), method);
+        }
+        return response;
+    }
+
+    private Object synchronousCall(Invocation.Builder builder, Object entity, Method method) {
+        Response response;
+
+        if (entity != null
+                && !httpMethod.equals(GET.class.getSimpleName())
+                && !httpMethod.equals(DELETE.class.getSimpleName())) {
+            response = builder.method(httpMethod, Entity.entity(entity, consumes[0]));
+        } else {
+            response = builder.method(httpMethod);
+        }
+
+        evaluateResponse(response, method);
+
+        if (returnType.equals(Void.class)) {
+            return null;
+        } else if (returnType.equals(Response.class)) {
+            return response;
+        }
+        return response.readEntity(returnType);
+    }
+
+    private CompletableFuture asynchronousCall(Invocation.Builder builder, Object entity, Method method) {
+        ParameterizedType type = (ParameterizedType) method.getGenericReturnType();
+        Type actualTypeArgument = type.getActualTypeArguments()[0]; //completionStage<actualTypeArgument>
+        CompletableFuture<Object> result = new CompletableFuture<>();
+        Future<Response> theFuture;
+        if (entity != null
+                && !httpMethod.equals(GET.class.getSimpleName())
+                && !httpMethod.equals(DELETE.class.getSimpleName())) {
+            theFuture = builder.async().method(httpMethod, Entity.entity(entity, consumes[0]));
+        } else {
+            theFuture = builder.async().method(httpMethod);
+        }
+
+        CompletableFuture<Response> completableFuture = (CompletableFuture<Response>) theFuture;
+        completableFuture.thenAccept(response -> {
+            interfaceModel.getAsyncInterceptors().forEach(AsyncInvocationInterceptor::removeContext);
+            try {
+                evaluateResponse(response, method);
+                if (returnType.equals(Void.class)) {
+                    result.complete(null);
+                } else if (returnType.equals(Response.class)) {
+                    result.complete(response);
+                } else {
+                    result.complete(response.readEntity(new GenericType<>(actualTypeArgument)));
+                }
+            } catch (Exception e) {
+                result.completeExceptionally(e);
+            }
+        }).exceptionally(throwable -> {
+            interfaceModel.getAsyncInterceptors().forEach(AsyncInvocationInterceptor::removeContext);
+            result.completeExceptionally(throwable);
+            return null;
+        });
+
+        return result;
+    }
+
+    @SuppressWarnings("unchecked")
+    private <T> T subResourceProxy(WebTarget webTarget, Class<T> subResourceType) {
+        return (T) Proxy.newProxyInstance(subResourceType.getClassLoader(),
+                                          new Class[] {subResourceType},
+                                          new ProxyInvocationHandler(webTarget, subResourceModel)
+        );
+    }
+
+    @SuppressWarnings("unchecked") //I am checking the type of parameter and I know it should handle instance I am sending
+    private WebTarget addQueryParams(WebTarget webTarget, Object[] args) {
+        Map<String, Object[]> queryParams = new HashMap<>();
+        WebTarget toReturn = webTarget;
+        parameterModels.stream()
+                .filter(parameterModel -> parameterModel.handles(QueryParam.class))
+                .forEach(parameterModel -> parameterModel.handleParameter(queryParams,
+                                                                          QueryParam.class,
+                                                                          args[parameterModel.getParamPosition()]));
+
+        for (Map.Entry<String, Object[]> entry : queryParams.entrySet()) {
+            toReturn = toReturn.queryParam(entry.getKey(), entry.getValue());
+        }
+        return toReturn;
+    }
+
+    @SuppressWarnings("unchecked") //I am checking the type of parameter and I know it should handle instance I am sending
+    private WebTarget addMatrixParams(WebTarget webTarget, Object[] args) {
+        AtomicReference<WebTarget> toReturn = new AtomicReference<>(webTarget);
+        parameterModels.stream()
+                .filter(parameterModel -> parameterModel.handles(MatrixParam.class))
+                .forEach(parameterModel -> toReturn
+                        .set((WebTarget) parameterModel.handleParameter(toReturn.get(),
+                                                                        MatrixParam.class,
+                                                                        args[parameterModel.getParamPosition()])));
+        return toReturn.get();
+    }
+
+    @SuppressWarnings("unchecked") //I am checking the type of parameter and I know it should handle instance I am sending
+    private Invocation.Builder addCookies(Invocation.Builder builder, Object[] args) {
+        Map<String, String> cookies = new HashMap<>();
+        Invocation.Builder toReturn = builder;
+        parameterModels.stream()
+                .filter(parameterModel -> parameterModel.handles(CookieParam.class))
+                .forEach(parameterModel -> parameterModel.handleParameter(cookies,
+                                                                          CookieParam.class,
+                                                                          args[parameterModel.getParamPosition()]));
+
+        for (Map.Entry<String, String> entry : cookies.entrySet()) {
+            toReturn = toReturn.cookie(entry.getKey(), entry.getValue());
+        }
+        return toReturn;
+    }
+
+    private MultivaluedMap<String, Object> addCustomHeaders(Object[] args) {
+        MultivaluedMap<String, Object> result = new MultivaluedHashMap<>();
+        for (Map.Entry<String, List<String>> entry : resolveCustomHeaders(args).entrySet()) {
+            entry.getValue().forEach(val -> result.add(entry.getKey(), val));
+        }
+        for (String produce : produces) {
+            result.add(HttpHeaders.ACCEPT, produce);
+        }
+        result.add(HttpHeaders.CONTENT_TYPE, consumes[0]);
+        return result;
+    }
+
+    @SuppressWarnings("unchecked") //I am checking the type of parameter and I know it should handle instance I am sending
+    private MultivaluedMap<String, String> resolveCustomHeaders(Object[] args) {
+        MultivaluedMap<String, String> customHeaders = new MultivaluedHashMap<>();
+        customHeaders.putAll(createMultivaluedHeadersMap(interfaceModel.getClientHeaders()));
+        customHeaders.putAll(createMultivaluedHeadersMap(clientHeaders));
+        parameterModels.stream()
+                .filter(parameterModel -> parameterModel.handles(HeaderParam.class))
+                .forEach(parameterModel -> parameterModel.handleParameter(customHeaders,
+                                                                          HeaderParam.class,
+                                                                          args[parameterModel.getParamPosition()]));
+
+        MultivaluedMap<String, String> inbound = new MultivaluedHashMap<>();
+        HeadersContext.get().ifPresent(headersContext -> inbound.putAll(headersContext.inboundHeaders()));
+
+        AtomicReference<MultivaluedMap<String, String>> toReturn = new AtomicReference<>(customHeaders);
+        interfaceModel.getClientHeadersFactory().ifPresent(clientHeadersFactory -> toReturn
+                .set(clientHeadersFactory.update(inbound, customHeaders)));
+        return toReturn.get();
+    }
+
+    private <T> MultivaluedMap<String, String> createMultivaluedHeadersMap(List<ClientHeaderParamModel> clientHeaders) {
+        MultivaluedMap<String, String> customHeaders = new MultivaluedHashMap<>();
+        for (ClientHeaderParamModel clientHeaderParamModel : clientHeaders) {
+            if (clientHeaderParamModel.getComputeMethod() == null) {
+                customHeaders
+                        .put(clientHeaderParamModel.getHeaderName(), Arrays.asList(clientHeaderParamModel.getHeaderValue()));
+            } else {
+                try {
+                    Method method = clientHeaderParamModel.getComputeMethod();
+                    if (method.isDefault()) {
+                        //method is interface default
+                        //we need to create instance of the interface to be able to call default method
+                        T instance = (T) ReflectionUtil.createProxyInstance(interfaceModel.getRestClientClass());
+                        if (method.getParameterCount() > 0) {
+                            customHeaders.put(clientHeaderParamModel.getHeaderName(),
+                                              createList(method.invoke(instance, clientHeaderParamModel.getHeaderName())));
+                        } else {
+                            customHeaders.put(clientHeaderParamModel.getHeaderName(),
+                                              createList(method.invoke(instance, null)));
+                        }
+                    } else {
+                        //Method is static
+                        if (method.getParameterCount() > 0) {
+                            customHeaders.put(clientHeaderParamModel.getHeaderName(),
+                                              createList(method.invoke(null, clientHeaderParamModel.getHeaderName())));
+                        } else {
+                            customHeaders.put(clientHeaderParamModel.getHeaderName(),
+                                              createList(method.invoke(null, null)));
+                        }
+                    }
+                } catch (IllegalAccessException e) {
+                    throw new RuntimeException(e);
+                } catch (InvocationTargetException e) {
+                    if (clientHeaderParamModel.isRequired()) {
+                        if (e.getCause() instanceof RuntimeException) {
+                            throw (RuntimeException) e.getCause();
+                        }
+                        throw new RuntimeException(e.getCause());
+                    }
+                }
+            }
+        }
+        return customHeaders;
+    }
+
+    private static List<String> createList(Object value) {
+        if (value instanceof String[]) {
+            String[] array = (String[]) value;
+            return Arrays.asList(array);
+        }
+        String s = (String) value;
+        return Collections.singletonList(s);
+    }
+
+    /**
+     * Evaluation of {@link Response} if it is applicable for any of the registered {@link ResponseExceptionMapper} providers.
+     *
+     * @param response obtained response
+     * @param method called method
+     */
+    void evaluateResponse(Response response, Method method) {
+        ResponseExceptionMapper lowestMapper = null;
+        Throwable throwable = null;
+        for (ResponseExceptionMapper responseExceptionMapper : interfaceModel.getResponseExceptionMappers()) {
+            if (responseExceptionMapper.handles(response.getStatus(), response.getHeaders())) {
+                if (lowestMapper == null
+                        || throwable == null
+                        || lowestMapper.getPriority() > responseExceptionMapper.getPriority()) {
+                    lowestMapper = responseExceptionMapper;
+                    Throwable tmp = lowestMapper.toThrowable(response);
+                    if (tmp != null) {
+                        throwable = tmp;
+                    }
+                }
+            }
+        }
+        if (throwable != null) {
+            if (throwable instanceof RuntimeException) {
+                throw (RuntimeException) throwable;
+            } else if (throwable instanceof Error) {
+                throw (Error) throwable;
+            }
+            for (Class<?> exception : method.getExceptionTypes()) {
+                if (throwable.getClass().isAssignableFrom(exception)) {
+                    throw new WebApplicationException(throwable);
+                }
+            }
+        }
+    }
+
+    private static String parseHttpMethod(InterfaceModel classModel, Method method) {
+        List<Class<?>> httpAnnotations = InterfaceUtil.getHttpAnnotations(method);
+        if (httpAnnotations.size() > 1) {
+            throw new RestClientDefinitionException("Method can't have more then one annotation of @HttpMethod type. "
+                                                            + "See " + classModel.getRestClientClass().getName()
+                                                            + "::" + method.getName());
+        } else if (httpAnnotations.isEmpty()) {
+            //Sub resource method
+            return "";
+        }
+        return httpAnnotations.get(0).getSimpleName();
+    }
+
+    private static List<ParamModel> parameterModels(InterfaceModel classModel, Method method) {
+        ArrayList<ParamModel> parameterModels = new ArrayList<>();
+        final List<org.glassfish.jersey.model.Parameter> jerseyParameters = org.glassfish.jersey.model.Parameter
+                .create(classModel.getRestClientClass(), classModel.getRestClientClass(),
+                        method, false);
+        Parameter[] parameters = method.getParameters();
+        for (int i = 0; i < parameters.length; i++) {
+            parameterModels.add(ParamModel.from(classModel, parameters[i].getType(), parameters[i], jerseyParameters.get(i), i));
+        }
+        return parameterModels;
+    }
+
+    private static class Builder {
+
+        private final InterfaceModel interfaceModel;
+        private final Method method;
+
+        private Class<?> returnType;
+        private String httpMethod;
+        private String pathValue;
+        private String[] produces;
+        private String[] consumes;
+        private List<ParamModel> parameterModels;
+        private List<ClientHeaderParamModel> clientHeaders;
+        private List<InterceptorInvocationContext.InvocationInterceptor> invocationInterceptors;
+
+        private Builder(InterfaceModel interfaceModel, Method method) {
+            this.interfaceModel = interfaceModel;
+            this.method = method;
+            filterAllInterceptorAnnotations();
+        }
+
+        private void filterAllInterceptorAnnotations() {
+            invocationInterceptors = new ArrayList<>();
+            try {
+                if (CDI.current() != null) {
+                    Set<Annotation> interceptorAnnotations = new HashSet<>();
+                    BeanManager beanManager = CDI.current().getBeanManager();
+                    for (Annotation annotation : method.getAnnotations()) {
+                        if (beanManager.isInterceptorBinding(annotation.annotationType())) {
+                            interceptorAnnotations.add(annotation);
+                        }
+                    }
+                    interceptorAnnotations.addAll(interfaceModel.getInterceptorAnnotations());
+                    Annotation[] allInterceptorAnnotations = interceptorAnnotations.toArray(new Annotation[0]);
+                    if (allInterceptorAnnotations.length == 0) {
+                        return;
+                    }
+                    List<Interceptor<?>> interceptors = beanManager.resolveInterceptors(InterceptionType.AROUND_INVOKE,
+                                                                                        allInterceptorAnnotations);
+                    if (!interceptors.isEmpty()) {
+                        for (Interceptor<?> interceptor : interceptors) {
+                            Object interceptorInstance = beanManager.getReference(interceptor,
+                                                                                  interceptor.getBeanClass(),
+                                                                                  interfaceModel.getCreationalContext());
+                            invocationInterceptors.add(new InterceptorInvocationContext
+                                    .InvocationInterceptor(interceptorInstance,
+                                                          interceptor));
+                        }
+                    }
+                }
+            } catch (IllegalStateException ignored) {
+                //CDI not present. Ignore.
+            }
+        }
+
+        /**
+         * Return type of the method.
+         *
+         * @param returnType Method return type
+         * @return updated Builder instance
+         */
+        Builder returnType(Type returnType) {
+            if (returnType instanceof ParameterizedType) {
+                this.returnType = (Class<?>) ((ParameterizedType) returnType).getActualTypeArguments()[0];
+            } else {
+                this.returnType = (Class<?>) returnType;
+            }
+            return this;
+        }
+
+        /**
+         * HTTP method of the method.
+         *
+         * @param httpMethod HTTP method of the method
+         * @return updated Builder instance
+         */
+        Builder httpMethod(String httpMethod) {
+            this.httpMethod = httpMethod;
+            return this;
+        }
+
+        /**
+         * Path value from {@link Path} annotation. If annotation is null, empty String is set as path.
+         *
+         * @param path {@link Path} annotation
+         * @return updated Builder instance
+         */
+        Builder pathValue(Path path) {
+            this.pathValue = path != null ? path.value() : "";
+            //if only / is added to path like this "localhost:80/test" it makes invalid path "localhost:80/test/"
+            this.pathValue = pathValue.equals("/") ? "" : pathValue;
+            return this;
+        }
+
+        /**
+         * Extracts MediaTypes from {@link Produces} annotation.
+         * If annotation is null, value from {@link InterfaceModel} is set.
+         *
+         * @param produces {@link Produces} annotation
+         * @return updated Builder instance
+         */
+        Builder produces(Produces produces) {
+            this.produces = produces == null ? interfaceModel.getProduces() : produces.value();
+            return this;
+        }
+
+        /**
+         * Extracts MediaTypes from {@link Consumes} annotation.
+         * If annotation is null, value from {@link InterfaceModel} is set.
+         *
+         * @param consumes {@link Consumes} annotation
+         * @return updated Builder instance
+         */
+        Builder consumes(Consumes consumes) {
+            this.consumes = consumes == null ? interfaceModel.getConsumes() : consumes.value();
+            return this;
+        }
+
+        /**
+         * {@link List} of transformed method parameters.
+         *
+         * @param parameterModels {@link List} of parameters
+         * @return updated Builder instance
+         */
+        Builder parameters(List<ParamModel> parameterModels) {
+            this.parameterModels = parameterModels;
+            return this;
+        }
+
+        /**
+         * Process data from {@link ClientHeaderParam} annotation to extract methods and values.
+         *
+         * @param clientHeaderParams {@link ClientHeaderParam} annotations
+         * @return updated Builder instance
+         */
+        Builder clientHeaders(ClientHeaderParam[] clientHeaderParams) {
+            clientHeaders = Arrays.stream(clientHeaderParams)
+                    .map(clientHeaderParam -> new ClientHeaderParamModel(interfaceModel.getRestClientClass(), clientHeaderParam))
+                    .collect(Collectors.toList());
+            return this;
+        }
+
+        /**
+         * Creates new MethodModel instance.
+         *
+         * @return new instance
+         */
+        MethodModel build() {
+            validateParameters();
+            validateHeaderDuplicityNames();
+            Optional<ParamModel> entity = parameterModels.stream()
+                    .filter(ParamModel::isEntity)
+                    .findFirst();
+            if (JsonValue.class.isAssignableFrom(returnType)
+                    || (
+                    entity.isPresent() && entity.get().getType() instanceof Class
+                            && JsonValue.class.isAssignableFrom((Class<?>) entity.get().getType()))) {
+                this.consumes = new String[] {MediaType.APPLICATION_JSON};
+            }
+            return new MethodModel(this);
+        }
+
+        private void validateParameters() {
+            UriBuilder uriBuilder = UriBuilder.fromUri(interfaceModel.getPath()).path(pathValue);
+            List<String> parameters = InterfaceUtil.parseParameters(uriBuilder.toTemplate());
+            List<String> methodPathParameters = new ArrayList<>();
+            List<ParamModel> pathHandlingParams = parameterModels.stream()
+                    .filter(parameterModel -> parameterModel.handles(PathParam.class))
+                    .collect(Collectors.toList());
+            for (ParamModel paramModel : pathHandlingParams) {
+                if (paramModel instanceof PathParamModel) {
+                    methodPathParameters.add(((PathParamModel) paramModel).getPathParamName());
+                } else if (paramModel instanceof BeanParamModel) {
+                    for (ParamModel beanPathParams : ((BeanParamModel) paramModel).getAllParamsWithType(PathParam.class)) {
+                        methodPathParameters.add(((PathParamModel) beanPathParams).getPathParamName());
+                    }
+                }
+            }
+            for (String parameterName : methodPathParameters) {
+                if (!parameters.contains(parameterName)) {
+                    throw new RestClientDefinitionException("Parameter name " + parameterName + " on "
+                                                                    + interfaceModel.getRestClientClass().getName()
+                                                                    + "::" + method.getName()
+                                                                    + " doesn't match any @Path variable name.");
+                }
+                parameters.remove(parameterName);
+            }
+            if (!parameters.isEmpty()) {
+                throw new RestClientDefinitionException("Some variable names does not have matching @PathParam "
+                                                                + "defined on method " + interfaceModel.getRestClientClass()
+                        .getName()
+                                                                + "::" + method.getName());
+            }
+            List<ParamModel> entities = parameterModels.stream()
+                    .filter(ParamModel::isEntity)
+                    .collect(Collectors.toList());
+            if (entities.size() > 1) {
+                throw new RestClientDefinitionException("You cant have more than 1 entity method parameter! Check "
+                                                                + interfaceModel.getRestClientClass().getName()
+                                                                + "::" + method.getName());
+            }
+        }
+
+        private void validateHeaderDuplicityNames() {
+            ArrayList<String> names = new ArrayList<>();
+            for (ClientHeaderParamModel clientHeaderParamModel : clientHeaders) {
+                String headerName = clientHeaderParamModel.getHeaderName();
+                if (names.contains(headerName)) {
+                    throw new RestClientDefinitionException("Header name cannot be registered more then once on the same target."
+                                                                    + "See " + interfaceModel.getRestClientClass().getName());
+                }
+                names.add(headerName);
+            }
+        }
+    }
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/ParamModel.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/ParamModel.java
new file mode 100644
index 0000000..800005d
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/ParamModel.java
@@ -0,0 +1,310 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.microprofile.restclient;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Type;
+
+import javax.ws.rs.BeanParam;
+import javax.ws.rs.CookieParam;
+import javax.ws.rs.FormParam;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.MatrixParam;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.QueryParam;
+
+import org.glassfish.jersey.model.Parameter;
+
+/**
+ * Abstract model for all elements with parameter annotation.
+ *
+ * @author David Kral
+ */
+abstract class ParamModel<T> {
+
+    protected final InterfaceModel interfaceModel;
+    protected final Parameter parameter;
+    private final Type type;
+    private final AnnotatedElement annotatedElement;
+    private final int paramPosition;
+    private final boolean entity;
+
+    /**
+     * Processes parameter annotations and creates new instance of the model corresponding model.
+     *
+     * @param interfaceModel model of the interface
+     * @param type annotated element type
+     * @param annotatedElement annotated element
+     * @param position position in method params
+     * @return new parameter instance
+     */
+    static ParamModel from(InterfaceModel interfaceModel, Type type, AnnotatedElement annotatedElement,
+                           Parameter parameter, int position) {
+        return new Builder(interfaceModel, type, annotatedElement, parameter)
+                .pathParamName(annotatedElement.getAnnotation(PathParam.class))
+                .headerParamName(annotatedElement.getAnnotation(HeaderParam.class))
+                .beanParam(annotatedElement.getAnnotation(BeanParam.class))
+                .cookieParam(annotatedElement.getAnnotation(CookieParam.class))
+                .queryParam(annotatedElement.getAnnotation(QueryParam.class))
+                .matrixParam(annotatedElement.getAnnotation(MatrixParam.class))
+                .formParam(annotatedElement.getAnnotation(FormParam.class))
+                .paramPosition(position)
+                .build();
+    }
+
+    ParamModel(Builder builder) {
+        this.interfaceModel = builder.interfaceModel;
+        this.type = builder.type;
+        this.annotatedElement = builder.annotatedElement;
+        this.entity = builder.entity;
+        this.paramPosition = builder.paramPosition;
+        this.parameter = builder.parameter;
+    }
+
+    /**
+     * Returns {@link Type} of the parameter.
+     *
+     * @return parameter type
+     */
+    Type getType() {
+        return type;
+    }
+
+    /**
+     * Returns annotated element.
+     *
+     * @return annotated element
+     */
+    AnnotatedElement getAnnotatedElement() {
+        return annotatedElement;
+    }
+
+    int getParamPosition() {
+        return paramPosition;
+    }
+
+    /**
+     * Returns value if parameter is entity or not.
+     *
+     * @return if parameter is entity
+     */
+    boolean isEntity() {
+        return entity;
+    }
+
+    /**
+     * Transforms parameter to be part of the request.
+     *
+     * @param requestPart part of a request
+     * @param annotationClass annotation type
+     * @param instance actual method parameter value
+     * @return updated request part
+     */
+    abstract T handleParameter(T requestPart, Class<?> annotationClass, Object instance);
+
+    /**
+     * Evaluates if the annotation passed in parameter is supported by this parameter.
+     *
+     * @param annotation checked annotation
+     * @return if annotation is supported
+     */
+    abstract boolean handles(Class<Annotation> annotation);
+
+    protected static class Builder {
+
+        private InterfaceModel interfaceModel;
+        private Type type;
+        private AnnotatedElement annotatedElement;
+        private Parameter parameter;
+        private String pathParamName;
+        private String headerParamName;
+        private String cookieParamName;
+        private String queryParamName;
+        private String matrixParamName;
+        private String formParamName;
+        private boolean beanParam;
+        private boolean entity;
+        private int paramPosition;
+
+        private Builder(InterfaceModel interfaceModel, Type type, AnnotatedElement annotatedElement, Parameter parameter) {
+            this.interfaceModel = interfaceModel;
+            this.type = type;
+            this.annotatedElement = annotatedElement;
+            this.parameter = parameter;
+        }
+
+        /**
+         * Path parameter name.
+         *
+         * @param pathParam {@link PathParam} annotation
+         * @return updated Builder instance
+         */
+        Builder pathParamName(PathParam pathParam) {
+            this.pathParamName = pathParam == null ? null : pathParam.value();
+            return this;
+        }
+
+        /**
+         * Header parameter name.
+         *
+         * @param headerParam {@link HeaderParam} annotation
+         * @return updated Builder instance
+         */
+        Builder headerParamName(HeaderParam headerParam) {
+            this.headerParamName = headerParam == null ? null : headerParam.value();
+            return this;
+        }
+
+        /**
+         * Bean parameter identifier.
+         *
+         * @param beanParam {@link BeanParam} annotation
+         * @return updated Builder instance
+         */
+        Builder beanParam(BeanParam beanParam) {
+            this.beanParam = beanParam != null;
+            return this;
+        }
+
+        /**
+         * Cookie parameter.
+         *
+         * @param cookieParam {@link CookieParam} annotation
+         * @return updated Builder instance
+         */
+        Builder cookieParam(CookieParam cookieParam) {
+            this.cookieParamName = cookieParam == null ? null : cookieParam.value();
+            return this;
+        }
+
+        /**
+         * Query parameter.
+         *
+         * @param queryParam {@link QueryParam} annotation
+         * @return updated Builder instance
+         */
+        Builder queryParam(QueryParam queryParam) {
+            this.queryParamName = queryParam == null ? null : queryParam.value();
+            return this;
+        }
+
+        /**
+         * Matrix parameter.
+         *
+         * @param matrixParam {@link MatrixParam} annotation
+         * @return updated Builder instance
+         */
+        Builder matrixParam(MatrixParam matrixParam) {
+            this.matrixParamName = matrixParam == null ? null : matrixParam.value();
+            return this;
+        }
+
+        /**
+         * Form parameter.
+         *
+         * @param formParam {@link FormParam} annotation
+         * @return updated Builder instance
+         */
+        Builder formParam(FormParam formParam) {
+            this.formParamName = formParam == null ? null : formParam.value();
+            return this;
+        }
+
+        /**
+         * Position of parameter in method parameters
+         *
+         * @param paramPosition Parameter position
+         * @return updated Builder instance
+         */
+        Builder paramPosition(int paramPosition) {
+            this.paramPosition = paramPosition;
+            return this;
+        }
+
+        /**
+         * Returns path param name;
+         *
+         * @return path param name
+         */
+        String pathParamName() {
+            return pathParamName;
+        }
+
+        /**
+         * Returns header param name;
+         *
+         * @return header param name
+         */
+        String headerParamName() {
+            return headerParamName;
+        }
+
+        String cookieParamName() {
+            return cookieParamName;
+        }
+
+        String queryParamName() {
+            return queryParamName;
+        }
+
+        String matrixParamName() {
+            return matrixParamName;
+        }
+
+        String formParamName() {
+            return matrixParamName;
+        }
+
+        /**
+         * Creates new ParamModel instance.
+         *
+         * @return new instance
+         */
+        ParamModel build() {
+            if (pathParamName != null) {
+                return new PathParamModel(this);
+            } else if (headerParamName != null) {
+                return new HeaderParamModel(this);
+            } else if (beanParam) {
+                return new BeanParamModel(this);
+            } else if (cookieParamName != null) {
+                return new CookieParamModel(this);
+            } else if (queryParamName != null) {
+                return new QueryParamModel(this);
+            } else if (matrixParamName != null) {
+                return new MatrixParamModel(this);
+            } else if (formParamName != null) {
+                return new FormParamModel(this);
+            }
+            entity = true;
+            return new ParamModel(this) {
+                @Override
+                public Object handleParameter(Object requestPart, Class annotationClass, Object instance) {
+                    return requestPart;
+                }
+
+                @Override
+                public boolean handles(Class annotation) {
+                    return false;
+                }
+            };
+        }
+
+    }
+
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/PathParamModel.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/PathParamModel.java
new file mode 100644
index 0000000..4c8768c
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/PathParamModel.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.microprofile.restclient;
+
+import java.lang.annotation.Annotation;
+
+import javax.ws.rs.PathParam;
+import javax.ws.rs.client.WebTarget;
+
+/**
+ * Contains information about method parameter or class field which is annotated by {@link PathParam}.
+ *
+ * @author David Kral
+ */
+class PathParamModel extends ParamModel<WebTarget> {
+
+    private final String pathParamName;
+
+    PathParamModel(Builder builder) {
+        super(builder);
+        pathParamName = builder.pathParamName();
+    }
+
+    public String getPathParamName() {
+        return pathParamName;
+    }
+
+    @Override
+    public WebTarget handleParameter(WebTarget requestPart, Class<?> annotationClass, Object instance) {
+        Object resolvedValue = interfaceModel.resolveParamValue(instance, parameter);
+        return requestPart.resolveTemplate(pathParamName, resolvedValue);
+    }
+
+    @Override
+    public boolean handles(Class<Annotation> annotation) {
+        return PathParam.class.equals(annotation);
+    }
+
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/ProxyInvocationHandler.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/ProxyInvocationHandler.java
new file mode 100644
index 0000000..2a2675e
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/ProxyInvocationHandler.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.microprofile.restclient;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+
+import javax.ws.rs.client.WebTarget;
+
+/**
+ * Invocation handler for interface proxy.
+ *
+ * @author David Kral
+ */
+class ProxyInvocationHandler implements InvocationHandler {
+
+    private final WebTarget target;
+    private final RestClientModel restClientModel;
+
+    ProxyInvocationHandler(WebTarget target,
+                           RestClientModel restClientModel) {
+        this.target = target;
+        this.restClientModel = restClientModel;
+    }
+
+    @Override
+    public Object invoke(Object proxy, Method method, Object[] args) {
+        if (method.getName().contains("toString") && (args == null || args.length == 0)) {
+            return restClientModel.toString();
+        }
+        return restClientModel.invokeMethod(target, method, args);
+    }
+
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/QueryParamModel.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/QueryParamModel.java
new file mode 100644
index 0000000..ce96099
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/QueryParamModel.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.microprofile.restclient;
+
+import java.lang.annotation.Annotation;
+import java.util.Map;
+
+import javax.ws.rs.QueryParam;
+
+/**
+ * Model which contains information about query parameter
+ *
+ * @author David Kral
+ */
+class QueryParamModel extends ParamModel<Map<String, Object[]>> {
+
+    private final String queryParamName;
+
+    QueryParamModel(Builder builder) {
+        super(builder);
+        queryParamName = builder.queryParamName();
+    }
+
+    @Override
+    public Map<String, Object[]> handleParameter(Map<String, Object[]> requestPart,
+                                                 Class<?> annotationClass,
+                                                 Object instance) {
+        Object resolvedValue = interfaceModel.resolveParamValue(instance, parameter);
+        if (resolvedValue instanceof Object[]) {
+            requestPart.put(queryParamName, (Object[]) resolvedValue);
+        } else {
+            requestPart.put(queryParamName, new Object[] {resolvedValue});
+        }
+        return requestPart;
+    }
+
+    @Override
+    public boolean handles(Class<Annotation> annotation) {
+        return QueryParam.class.equals(annotation);
+    }
+
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/ReflectionUtil.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/ReflectionUtil.java
new file mode 100644
index 0000000..27344cb
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/ReflectionUtil.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.microprofile.restclient;
+
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Proxy;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+import org.glassfish.jersey.internal.util.ReflectionHelper;
+
+/**
+ * Created by David Kral.
+ */
+class ReflectionUtil {
+
+    static <T> T createInstance(Class<T> tClass) {
+        return AccessController.doPrivileged((PrivilegedAction<T>) () -> {
+            try {
+                return tClass.newInstance();
+            } catch (Throwable t) {
+                throw new RuntimeException("No default constructor in class " + tClass + " present. Class cannot be created!", t);
+            }
+        });
+    }
+
+    @SuppressWarnings("unchecked")
+    static <T> T createProxyInstance(Class<T> restClientClass) {
+        return AccessController.doPrivileged((PrivilegedAction<T>) () -> (T) Proxy.newProxyInstance(
+                Thread.currentThread().getContextClassLoader(),
+                new Class[] {restClientClass},
+                (proxy, m, args) -> {
+                    Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
+                            .getDeclaredConstructor(Class.class);
+                    constructor.setAccessible(true);
+                    return constructor.newInstance(restClientClass)
+                            .in(restClientClass)
+                            .unreflectSpecial(m, restClientClass)
+                            .bindTo(proxy)
+                            .invokeWithArguments(args);
+                }));
+    }
+
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RequestHeaderAutoDiscoverable.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RequestHeaderAutoDiscoverable.java
new file mode 100644
index 0000000..bcda814
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RequestHeaderAutoDiscoverable.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.microprofile.restclient;
+
+import javax.ws.rs.ConstrainedTo;
+import javax.ws.rs.RuntimeType;
+import javax.ws.rs.core.FeatureContext;
+
+import org.glassfish.jersey.internal.spi.AutoDiscoverable;
+
+/**
+ * Auto discoverable feature to bind into jersey runtime.
+ */
+@ConstrainedTo(RuntimeType.SERVER)
+public class RequestHeaderAutoDiscoverable implements AutoDiscoverable {
+    @Override
+    public void configure(FeatureContext context) {
+        if (!context.getConfiguration().isRegistered(HeadersRequestFilter.class)) {
+            context.register(HeadersRequestFilter.class);
+        }
+    }
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientBuilderImpl.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientBuilderImpl.java
new file mode 100644
index 0000000..fadca0f
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientBuilderImpl.java
@@ -0,0 +1,357 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.microprofile.restclient;
+
+import java.lang.reflect.Proxy;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.security.AccessController;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.ServiceLoader;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+import javax.annotation.Priority;
+import javax.ws.rs.Priorities;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.Feature;
+import javax.ws.rs.core.FeatureContext;
+import javax.ws.rs.ext.ParamConverterProvider;
+
+import org.eclipse.microprofile.config.Config;
+import org.eclipse.microprofile.config.ConfigProvider;
+import org.eclipse.microprofile.rest.client.RestClientBuilder;
+import org.eclipse.microprofile.rest.client.RestClientDefinitionException;
+import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
+import org.eclipse.microprofile.rest.client.ext.AsyncInvocationInterceptor;
+import org.eclipse.microprofile.rest.client.ext.AsyncInvocationInterceptorFactory;
+import org.eclipse.microprofile.rest.client.ext.ResponseExceptionMapper;
+import org.eclipse.microprofile.rest.client.spi.RestClientListener;
+import org.glassfish.jersey.client.Initializable;
+import org.glassfish.jersey.client.JerseyClient;
+import org.glassfish.jersey.internal.inject.InjectionManager;
+import org.glassfish.jersey.internal.inject.InjectionManagerSupplier;
+import org.glassfish.jersey.internal.util.ReflectionHelper;
+
+/**
+ * Rest client builder implementation. Creates proxy instance of requested interface.
+ *
+ * @author David Kral
+ */
+public class RestClientBuilderImpl implements RestClientBuilder {
+
+    private static final String CONFIG_DISABLE_DEFAULT_MAPPER = "microprofile.rest.client.disable.default.mapper";
+    private static final String CONFIG_PROVIDERS = "/mp-rest/providers";
+    private static final String CONFIG_PROVIDER_PRIORITY = "/priority";
+    private static final String PROVIDER_SEPARATOR = ",";
+
+    private final Set<ResponseExceptionMapper> responseExceptionMappers;
+    private final Set<ParamConverterProvider> paramConverterProviders;
+    private final List<AsyncInvocationInterceptorFactory> asyncInterceptorFactories;
+    private final Config config;
+    private final ConfigWrapper configWrapper;
+    private URI uri;
+    private ClientBuilder clientBuilder;
+    private Supplier<ExecutorService> executorService;
+
+    RestClientBuilderImpl() {
+        clientBuilder = ClientBuilder.newBuilder();
+        responseExceptionMappers = new HashSet<>();
+        paramConverterProviders = new HashSet<>();
+        asyncInterceptorFactories = new ArrayList<>();
+        config = ConfigProvider.getConfig();
+        configWrapper = new ConfigWrapper(clientBuilder.getConfiguration());
+        executorService = Executors::newCachedThreadPool;
+    }
+
+    @Override
+    public RestClientBuilder baseUrl(URL url) {
+        try {
+            this.uri = url.toURI();
+            return this;
+        } catch (URISyntaxException e) {
+            throw new RuntimeException(e.getMessage());
+        }
+    }
+
+    @Override
+    public RestClientBuilder connectTimeout(long timeout, TimeUnit unit) {
+        clientBuilder.connectTimeout(timeout, unit);
+        return this;
+    }
+
+    @Override
+    public RestClientBuilder readTimeout(long timeout, TimeUnit unit) {
+        clientBuilder.readTimeout(timeout, unit);
+        return this;
+    }
+
+    @Override
+    public RestClientBuilder executorService(ExecutorService executor) {
+        if (executor == null) {
+            throw new IllegalArgumentException("ExecutorService cannot be null.");
+        }
+        executorService = () -> executor;
+        return this;
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public <T> T build(Class<T> interfaceClass) throws IllegalStateException, RestClientDefinitionException {
+
+        if (uri == null) {
+            throw new IllegalStateException("Base uri/url cannot be null!");
+        }
+
+        //Provider registration part
+        Object providersFromJerseyConfig = clientBuilder.getConfiguration()
+                .getProperty(interfaceClass.getName() + CONFIG_PROVIDERS);
+        if (providersFromJerseyConfig instanceof String && !((String) providersFromJerseyConfig).isEmpty()) {
+            String[] providerArray = ((String) providersFromJerseyConfig).split(PROVIDER_SEPARATOR);
+            processConfigProviders(interfaceClass, providerArray);
+        }
+        Optional<String> providersFromConfig = config.getOptionalValue(interfaceClass.getName() + CONFIG_PROVIDERS, String.class);
+        if (providersFromConfig.isPresent() && !providersFromConfig.get().isEmpty()) {
+            String[] providerArray = providersFromConfig.get().split(PROVIDER_SEPARATOR);
+            processConfigProviders(interfaceClass, providerArray);
+        }
+        RegisterProvider[] registerProviders = interfaceClass.getAnnotationsByType(RegisterProvider.class);
+        for (RegisterProvider registerProvider : registerProviders) {
+            register(registerProvider.value(), registerProvider.priority() < 0 ? Priorities.USER : registerProvider.priority());
+        }
+        InjectionManagerExposer injectionManagerExposer = new InjectionManagerExposer();
+        register(injectionManagerExposer);
+
+        for (RestClientListener restClientListener : ServiceLoader.load(RestClientListener.class)) {
+            restClientListener.onNewClient(interfaceClass, this);
+        }
+
+        //We need to check first if default exception mapper was not disabled by property on builder.
+        Object disableDefaultMapperJersey = clientBuilder.getConfiguration().getProperty(CONFIG_DISABLE_DEFAULT_MAPPER);
+        if (disableDefaultMapperJersey != null && disableDefaultMapperJersey.equals(Boolean.FALSE)) {
+            register(new DefaultResponseExceptionMapper());
+        } else if (disableDefaultMapperJersey == null) {
+            //If property was not set on Jersey ClientBuilder, we need to check config.
+            Optional<Boolean> disableDefaultMapperConfig = config.getOptionalValue(CONFIG_DISABLE_DEFAULT_MAPPER, boolean.class);
+            if (!disableDefaultMapperConfig.isPresent() || !disableDefaultMapperConfig.get()) {
+                register(new DefaultResponseExceptionMapper());
+            }
+        }
+
+        //AsyncInterceptors initialization
+        List<AsyncInvocationInterceptor> asyncInterceptors = asyncInterceptorFactories.stream()
+                .map(AsyncInvocationInterceptorFactory::newInterceptor)
+                .collect(Collectors.toList());
+        asyncInterceptors.forEach(AsyncInvocationInterceptor::prepareContext);
+
+        clientBuilder.executorService(new ExecutorServiceWrapper(executorService.get(), asyncInterceptors));
+
+        Client client = clientBuilder.build();
+        if (client instanceof Initializable) {
+            ((Initializable) client).preInitialize();
+        }
+        WebTarget webTarget = client.target(this.uri);
+
+        RestClientModel restClientModel = RestClientModel.from(interfaceClass,
+                                                               responseExceptionMappers,
+                                                               paramConverterProviders,
+                                                               asyncInterceptors,
+                                                               injectionManagerExposer.injectionManager);
+
+
+        return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(),
+                                          new Class[] {interfaceClass},
+                                          new ProxyInvocationHandler(webTarget, restClientModel)
+        );
+    }
+
+    private void processConfigProviders(Class<?> restClientInterface, String[] providerArray) {
+        for (String provider : providerArray) {
+            Class<?> providerClass = AccessController.doPrivileged(ReflectionHelper.classForNamePA(provider));
+            if (providerClass == null) {
+                throw new IllegalStateException("No provider class with following name found: " + provider);
+            }
+            int priority = getProviderPriority(restClientInterface, providerClass);
+            register(providerClass, priority);
+        }
+    }
+
+    private int getProviderPriority(Class<?> restClientInterface, Class<?> providerClass) {
+        String property = restClientInterface.getName() + CONFIG_PROVIDERS + "/"
+                + providerClass.getName() + CONFIG_PROVIDER_PRIORITY;
+        Object providerPriorityJersey = clientBuilder.getConfiguration().getProperty(property);
+        if (providerPriorityJersey == null) {
+            //If property was not set on Jersey ClientBuilder, we need to check MP config.
+            Optional<Integer> providerPriorityMP = config.getOptionalValue(property, int.class);
+            if (providerPriorityMP.isPresent()) {
+                return providerPriorityMP.get();
+            }
+        } else if (providerPriorityJersey instanceof Integer) {
+            return (int) providerPriorityJersey;
+        }
+        Priority priority = providerClass.getAnnotation(Priority.class);
+        return priority == null ? -1 : priority.value();
+    }
+
+    @Override
+    public Configuration getConfiguration() {
+        return configWrapper;
+    }
+
+    @Override
+    public RestClientBuilder property(String name, Object value) {
+        clientBuilder.property(name, value);
+        return this;
+    }
+
+    @Override
+    public RestClientBuilder register(Class<?> aClass) {
+        if (isSupportedCustomProvider(aClass)) {
+            register(ReflectionUtil.createInstance(aClass));
+        } else {
+            clientBuilder.register(aClass);
+        }
+        return this;
+    }
+
+    @Override
+    public RestClientBuilder register(Class<?> aClass, int i) {
+        if (isSupportedCustomProvider(aClass)) {
+            register(ReflectionUtil.createInstance(aClass), i);
+        } else {
+            clientBuilder.register(aClass, i);
+        }
+        return this;
+    }
+
+    @Override
+    public RestClientBuilder register(Class<?> aClass, Class<?>... classes) {
+        if (isSupportedCustomProvider(aClass)) {
+            register(ReflectionUtil.createInstance(aClass), classes);
+        } else {
+            clientBuilder.register(aClass, classes);
+        }
+        return this;
+    }
+
+    @Override
+    public RestClientBuilder register(Class<?> aClass, Map<Class<?>, Integer> map) {
+        if (isSupportedCustomProvider(aClass)) {
+            register(ReflectionUtil.createInstance(aClass), map);
+        } else {
+            clientBuilder.register(aClass, map);
+        }
+        return this;
+    }
+
+    @Override
+    public RestClientBuilder register(Object o) {
+        if (o instanceof ResponseExceptionMapper) {
+            ResponseExceptionMapper mapper = (ResponseExceptionMapper) o;
+            registerCustomProvider(o, -1);
+            clientBuilder.register(mapper, mapper.getPriority());
+        } else {
+            clientBuilder.register(o);
+            registerCustomProvider(o, -1);
+        }
+        return this;
+    }
+
+    @Override
+    public RestClientBuilder register(Object o, int i) {
+        clientBuilder.register(o, i);
+        registerCustomProvider(o, i);
+        return this;
+    }
+
+    @Override
+    public RestClientBuilder register(Object o, Class<?>... classes) {
+        for (Class<?> clazz : classes) {
+            if (isSupportedCustomProvider(clazz)) {
+                register(o);
+            }
+        }
+        clientBuilder.register(o, classes);
+        return this;
+    }
+
+    @Override
+    public RestClientBuilder register(Object o, Map<Class<?>, Integer> map) {
+        if (isSupportedCustomProvider(o.getClass())) {
+            if (o instanceof ResponseExceptionMapper) {
+                registerCustomProvider(o, map.get(ResponseExceptionMapper.class));
+            } else if (o instanceof ParamConverterProvider) {
+                registerCustomProvider(o, map.get(ParamConverterProvider.class));
+            }
+        }
+        clientBuilder.register(o, map);
+        return this;
+    }
+
+    private boolean isSupportedCustomProvider(Class<?> providerClass) {
+        return ResponseExceptionMapper.class.isAssignableFrom(providerClass)
+                || ParamConverterProvider.class.isAssignableFrom(providerClass)
+                || AsyncInvocationInterceptorFactory.class.isAssignableFrom(providerClass);
+    }
+
+    private void registerCustomProvider(Object instance, int priority) {
+        if (!isSupportedCustomProvider(instance.getClass())) {
+            return;
+        }
+        if (instance instanceof ResponseExceptionMapper) {
+            responseExceptionMappers.add((ResponseExceptionMapper) instance);
+            //needs to be registered separately due to it is not possible to register custom provider in jersey
+            Map<Class<?>, Integer> contracts = new HashMap<>();
+            contracts.put(ResponseExceptionMapper.class, priority);
+            configWrapper.addCustomProvider(instance.getClass(), contracts);
+        }
+        if (instance instanceof ParamConverterProvider) {
+            paramConverterProviders.add((ParamConverterProvider) instance);
+        }
+        if (instance instanceof AsyncInvocationInterceptorFactory) {
+            asyncInterceptorFactories.add((AsyncInvocationInterceptorFactory) instance);
+        }
+    }
+
+    private static class InjectionManagerExposer implements Feature {
+        InjectionManager injectionManager;
+
+        @Override
+        public boolean configure(FeatureContext context) {
+            if (context instanceof InjectionManagerSupplier) {
+                this.injectionManager = ((InjectionManagerSupplier) context).getInjectionManager();
+                return true;
+            } else {
+                throw new IllegalArgumentException("The client needs Jersey runtime to work properly");
+            }
+        }
+    }
+
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientExtension.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientExtension.java
new file mode 100644
index 0000000..05743a2
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientExtension.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.microprofile.restclient;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.enterprise.event.Observes;
+import javax.enterprise.inject.spi.AfterBeanDiscovery;
+import javax.enterprise.inject.spi.BeanManager;
+import javax.enterprise.inject.spi.DeploymentException;
+import javax.enterprise.inject.spi.Extension;
+import javax.enterprise.inject.spi.InjectionPoint;
+import javax.enterprise.inject.spi.ProcessAnnotatedType;
+import javax.enterprise.inject.spi.ProcessInjectionPoint;
+import javax.enterprise.inject.spi.WithAnnotations;
+import javax.enterprise.util.AnnotationLiteral;
+import javax.inject.Qualifier;
+
+import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
+import org.eclipse.microprofile.rest.client.inject.RestClient;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Filters out all interfaces annotated with {@link RegisterRestClient}
+ * and creates new Producer from each of these selected interfaces.
+ *
+ * Also adds support for injection of rest client instances to fields
+ * without {@link RestClient} annotation.
+ *
+ * @author David Kral
+ */
+public class RestClientExtension implements Extension {
+
+    private Set<Class<?>> interfaces = new HashSet<>();
+
+    /**
+     * Filters out all interfaces annotated with {@link RegisterRestClient} annotation and
+     * adds them to the collection for further processing.
+     *
+     * @param processAnnotatedType filtered annotated types
+     */
+    public void collectClientRegistrations(@Observes
+                                           @WithAnnotations({RegisterRestClient.class})
+                                                   ProcessAnnotatedType<?> processAnnotatedType) {
+        Class<?> typeDef = processAnnotatedType.getAnnotatedType().getJavaClass();
+        if (typeDef.isInterface()) {
+            interfaces.add(typeDef);
+        } else {
+            throw new DeploymentException("RegisterRestClient annotation has to be on interface! " + typeDef + " is not "
+                    + "interface.");
+        }
+    }
+
+    /**
+     * Iterates over all {@link ProcessInjectionPoint} to find only those annotated
+     * with {@link RestClient} and configures their proper injection.
+     *
+     * @param pip processed injection point
+     */
+    public void collectClientProducer(@Observes ProcessInjectionPoint<?, ?> pip) {
+        RestClient restClient = pip.getInjectionPoint().getAnnotated().getAnnotation(RestClient.class);
+        if (restClient != null) {
+            InjectionPoint ip = pip.getInjectionPoint();
+            Class<?> type = (Class<?>) ip.getType();
+
+            RestClientLiteral q = new RestClientLiteral(type);
+
+            pip.configureInjectionPoint().addQualifier(q);
+        }
+    }
+
+    /**
+     * Creates new producers based on collected interfaces.
+     *
+     * @param abd after bean discovery instance
+     * @param bm bean manager instance
+     */
+    public void restClientRegistration(@Observes AfterBeanDiscovery abd, BeanManager bm) {
+        interfaces.forEach(type -> abd.addBean(new RestClientProducer(new RestClientLiteral(type), type, bm)));
+        interfaces.forEach(type -> abd.addBean(new RestClientProducer(null, type, bm)));
+    }
+
+    @Qualifier
+    @Retention(RUNTIME)
+    @Target({METHOD, FIELD})
+    @interface MpRestClientQualifier {
+
+        Class<?> interfaceType();
+
+    }
+
+    private static class RestClientLiteral extends AnnotationLiteral<MpRestClientQualifier> implements MpRestClientQualifier {
+
+        private final Class<?> interfaceType;
+
+        RestClientLiteral(Class<?> interfaceType) {
+            this.interfaceType = interfaceType;
+        }
+
+        @Override
+        public Class<?> interfaceType() {
+            return interfaceType;
+        }
+
+        @Override
+        public String toString() {
+            return "RestClientLiteral{"
+                    + "interfaceType=" + interfaceType
+                    + '}';
+        }
+
+    }
+
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientModel.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientModel.java
new file mode 100644
index 0000000..49d353c
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientModel.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.microprofile.restclient;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.ext.ParamConverterProvider;
+
+import org.eclipse.microprofile.rest.client.ext.AsyncInvocationInterceptor;
+import org.eclipse.microprofile.rest.client.ext.ResponseExceptionMapper;
+import org.glassfish.jersey.internal.inject.InjectionManager;
+
+/**
+ * Model of the rest client interface.
+ *
+ * @author David Kral
+ */
+class RestClientModel {
+
+    private final InterfaceModel interfaceModel;
+    private final Map<Method, MethodModel> methodModels;
+
+    /**
+     * Creates new instance of the {@link RestClientModel} base on interface class.
+     *
+     * @param restClientClass          rest client interface
+     * @param responseExceptionMappers registered exception mappers
+     * @param paramConverterProviders  registered param converters
+     * @param asyncInterceptors        registered async interceptor factories
+     * @param injectionManager
+     * @return new instance
+     */
+    static RestClientModel from(Class<?> restClientClass,
+                                Set<ResponseExceptionMapper> responseExceptionMappers,
+                                Set<ParamConverterProvider> paramConverterProviders,
+                                List<AsyncInvocationInterceptor> asyncInterceptors,
+                                InjectionManager injectionManager) {
+        InterfaceModel interfaceModel = InterfaceModel.from(restClientClass,
+                                                            responseExceptionMappers,
+                                                            paramConverterProviders,
+                                                            asyncInterceptors,
+                                                            injectionManager);
+        return new Builder()
+                .interfaceModel(interfaceModel)
+                .methodModels(parseMethodModels(interfaceModel))
+                .build();
+    }
+
+    private RestClientModel(Builder builder) {
+        this.interfaceModel = builder.classModel;
+        this.methodModels = builder.methodModels;
+    }
+
+    /**
+     * Invokes desired rest client method.
+     *
+     * @param baseWebTarget path to endpoint
+     * @param method        desired method
+     * @param args          actual method parameters
+     * @return method return value
+     */
+    <T> Object invokeMethod(WebTarget baseWebTarget, Method method, Object[] args) {
+        WebTarget classLevelTarget = baseWebTarget.path(interfaceModel.getPath());
+        MethodModel methodModel = methodModels.get(method);
+        if (methodModel != null) {
+            return new InterceptorInvocationContext(classLevelTarget, methodModel, method, args).proceed();
+        }
+        try {
+            if (method.isDefault()) {
+                T instance = (T) ReflectionUtil.createProxyInstance(interfaceModel.getRestClientClass());
+                return method.invoke(instance, args);
+            } else {
+                throw new UnsupportedOperationException("This method is not supported!");
+            }
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static Map<Method, MethodModel> parseMethodModels(InterfaceModel classModel) {
+        Map<Method, MethodModel> methodMap = new HashMap<>();
+        for (Method method : classModel.getRestClientClass().getMethods()) {
+            if (method.isDefault() || Modifier.isStatic(method.getModifiers())) {
+                continue;
+            }
+            //Skip method processing if method does not have HTTP annotation
+            //and is not sub resource (does not have Path annotation)
+            methodMap.put(method, MethodModel.from(classModel, method));
+        }
+        return methodMap;
+    }
+
+    private static class Builder {
+
+        private InterfaceModel classModel;
+        private Map<Method, MethodModel> methodModels;
+
+        private Builder() {
+        }
+
+        /**
+         * Rest client class converted to {@link InterfaceModel}
+         *
+         * @param classModel {@link InterfaceModel} instance
+         * @return Updated Builder instance
+         */
+        Builder interfaceModel(InterfaceModel classModel) {
+            this.classModel = classModel;
+            return this;
+        }
+
+        /**
+         * Rest client class methods converted to {@link Map} of {@link MethodModel}
+         *
+         * @param methodModels Method models
+         * @return Updated Builder instance
+         */
+        Builder methodModels(Map<Method, MethodModel> methodModels) {
+            this.methodModels = methodModels;
+            return this;
+        }
+
+        /**
+         * Creates new RestClientModel instance.
+         *
+         * @return new instance
+         */
+        public RestClientModel build() {
+            return new RestClientModel(this);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return interfaceModel.getRestClientClass().getName();
+    }
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientProducer.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientProducer.java
new file mode 100644
index 0000000..7c0c4b3
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientProducer.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.microprofile.restclient;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.security.AccessController;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+import javax.enterprise.context.Dependent;
+import javax.enterprise.context.spi.CreationalContext;
+import javax.enterprise.inject.Default;
+import javax.enterprise.inject.spi.Bean;
+import javax.enterprise.inject.spi.BeanManager;
+import javax.enterprise.inject.spi.DeploymentException;
+import javax.enterprise.inject.spi.InjectionPoint;
+import javax.enterprise.inject.spi.PassivationCapable;
+
+import org.eclipse.microprofile.config.Config;
+import org.eclipse.microprofile.config.ConfigProvider;
+import org.eclipse.microprofile.rest.client.RestClientBuilder;
+import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
+import org.eclipse.microprofile.rest.client.inject.RestClient;
+import org.glassfish.jersey.internal.util.ReflectionHelper;
+
+/**
+ * Handles proper rest client injection.
+ *
+ * Contains information about the rest client interface and extracts additional parameters from
+ * config.
+ *
+ * @author David Kral
+ */
+class RestClientProducer implements Bean<Object>, PassivationCapable {
+
+    private static final String CONFIG_URL = "/mp-rest/url";
+    private static final String CONFIG_URI = "/mp-rest/uri";
+    private static final String CONFIG_SCOPE = "/mp-rest/scope";
+    private static final String CONFIG_CONNECTION_TIMEOUT = "/mp-rest/connectTimeout";
+    private static final String CONFIG_READ_TIMEOUT = "/mp-rest/readTimeout";
+
+    private final RestClientExtension.MpRestClientQualifier qualifier;
+    private final BeanManager beanManager;
+    private final Class<?> interfaceType;
+    private final Class<? extends Annotation> scope;
+    private final Config config;
+    private final String baseUrl;
+
+    /**
+     * Creates new instance of RestClientProducer.
+     *
+     * @param qualifier     qualifier which defines rest client interface
+     * @param interfaceType rest client interface
+     * @param beanManager   bean manager
+     */
+    RestClientProducer(RestClientExtension.MpRestClientQualifier qualifier,
+                       Class<?> interfaceType,
+                       BeanManager beanManager) {
+        this.qualifier = qualifier;
+        this.interfaceType = interfaceType;
+        this.beanManager = beanManager;
+        this.config = ConfigProvider.getConfig();
+        this.baseUrl = getBaseUrl(interfaceType);
+        this.scope = resolveProperClientScope();
+    }
+
+    private String getBaseUrl(Class<?> interfaceType) {
+        Optional<String> uri = config.getOptionalValue(interfaceType.getName() + CONFIG_URI, String.class);
+        return uri.orElse(config.getOptionalValue(interfaceType.getName() + CONFIG_URL, String.class).orElseGet(
+                () -> {
+                    RegisterRestClient registerRestClient = interfaceType.getAnnotation(RegisterRestClient.class);
+                    if (registerRestClient != null) {
+                        return registerRestClient.baseUri();
+                    }
+                    throw new DeploymentException("This interface has to be annotated with @RegisterRestClient annotation.");
+                }
+        ));
+    }
+
+    @Override
+    public Class<?> getBeanClass() {
+        return interfaceType;
+    }
+
+    @Override
+    public Set<InjectionPoint> getInjectionPoints() {
+        return Collections.emptySet();
+    }
+
+    @Override
+    public boolean isNullable() {
+        return false;
+    }
+
+    @Override
+    public Object create(CreationalContext<Object> creationalContext) {
+        try {
+            RestClientBuilder restClientBuilder = RestClientBuilder.newBuilder().baseUrl(new URL(baseUrl));
+            config.getOptionalValue(interfaceType.getName() + CONFIG_CONNECTION_TIMEOUT, Long.class)
+                    .ifPresent(aLong -> restClientBuilder.connectTimeout(aLong, TimeUnit.MILLISECONDS));
+            config.getOptionalValue(interfaceType.getName() + CONFIG_READ_TIMEOUT, Long.class)
+                    .ifPresent(aLong -> restClientBuilder.readTimeout(aLong, TimeUnit.MILLISECONDS));
+            return restClientBuilder.build(interfaceType);
+        } catch (MalformedURLException e) {
+            throw new IllegalStateException("URL is not in valid format: " + baseUrl);
+        }
+    }
+
+    @Override
+    public void destroy(Object instance, CreationalContext<Object> creationalContext) {
+    }
+
+    @Override
+    public Set<Type> getTypes() {
+        return Collections.singleton(interfaceType);
+    }
+
+    @Override
+    public Set<Annotation> getQualifiers() {
+        if (qualifier == null) {
+            return Collections.singleton(Default.Literal.INSTANCE);
+        }
+        Set<Annotation> annotations = new HashSet<>();
+        annotations.add(qualifier);
+        annotations.add(RestClient.LITERAL);
+        return annotations;
+    }
+
+    @Override
+    public Class<? extends Annotation> getScope() {
+        return scope;
+    }
+
+    @Override
+    public String getName() {
+        if (qualifier == null) {
+            return interfaceType.getName() + "RestClient";
+        }
+        return interfaceType.getName();
+    }
+
+    @Override
+    public Set<Class<? extends Annotation>> getStereotypes() {
+        return Collections.emptySet();
+    }
+
+    @Override
+    public boolean isAlternative() {
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return "RestClientProducer [ interfaceType: " + interfaceType.getSimpleName()
+                + " ] with Qualifiers [" + getQualifiers() + "]";
+    }
+
+    @Override
+    public String getId() {
+        return interfaceType.getName();
+    }
+
+    private Class<? extends Annotation> resolveProperClientScope() {
+        String configScope = config.getOptionalValue(interfaceType.getName() + CONFIG_SCOPE, String.class).orElse(null);
+        if (configScope != null) {
+            Class<Annotation> scope = AccessController.doPrivileged(ReflectionHelper.classForNamePA(configScope));
+            if (scope == null) {
+                throw new IllegalStateException("Invalid scope from config: " + configScope);
+            }
+            return scope;
+        }
+        List<Annotation> possibleScopes = Arrays.stream(interfaceType.getDeclaredAnnotations())
+                .filter(annotation -> beanManager.isScope(annotation.annotationType()))
+                .collect(Collectors.toList());
+
+        if (possibleScopes.size() == 1) {
+            return possibleScopes.get(0).annotationType();
+        } else if (possibleScopes.isEmpty()) {
+            return Dependent.class;
+        } else {
+            throw new IllegalArgumentException("Client should have only one scope defined: "
+                                                       + interfaceType + " has " + possibleScopes);
+        }
+    }
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/resources/META-INF/beans.xml b/ext/microprofile/mp-rest-client/src/main/resources/META-INF/beans.xml
new file mode 100644
index 0000000..f472ff4
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/resources/META-INF/beans.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
+        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+        xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
+                           http://xmlns.jcp.org/xml/ns/javaee/beans_2_0.xsd"
+        version="2.0"
+        bean-discovery-mode="none">
+</beans>
diff --git a/ext/microprofile/mp-rest-client/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension b/ext/microprofile/mp-rest-client/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
new file mode 100644
index 0000000..bca39a9
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
@@ -0,0 +1,17 @@
+#
+# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Eclipse Public License v. 2.0, which is available at
+# http://www.eclipse.org/legal/epl-2.0.
+#
+# This Source Code may also be made available under the following Secondary
+# Licenses when the conditions for such availability set forth in the
+# Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+# version 2 with the GNU Classpath Exception, which is available at
+# https://www.gnu.org/software/classpath/license.html.
+#
+# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+#
+
+org.glassfish.jersey.microprofile.restclient.RestClientExtension
\ No newline at end of file
diff --git a/ext/microprofile/mp-rest-client/src/main/resources/META-INF/services/org.eclipse.microprofile.rest.client.spi.RestClientBuilderResolver b/ext/microprofile/mp-rest-client/src/main/resources/META-INF/services/org.eclipse.microprofile.rest.client.spi.RestClientBuilderResolver
new file mode 100644
index 0000000..e23142f
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/resources/META-INF/services/org.eclipse.microprofile.rest.client.spi.RestClientBuilderResolver
@@ -0,0 +1,17 @@
+#
+# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Eclipse Public License v. 2.0, which is available at
+# http://www.eclipse.org/legal/epl-2.0.
+#
+# This Source Code may also be made available under the following Secondary
+# Licenses when the conditions for such availability set forth in the
+# Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+# version 2 with the GNU Classpath Exception, which is available at
+# https://www.gnu.org/software/classpath/license.html.
+#
+# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+#
+
+org.glassfish.jersey.microprofile.restclient.JerseyRestClientBuilderResolver
\ No newline at end of file
diff --git a/ext/microprofile/mp-rest-client/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.AutoDiscoverable b/ext/microprofile/mp-rest-client/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.AutoDiscoverable
new file mode 100644
index 0000000..d9a1262
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.AutoDiscoverable
@@ -0,0 +1,17 @@
+#
+# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Eclipse Public License v. 2.0, which is available at
+# http://www.eclipse.org/legal/epl-2.0.
+#
+# This Source Code may also be made available under the following Secondary
+# Licenses when the conditions for such availability set forth in the
+# Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+# version 2 with the GNU Classpath Exception, which is available at
+# https://www.gnu.org/software/classpath/license.html.
+#
+# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+#
+
+org.glassfish.jersey.microprofile.restclient.RequestHeaderAutoDiscoverable
\ No newline at end of file
diff --git a/ext/microprofile/mp-rest-client/src/test/java/org/glassfish/jersey/message/internal/StringMessageProvider.java b/ext/microprofile/mp-rest-client/src/test/java/org/glassfish/jersey/message/internal/StringMessageProvider.java
new file mode 100644
index 0000000..203ecbc
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/test/java/org/glassfish/jersey/message/internal/StringMessageProvider.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.message.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.inject.Singleton;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+
+import org.glassfish.jersey.message.internal.AbstractMessageReaderWriterProvider;
+
+/*
+ * This class was copied from Jersey Common 2.26 due to TCK workaround reasons.
+ */
+
+/**
+ *
+ * @author Paul Sandoz
+ */
+@Produces
+@Consumes
+@Singleton
+final class StringMessageProvider extends AbstractMessageReaderWriterProvider<String> {
+
+    @Override
+    public boolean isReadable(Class<?> type, Type genericType, Annotation annotations[], MediaType mediaType) {
+        return type == String.class;
+    }
+
+    @Override
+    public String readFrom(
+            Class<String> type,
+            Type genericType,
+            Annotation annotations[],
+            MediaType mediaType,
+            MultivaluedMap<String, String> httpHeaders,
+            InputStream entityStream) throws IOException {
+        return readFromAsString(entityStream, mediaType);
+    }
+
+    @Override
+    public boolean isWriteable(Class<?> type, Type genericType, Annotation annotations[], MediaType mediaType) {
+        return type == String.class;
+    }
+
+    @Override
+    public long getSize(String s, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
+        return s.length();
+    }
+
+    @Override
+    public void writeTo(
+            String t,
+            Class<?> type,
+            Type genericType,
+            Annotation annotations[],
+            MediaType mediaType,
+            MultivaluedMap<String, Object> httpHeaders,
+            OutputStream entityStream) throws IOException {
+        writeToAsString(t, entityStream, mediaType);
+    }
+}
diff --git a/ext/microprofile/mp-rest-client/src/test/java/org/glassfish/jersey/microprofile/restclient/ApplicationResource.java b/ext/microprofile/mp-rest-client/src/test/java/org/glassfish/jersey/microprofile/restclient/ApplicationResource.java
new file mode 100644
index 0000000..caf3bb6
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/test/java/org/glassfish/jersey/microprofile/restclient/ApplicationResource.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.microprofile.restclient;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+
+/**
+ * Created by David Kral.
+ */
+
+@Path("resource")
+public interface ApplicationResource {
+
+    @GET
+    String getValue();
+
+    @POST
+    String postAppendValue(String value);
+
+    default String sayHi() {
+        return "Hi";
+    }
+
+
+}
diff --git a/ext/microprofile/mp-rest-client/src/test/java/org/glassfish/jersey/microprofile/restclient/ApplicationResourceImpl.java b/ext/microprofile/mp-rest-client/src/test/java/org/glassfish/jersey/microprofile/restclient/ApplicationResourceImpl.java
new file mode 100644
index 0000000..4194b40
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/test/java/org/glassfish/jersey/microprofile/restclient/ApplicationResourceImpl.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.microprofile.restclient;
+
+/**
+ * Created by David Kral.
+ */
+public class ApplicationResourceImpl implements ApplicationResource {
+    @Override
+    public String getValue() {
+        return "This is default value!";
+    }
+
+    @Override
+    public String postAppendValue(String value) {
+        return null;
+    }
+}
diff --git a/ext/microprofile/mp-rest-client/src/test/java/org/glassfish/jersey/microprofile/restclient/CorrectInterface.java b/ext/microprofile/mp-rest-client/src/test/java/org/glassfish/jersey/microprofile/restclient/CorrectInterface.java
new file mode 100644
index 0000000..efe6c15
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/test/java/org/glassfish/jersey/microprofile/restclient/CorrectInterface.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.microprofile.restclient;
+
+import javax.ws.rs.BeanParam;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+
+import org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam;
+
+/**
+ * Correct test interface for validation
+ *
+ * @author David Kral
+ */
+
+@Path("test/{first}")
+public interface CorrectInterface {
+
+    @GET
+    @Path("{second}")
+    @ClientHeaderParam(name = "test", value = "someValue")
+    void firstMethod(@PathParam("first") String first, @PathParam("second") String second);
+
+    @GET
+    @ClientHeaderParam(name = "test", value = "{value}")
+    void secondMethod(@PathParam("first") String first, String second);
+
+    @POST
+    @ClientHeaderParam(name = "test", value = "org.glassfish.jersey.restclient.CustomHeaderGenerator.customHeader")
+    void thirdMethod(@PathParam("first") String first);
+
+    @GET
+    @Path("{second}")
+    void fourthMethod(@PathParam("first") String first, @BeanParam BeanWithPathParam second);
+
+    default String value() {
+        return "testValue";
+    }
+
+    class CustomHeaderGenerator {
+
+        public static String customHeader() {
+            return "static";
+        }
+
+    }
+
+    class BeanWithPathParam {
+
+        @PathParam("second")
+        public String pathParam;
+
+    }
+
+}
diff --git a/ext/microprofile/mp-rest-client/src/test/java/org/glassfish/jersey/microprofile/restclient/InterfaceValidationTest.java b/ext/microprofile/mp-rest-client/src/test/java/org/glassfish/jersey/microprofile/restclient/InterfaceValidationTest.java
new file mode 100644
index 0000000..a810399
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/test/java/org/glassfish/jersey/microprofile/restclient/InterfaceValidationTest.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.microprofile.restclient;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+
+import org.junit.Test;
+
+/**
+ * @author David Kral
+ */
+public class InterfaceValidationTest {
+
+    @Test
+    public void testValidInterface() {
+        RestClientModel.from(CorrectInterface.class, new HashSet<>(), new HashSet<>(), new ArrayList<>(),
+                             null);
+    }
+
+}
diff --git a/ext/microprofile/mp-rest-client/src/test/java/org/glassfish/jersey/microprofile/restclient/RestClientModelTest.java b/ext/microprofile/mp-rest-client/src/test/java/org/glassfish/jersey/microprofile/restclient/RestClientModelTest.java
new file mode 100644
index 0000000..046f528
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/test/java/org/glassfish/jersey/microprofile/restclient/RestClientModelTest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.microprofile.restclient;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import org.eclipse.microprofile.rest.client.RestClientBuilder;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.TestProperties;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Created by David Kral.
+ */
+public class RestClientModelTest extends JerseyTest {
+    @Override
+    protected ResourceConfig configure() {
+        enable(TestProperties.LOG_TRAFFIC);
+        return new ResourceConfig(ApplicationResourceImpl.class);
+    }
+
+    @Test
+    public void testGetIt() throws URISyntaxException {
+        ApplicationResource app = RestClientBuilder.newBuilder()
+                .baseUri(new URI("http://localhost:9998"))
+                .build(ApplicationResource.class);
+        assertEquals("This is default value!", app.getValue());
+        assertEquals("Hi", app.sayHi());
+    }
+}
diff --git a/ext/microprofile/mp-rest-client/src/test/resources/arquillian.xml b/ext/microprofile/mp-rest-client/src/test/resources/arquillian.xml
new file mode 100644
index 0000000..e748760
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/test/resources/arquillian.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<arquillian xmlns="http://jboss.org/schema/arquillian"
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            xsi:schemaLocation="
+        http://jboss.org/schema/arquillian
+        http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
+
+    <engine>
+        <property name="deploymentExportPath">target/deployments</property>
+    </engine>
+</arquillian>
diff --git a/ext/microprofile/mp-rest-client/src/test/resources/server.policy b/ext/microprofile/mp-rest-client/src/test/resources/server.policy
new file mode 100644
index 0000000..edff3db
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/src/test/resources/server.policy
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+grant {
+  permission java.util.PropertyPermission "idea.launcher.bin.path", "read";
+  permission java.lang.RuntimePermission "loadLibrary.C:\\Program Files\\JetBrains\\IntelliJ IDEA 2019.1\\bin\\breakgen64.dll";
+  permission java.lang.RuntimePermission "accessDeclaredMembers";
+  permission java.lang.RuntimePermission "getClassLoader";
+  permission java.lang.RuntimePermission "createClassLoader";
+  permission java.lang.RuntimePermission "setContextClassLoader";
+  permission java.lang.RuntimePermission "getProtectionDomain";
+  permission java.lang.RuntimePermission "getenv.*";
+  permission java.lang.RuntimePermission "setIO";
+  permission java.io.FilePermission "C:\\Program Files\\JetBrains\\IntelliJ IDEA 2019.1\\bin\\breakgen64.dll", "read";
+  permission java.util.PropertyPermission "idea.launcher.port", "read";
+  permission java.util.PropertyPermission "idea.launcher.bin.path", "read";
+  permission java.util.PropertyPermission "jcommander.debug", "read";
+  permission java.util.PropertyPermission "user.dir", "read";
+  permission java.util.PropertyPermission "debug", "read";
+  permission java.util.PropertyPermission "arquillian.debug", "read";
+  permission java.util.PropertyPermission "trace", "read";
+  permission java.util.PropertyPermission "testng.test.classpath", "read";
+  permission java.util.PropertyPermission "localscoping", "read";
+  permission java.util.PropertyPermission "outfile", "read";
+  permission java.util.PropertyPermission "org.eclipse.microprofile.*", "read";
+  permission java.util.PropertyPermission "arquillian.xml", "read";
+  permission java.util.PropertyPermission "*", "read,write";
+  permission java.net.SocketPermission "127.0.0.1:*", "connect,resolve";
+  permission java.net.SocketPermission "*", "connect,resolve";
+  permission java.io.FilePermission "<<ALL FILES>>", "read,write,delete";
+  permission java.lang.reflect.ReflectPermission "suppressAccessChecks";
+  permission java.net.NetPermission "getProxySelector";
+  permission java.net.NetPermission "specifyStreamHandler";
+  permission java.lang.management.ManagementPermission "monitor";
+};
+
+grant codebase "file:${java.home}/-" {
+  permission java.security.AllPermission;
+};
+
+grant codebase "file:${settings.localRepository}/-" {
+  permission java.security.AllPermission;
+};
+
+grant codebase "file:${project.build.directory}/test-classes/-" {
+  permission java.util.PropertyPermission "idea.launcher.bin.path", "read";
+  permission java.lang.RuntimePermission "loadLibrary.C:\\Program Files\\JetBrains\\IntelliJ IDEA 2019.1\\bin\\breakgen64.dll";
+  permission java.io.FilePermission "C:\\Program Files\\JetBrains\\IntelliJ IDEA 2019.1\\bin\\breakgen64.dll", "read";
+  permission java.util.PropertyPermission "idea.launcher.port", "read";
+  permission java.util.PropertyPermission "idea.launcher.bin.path", "read";
+
+  permission java.io.FilePermission "<<ALL FILES>>", "read,write,delete";
+  permission java.net.SocketPermission "*", "connect,resolve";
+};
+
+grant codebase "file:${project.build.directory}/classes/-" {
+  permission java.util.PropertyPermission "idea.launcher.bin.path", "read";
+  permission java.lang.RuntimePermission "loadLibrary.C:\\Program Files\\JetBrains\\IntelliJ IDEA 2019.1\\bin\\breakgen64.dll";
+  permission java.io.FilePermission "C:\\Program Files\\JetBrains\\IntelliJ IDEA 2019.1\\bin\\breakgen64.dll", "read";
+  permission java.util.PropertyPermission "idea.launcher.port", "read";
+  permission java.util.PropertyPermission "idea.launcher.bin.path", "read";
+
+  permission java.io.FilePermission "<<ALL FILES>>", "read,write,delete";
+  permission java.net.SocketPermission "*", "connect,resolve";
+};
diff --git a/ext/microprofile/mp-rest-client/tck-suite.xml b/ext/microprofile/mp-rest-client/tck-suite.xml
new file mode 100644
index 0000000..3106d69
--- /dev/null
+++ b/ext/microprofile/mp-rest-client/tck-suite.xml
@@ -0,0 +1,42 @@
+<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
+<!--
+
+    Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+<suite name="microprofile-rest-client-TCK" verbose="2" configfailurepolicy="continue" >
+
+    <test name="microprofile-rest-client TCK">
+        <packages>
+            <package name="org.eclipse.microprofile.rest.client.tck.*">
+            </package>
+        </packages>
+        <classes>
+            <class name="org.eclipse.microprofile.rest.client.tck.InvokeWithJsonBProviderTest">
+                <methods>
+                    <exclude name="testGetExecutesForBothClients"/> <!-- Json in this test is malformed -->
+                </methods>
+            </class>
+        </classes>
+<!--        <classes>-->
+<!--            <class name="org.eclipse.microprofile.rest.client.tck.asynctests.AsyncMethodTest">-->
+<!--                <methods>-->
+<!--                    <include name="testAsyncInvocationInterceptorProvider"/>-->
+<!--                </methods>-->
+<!--            </class>-->
+<!--        </classes>-->
+    </test>
+
+</suite>
\ No newline at end of file
diff --git a/ext/microprofile/pom.xml b/ext/microprofile/pom.xml
new file mode 100644
index 0000000..7a50ddf
--- /dev/null
+++ b/ext/microprofile/pom.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>project</artifactId>
+        <groupId>org.glassfish.jersey.ext</groupId>
+        <version>2.29-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>jersey-microprofile</artifactId>
+    <packaging>pom</packaging>
+
+    <modules>
+        <module>mp-rest-client</module>
+    </modules>
+
+
+</project>
\ No newline at end of file
diff --git a/ext/pom.xml b/ext/pom.xml
index 22245f1..df0ccd7 100644
--- a/ext/pom.xml
+++ b/ext/pom.xml
@@ -56,6 +56,7 @@
         <module>spring4</module>
         <module>spring5</module>
         <module>wadl-doclet</module>
+        <module>microprofile</module>
     </modules>
 
     <dependencies>