diff --git a/jaxrs2/pom.xml b/jaxrs2/pom.xml
index 4f65eb80..c2002fc5 100644
--- a/jaxrs2/pom.xml
+++ b/jaxrs2/pom.xml
@@ -78,5 +78,28 @@
test-jar
test
+
+
+ org.glassfish.jersey.core
+ jersey-client
+ 2.26
+ test
+
+
+ org.glassfish.jersey.inject
+ jersey-hk2
+ 2.26
+ test
+
+
+ com.squareup.okhttp3
+ mockwebserver
+ test
+
+
+ org.hamcrest
+ java-hamcrest
+ 2.0.0.0
+
diff --git a/jaxrs2/src/main/java/feign/jaxrs2/JAXRSClient.java b/jaxrs2/src/main/java/feign/jaxrs2/JAXRSClient.java
new file mode 100644
index 00000000..22b6022e
--- /dev/null
+++ b/jaxrs2/src/main/java/feign/jaxrs2/JAXRSClient.java
@@ -0,0 +1,134 @@
+/**
+ * Copyright 2012-2018 The Feign Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package feign.jaxrs2;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.Charset;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.*;
+import feign.Client;
+import feign.Request.Options;
+
+/**
+ * This module directs Feign's http requests to javax.ws.rs.client.Client . Ex:
+ *
+ *
+ * GitHub github =
+ * Feign.builder().client(new JaxRSClient()).target(GitHub.class, "https://api.github.com");
+ *
+ */
+public class JAXRSClient implements Client {
+
+ private final ClientBuilder clientBuilder;
+
+ public JAXRSClient() {
+ this(ClientBuilder.newBuilder());
+ }
+
+ public JAXRSClient(ClientBuilder clientBuilder) {
+ this.clientBuilder = clientBuilder;
+ }
+
+ @Override
+ public feign.Response execute(feign.Request request, Options options) throws IOException {
+ final Response response = clientBuilder
+ .connectTimeout(options.connectTimeoutMillis(), TimeUnit.MILLISECONDS)
+ .readTimeout(options.readTimeoutMillis(), TimeUnit.MILLISECONDS)
+ .build()
+ .target(request.url())
+ .request()
+ .headers(toMultivaluedMap(request.headers()))
+ .method(request.method(), createRequestEntity(request));
+
+ return feign.Response.builder()
+ .request(request)
+ .body(response.readEntity(InputStream.class),
+ integerHeader(response, HttpHeaders.CONTENT_LENGTH))
+ .headers(toMap(response.getStringHeaders()))
+ .status(response.getStatus())
+ .reason(response.getStatusInfo().getReasonPhrase())
+ .build();
+ }
+
+ private Entity createRequestEntity(feign.Request request) {
+ if (request.body() == null) {
+ return null;
+ }
+
+ return Entity.entity(
+ request.body(),
+ new Variant(mediaType(request.headers()), locale(request.headers()),
+ encoding(request.charset())));
+ }
+
+ private Integer integerHeader(Response response, String header) {
+ final MultivaluedMap headers = response.getStringHeaders();
+ if (!headers.containsKey(header)) {
+ return null;
+ }
+
+ try {
+ return new Integer(headers.getFirst(header));
+ } catch (final NumberFormatException e) {
+ // not a number or too big to fit Integer
+ return null;
+ }
+ }
+
+ private String encoding(Charset charset) {
+ if (charset == null)
+ return null;
+
+ return charset.name();
+ }
+
+ private String locale(Map> headers) {
+ if (!headers.containsKey(HttpHeaders.CONTENT_LANGUAGE))
+ return null;
+
+ return headers.get(HttpHeaders.CONTENT_LANGUAGE).iterator().next();
+ }
+
+ private MediaType mediaType(Map> headers) {
+ if (!headers.containsKey(HttpHeaders.CONTENT_TYPE))
+ return null;
+
+ return MediaType.valueOf(headers.get(HttpHeaders.CONTENT_TYPE).iterator().next());
+ }
+
+ private MultivaluedMap toMultivaluedMap(Map> headers) {
+ final MultivaluedHashMap mvHeaders = new MultivaluedHashMap<>();
+
+ headers.entrySet().forEach(entry -> entry.getValue().stream()
+ .forEach(value -> mvHeaders.add(entry.getKey(), value)));
+
+ return mvHeaders;
+ }
+
+ private Map> toMap(MultivaluedMap headers) {
+ return headers.entrySet().stream()
+ .collect(Collectors.toMap(
+ Entry::getKey,
+ Entry::getValue));
+ }
+
+}
+
diff --git a/jaxrs2/src/test/java/feign/jaxrs2/JAXRSClientTest.java b/jaxrs2/src/test/java/feign/jaxrs2/JAXRSClientTest.java
new file mode 100644
index 00000000..7ec10b87
--- /dev/null
+++ b/jaxrs2/src/test/java/feign/jaxrs2/JAXRSClientTest.java
@@ -0,0 +1,122 @@
+/**
+ * Copyright 2012-2018 The Feign Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package feign.jaxrs2;
+
+import feign.Feign.Builder;
+import feign.Headers;
+import feign.RequestLine;
+import feign.Response;
+import feign.Util;
+import feign.assertj.MockWebServerAssertions;
+import feign.client.AbstractClientTest;
+import feign.jaxrs2.JAXRSClient;
+import feign.Feign;
+import okhttp3.mockwebserver.MockResponse;
+import org.junit.Test;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import javax.ws.rs.ProcessingException;
+import static feign.Util.UTF_8;
+import static java.util.Arrays.asList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.assertEquals;
+import org.junit.Assume;
+
+/** Tests client-specific behavior, such as ensuring Content-Length is sent when specified. */
+public class JAXRSClientTest extends AbstractClientTest {
+
+ @Override
+ public Builder newBuilder() {
+ return Feign.builder().client(new JAXRSClient());
+ }
+
+ @Override
+ public void testPatch() throws Exception {
+ try {
+ super.testPatch();
+ } catch (final ProcessingException e) {
+ Assume.assumeNoException("JaxRS client do not support PATCH requests", e);
+ }
+ }
+
+ @Override
+ public void noResponseBodyForPut() {
+ try {
+ super.noResponseBodyForPut();
+ } catch (final IllegalStateException e) {
+ Assume.assumeNoException("JaxRS client do not support empty bodies on PUT", e);
+ }
+ }
+
+ @Test
+ public void reasonPhraseIsOptional() throws IOException, InterruptedException {
+ server.enqueue(new MockResponse().setStatus("HTTP/1.1 " + 200));
+
+ final TestInterface api = newBuilder()
+ .target(TestInterface.class, "http://localhost:" + server.getPort());
+
+ final Response response = api.post("foo");
+
+ assertThat(response.status()).isEqualTo(200);
+ // jaxrsclient is creating a reason when none is present
+ // assertThat(response.reason()).isNullOrEmpty();
+ }
+
+ @Test
+ public void parsesRequestAndResponse() throws IOException, InterruptedException {
+ server.enqueue(new MockResponse().setBody("foo").addHeader("Foo: Bar"));
+
+ final TestInterface api = newBuilder()
+ .target(TestInterface.class, "http://localhost:" + server.getPort());
+
+ final Response response = api.post("foo");
+
+ assertThat(response.status()).isEqualTo(200);
+ assertThat(response.reason()).isEqualTo("OK");
+ assertThat(response.headers())
+ .containsEntry("Content-Length", asList("3"))
+ .containsEntry("Foo", asList("Bar"));
+ assertThat(response.body().asInputStream())
+ .hasContentEqualTo(new ByteArrayInputStream("foo".getBytes(UTF_8)));
+
+ MockWebServerAssertions.assertThat(server.takeRequest()).hasMethod("POST")
+ .hasPath("/?foo=bar&foo=baz&qux=")
+ .hasBody("foo");
+ }
+
+ @Test
+ public void testContentTypeWithoutCharset2() throws Exception {
+ server.enqueue(new MockResponse()
+ .setBody("AAAAAAAA"));
+ final JaxRSClientTestInterface api = newBuilder()
+ .target(JaxRSClientTestInterface.class, "http://localhost:" + server.getPort());
+
+ final Response response = api.getWithContentType();
+ // Response length should not be null
+ assertEquals("AAAAAAAA", Util.toString(response.body().asReader()));
+
+ MockWebServerAssertions.assertThat(server.takeRequest())
+ .hasHeaders("Accept: text/plain", "Content-Type: text/plain") // Note: OkHttp adds content
+ // length.
+ .hasMethod("GET");
+ }
+
+
+ public interface JaxRSClientTestInterface {
+
+ @RequestLine("GET /")
+ @Headers({"Accept: text/plain", "Content-Type: text/plain"})
+ Response getWithContentType();
+ }
+}