Repository: square/retrofit
Issue: PATCH method support

The `PATCH` method can be used to only edit some properties of a resource, instead of overwriting it as `PUT` does. Unfortunately, Retrofit doesn't offer support for `PATCH` requests. Are there any plans of adding this request method?


Issue comments:
one of the challenges of PATCH is idiomatically handling it.

the json-patch rfc is pretty good though sadly no gson impl exists for it.
 there's a jackson LGPL project though https://github.com/fge/json-patch

On Wed, Jul 24, 2013 at 10:33 AM, Marijn van der Werf <
notifications@github.com> wrote:

> The PATCH method can be used to only edit some properties of a resource,
> instead of overwriting it as PUT does. Unfortunately, Retrofit doesn't
> offer support for PATCH requests. Are there any plans of adding this
> request method?
> 
> —
> Reply to this email directly or view it on GitHubhttps://github.com/square/retrofit/issues/282
> .

You can also add a custom HTTP method annotation yourself.

``` java
@RestMethod("PATCH")
@Target(METHOD) @Retention(RUNTIME)
private @interface PATCH {
  String value();
}
```

I already tried that, by replicating the PUT annotation (with `@RestMethod(value = "PATCH", hasBody = true)`). Unfortunately this causes a `retrofit.RetrofitError`: `java.net.ProtocolException: Unknown method 'PATCH'; must be one of [OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE]`.

Is there a way to add a `X-HTTP-Method-Override: PUT` only to specific interface methods?

As for a parseable response: I was planning to return a full object, but I understand that using a json-patch instead would possibly make things quite complicated.

That's a bug then! A custom annotation is the right way to do this. We can't feasibly support every HTTP method on the planet (since you can make arbitrary ones) and like @adriancole PATCH has certain semantics which we can't know.

Can you paste the full stack trace?

Will this suffice?

```
retrofit.RetrofitError: java.net.ProtocolException: Unknown method 'PATCH'; must be one of [OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE]
    at retrofit.RestAdapter$RestHandler.invokeRequest(RestAdapter.java:292)
    at retrofit.RestAdapter$RestHandler.access$400(RestAdapter.java:157)
    at retrofit.RestAdapter$RestHandler$1.obtainResponse(RestAdapter.java:199)
    at retrofit.CallbackRunnable.run(CallbackRunnable.java:38)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1080)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:573)
    at retrofit.Platform$Android$2$1.run(Platform.java:132)
    at java.lang.Thread.run(Thread.java:841)
Caused by: java.net.ProtocolException: Unknown method 'PATCH'; must be one of [OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE]
    at java.net.HttpURLConnection.setRequestMethod(HttpURLConnection.java:677)
    at retrofit.client.UrlConnectionClient.prepareRequest(UrlConnectionClient.java:41)
    at retrofit.client.UrlConnectionClient.execute(UrlConnectionClient.java:32)
    at retrofit.RestAdapter$RestHandler.invokeRequest(RestAdapter.java:241)
    at retrofit.RestAdapter$RestHandler.access$400(RestAdapter.java:157)
    at retrofit.RestAdapter$RestHandler$1.obtainResponse(RestAdapter.java:199)
    at retrofit.CallbackRunnable.run(CallbackRunnable.java:38)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1080)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:573)
    at retrofit.Platform$Android$2$1.run(Platform.java:132)
    at java.lang.Thread.run(Thread.java:841)
```

Yep. I just needed to see which client this was happening in so I can easily reproduce.

take a look at #294 

I am attempting to use @PATCH. The retrofit logs say "PATCH", but my server says it is receiving a POST. I have tried both OkClient and UrlConnectionClient.

@alecplumb can you use okhttp's MockWebServer to confirm this in a test case?

I ran into this issue (v1.6.1) as well, was wondering if `PATCH` is supported. I am new to Android/Retrofit so not sure if this has been addressed.

```
D/Retrofit﹕ java.net.ProtocolException: Unknown method 'PATCH'; must be one of [OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE]
            at java.net.HttpURLConnection.setRequestMethod(HttpURLConnection.java:677)
            at retrofit.client.UrlConnectionClient.prepareRequest(UrlConnectionClient.java:50)
            at retrofit.client.UrlConnectionClient.execute(UrlConnectionClient.java:37)
            at retrofit.RestAdapter$RestHandler.invokeRequest(RestAdapter.java:321)
            at retrofit.RestAdapter$RestHandler.access$100(RestAdapter.java:220)
            at retrofit.RestAdapter$RestHandler$2.obtainResponse(RestAdapter.java:278)
            at retrofit.CallbackRunnable.run(CallbackRunnable.java:42)
            at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1080)
            at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:573)
            at retrofit.Platform$Android$2$1.run(Platform.java:142)
            at java.lang.Thread.run(Thread.java:841)
```

That's a limitation of the built-in HttpUrlConnection. You need to use
OkHttp or Apache HttpClient instead.
On Jul 20, 2014 2:49 PM, "Kapil" notifications@github.com wrote:

> I ran into this issue (v1.6.1) as well, was wondering if PATCH is
> supported. I am new to Android/Retrofit so not sure if this has been
> addressed.
> 
> D/Retrofit﹕ java.net.ProtocolException: Unknown method 'PATCH'; must be one of [OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE]
>             at java.net.HttpURLConnection.setRequestMethod(HttpURLConnection.java:677)
>             at retrofit.client.UrlConnectionClient.prepareRequest(UrlConnectionClient.java:50)
>             at retrofit.client.UrlConnectionClient.execute(UrlConnectionClient.java:37)
>             at retrofit.RestAdapter$RestHandler.invokeRequest(RestAdapter.java:321)
>             at retrofit.RestAdapter$RestHandler.access$100(RestAdapter.java:220)
>             at retrofit.RestAdapter$RestHandler$2.obtainResponse(RestAdapter.java:278)
>             at retrofit.CallbackRunnable.run(CallbackRunnable.java:42)
>             at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1080)
>             at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:573)
>             at retrofit.Platform$Android$2$1.run(Platform.java:142)
>             at java.lang.Thread.run(Thread.java:841)
> 
> —
> Reply to this email directly or view it on GitHub
> https://github.com/square/retrofit/issues/282#issuecomment-49560672.

Thanks. How can I get the latest `okhttp` build, I am getting this error -

I downloaded `v2.0.0` from here - http://square.github.io/okhttp/

```
E/AndroidRuntime﹕ FATAL EXCEPTION: main
    java.lang.RuntimeException: Retrofit detected an unsupported OkHttp on the classpath.
    To use OkHttp with this version of Retrofit, you'll need:
    1. com.squareup.okhttp:okhttp:1.6.0 (or newer)
    2. com.squareup.okhttp:okhttp-urlconnection:1.6.0 (or newer)
    Note that OkHttp 2.0.0+ is supported!
            at retrofit.Platform.hasOkHttpOnClasspath(Platform.java:186)
```

You need both the okhttp jar and the okhttp-urlconnection jar. Downloads are here: http://search.maven.org/#search%7Cga%7C1%7Cg%3Acom.squareup.okhttp

Now I get this error -

```
Retrofit﹕ java.lang.NoClassDefFoundError: com.squareup.okhttp.internal.http.RetryableSink
            at com.squareup.okhttp.internal.Util.<clinit>(Util.java:231)
            at com.squareup.okhttp.ConnectionPool.<init>(ConnectionPool.java:82)
            at com.squareup.okhttp.ConnectionPool.<clinit>(ConnectionPool.java:71)
            at com.squareup.okhttp.OkHttpClient.copyWithDefaults(OkHttpClient.java:477)
            at com.squareup.okhttp.OkUrlFactory.open(OkUrlFactory.java:66)
            at com.squareup.okhttp.OkUrlFactory.open(OkUrlFactory.java:61)
            at retrofit.client.OkClient.openConnection(OkClient.java:45)
            at retrofit.client.UrlConnectionClient.execute(UrlConnectionClient.java:36)
            at retrofit.RestAdapter$RestHandler.invokeRequest(RestAdapter.java:321)
            at retrofit.RestAdapter$RestHandler.access$100(RestAdapter.java:220)
            at retrofit.RestAdapter$RestHandler$2.obtainResponse(RestAdapter.java:278)
            at retrofit.CallbackRunnable.run(CallbackRunnable.java:42)
            at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1080)
            at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:573)
            at retrofit.Platform$Android$2$1.run(Platform.java:142)
            at java.lang.Thread.run(Thread.java:841)
```

I am using Android Studio and have added both the jars to Gradle dependency -

```
dependencies {
   compile files('libs/retrofit-1.6.1.jar')
   compile files('libs/gson-2.2.4.jar')
   compile files('libs/okhttp-2.0.0.jar')
   compile files('libs/okhttp-urlconnection-2.0.0.jar')
}
```

If you are using Gradle you can stop ferrying jars around. Try:

``` groovy
dependencies {
  compile 'com.squareup.retrofit:retrofit:1.6.1'
  compile 'com.squareup.okhttp:okhttp:2.0.0'
  compile 'com.squareup.okhttp:okhttp-urlconnection:2.0.0'
}
```

Thanks yea that worked :thumbsup: 

Hey @kapso, do you mind to share how to get PATCH to work in Android Studio?  I am new to retrofit, so any help will be highly appreciated.

Thanks!

@pdichone You just need to add the gradle the following dependences:

```
compile 'com.squareup.retrofit:retrofit:1.9.0'
compile 'com.squareup.okhttp:okhttp:2.0.0'
compile 'com.squareup.okhttp:okhttp-urlconnection:2.0.0'
```

And the patch will start working.

As of March 2016, with Retrofit `2.0.0-beta4` I am using PATCH successfully without any further libraries inclusion as mentioned in the comments here.

Could someone give an example of using a PATCH request? I am sending data to server but I am receiving 400 error in response. The server is seeing some weird characters as input and not decipherable data.

Client Interface in Android is here

`@PATCH("employeeJobs/{id}")
    Call<EmployeeJob> UpdateEmpJob(@Body HashMap empJob, @Path("id") int id);`

Where my `empJob` HashMap has as many properties of empJob entity as I want.

Corresponding .Net WebApi generic controller which is inherited by all other controllers has this `PATCH` method.

```
    [Route("{id:int}")]
    [HttpPatch]
    public IHttpActionResult Patch(int id, Delta<T> ent)
    {
        if (!ModelState.IsValid) return BadRequest(ModelState);

        T origEnt = dbSet.Single(p => p.Id == id);

        if (origEnt == null) return NotFound();

        try
        {
            ent.Patch(origEnt);
            db.SaveChanges();

            return Ok(origEnt);
        }
        catch (Exception e)
        {
            while (e.InnerException != null) e = e.InnerException;
            return BadRequest(e.Message);
        }
    }
```

`


Pull request review comments:
:-o

How did this pass checkstyle?

And why did idea insist on doing that :p NP will fix when I get back online.

On Monday, August 5, 2013, Jake Wharton wrote:

> In retrofit/src/test/java/retrofit/RestMethodInfoTest.java:
> 
> > -import retrofit.http.Body;
> > -import retrofit.http.DELETE;
> > -import retrofit.http.Field;
> > -import retrofit.http.FormUrlEncoded;
> > -import retrofit.http.GET;
> > -import retrofit.http.HEAD;
> > -import retrofit.http.Header;
> > -import retrofit.http.Headers;
> > -import retrofit.http.Multipart;
> > -import retrofit.http.POST;
> > -import retrofit.http.PUT;
> > -import retrofit.http.Part;
> > -import retrofit.http.Path;
> > -import retrofit.http.Query;
> > -import retrofit.http.RestMethod;
> > +import retrofit.http.*;
> 
> :-o
> 
> How did this pass checkstyle?
> 
> —
> Reply to this email directly or view it on GitHubhttps://github.com/square/retrofit/pull/294/files#r5587198
> .

UGH! That's awful.

Hmm tests don't seem to be run through checkstyle. Not sure how I feel about that.

Kinda stupid that `setRequestMethod` uses strings for a fixed set of valid values. Great that you found a way to bypass that though!


Patch that resolved the issue:
diff --git a/retrofit/src/main/java/retrofit/client/UrlConnectionClient.java b/retrofit/src/main/java/retrofit/client/UrlConnectionClient.java
index d7375d8b29..861ad3615d 100644
--- a/retrofit/src/main/java/retrofit/client/UrlConnectionClient.java
+++ b/retrofit/src/main/java/retrofit/client/UrlConnectionClient.java
@@ -17,16 +17,31 @@
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.lang.reflect.Field;
 import java.net.HttpURLConnection;
+import java.net.ProtocolException;
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+
+import retrofit.RetrofitError;
 import retrofit.mime.TypedInput;
 import retrofit.mime.TypedOutput;
 
 /** Retrofit client that uses {@link HttpURLConnection} for communication. */
 public class UrlConnectionClient implements Client {
+  private final Field methodField;
+
+  public UrlConnectionClient() {
+    try {
+      this.methodField = HttpURLConnection.class.getDeclaredField("method");
+      this.methodField.setAccessible(true);
+    } catch (NoSuchFieldException e) {
+      throw RetrofitError.unexpectedError(null, e);
+    }
+  }
+
   @Override public Response execute(Request request) throws IOException {
     HttpURLConnection connection = openConnection(request);
     prepareRequest(connection, request);
@@ -42,7 +57,17 @@ protected HttpURLConnection openConnection(Request request) throws IOException {
   }
 
   void prepareRequest(HttpURLConnection connection, Request request) throws IOException {
-    connection.setRequestMethod(request.getMethod());
+    // HttpURLConnection artificially restricts request method
+    try {
+      connection.setRequestMethod(request.getMethod());
+    } catch (ProtocolException e) {
+      try {
+        methodField.set(connection, request.getMethod());
+      } catch (IllegalAccessException e1) {
+        throw RetrofitError.unexpectedError(request.getUrl(), e1);
+      }
+    }
+
     connection.setDoInput(true);
 
     for (Header header : request.getHeaders()) {
diff --git a/retrofit/src/main/java/retrofit/http/PATCH.java b/retrofit/src/main/java/retrofit/http/PATCH.java
new file mode 100644
index 0000000000..57f1739152
--- /dev/null
+++ b/retrofit/src/main/java/retrofit/http/PATCH.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2013 Square, Inc.
+ *
+ * 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 retrofit.http;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/** Make a PATCH request to a REST path relative to base URL. */
+@Target(METHOD)
+@Retention(RUNTIME)
+@RestMethod(value = "PATCH", hasBody = true)
+public @interface PATCH {
+  String value();
+}


Test patch:
diff --git a/retrofit/src/test/java/retrofit/RestMethodInfoTest.java b/retrofit/src/test/java/retrofit/RestMethodInfoTest.java
index 064c450d97..7d86636612 100644
--- a/retrofit/src/test/java/retrofit/RestMethodInfoTest.java
+++ b/retrofit/src/test/java/retrofit/RestMethodInfoTest.java
@@ -20,6 +20,7 @@
 import retrofit.http.Header;
 import retrofit.http.Headers;
 import retrofit.http.Multipart;
+import retrofit.http.PATCH;
 import retrofit.http.POST;
 import retrofit.http.PUT;
 import retrofit.http.Part;
@@ -315,6 +316,22 @@ class Example {
     assertThat(methodInfo.requestUrl).isEqualTo("/foo");
   }
 
+  @Test public void patchMethod() {
+    class Example {
+      @PATCH("/foo") Response a() {
+        return null;
+      }
+    }
+
+    Method method = TestingUtils.getMethod(Example.class, "a");
+    RestMethodInfo methodInfo = new RestMethodInfo(method);
+    methodInfo.init();
+
+    assertThat(methodInfo.requestMethod).isEqualTo("PATCH");
+    assertThat(methodInfo.requestHasBody).isTrue();
+    assertThat(methodInfo.requestUrl).isEqualTo("/foo");
+  }
+
   @RestMethod("CUSTOM1")
   @Target(METHOD) @Retention(RUNTIME)
   private @interface CUSTOM1 {
diff --git a/retrofit/src/test/java/retrofit/client/UrlConnectionClientTest.java b/retrofit/src/test/java/retrofit/client/UrlConnectionClientTest.java
index 68fef779d9..e00f321ce9 100644
--- a/retrofit/src/test/java/retrofit/client/UrlConnectionClientTest.java
+++ b/retrofit/src/test/java/retrofit/client/UrlConnectionClientTest.java
@@ -37,6 +37,22 @@ public class UrlConnectionClientTest {
     assertThat(connection.getHeaderFields()).isEmpty();
   }
 
+  @Test public void patch() throws Exception {
+    TypedString body = new TypedString("hi");
+    Request request = new Request("PATCH", HOST + "/foo/bar/", null, body);
+
+    DummyHttpUrlConnection connection = (DummyHttpUrlConnection) client.openConnection(request);
+    client.prepareRequest(connection, request);
+
+    assertThat(connection.getRequestMethod()).isEqualTo("PATCH");
+    assertThat(connection.getURL().toString()).isEqualTo(HOST + "/foo/bar/");
+    assertThat(connection.getRequestProperties()).hasSize(2);
+    assertThat(connection.getRequestProperty("Content-Type")) //
+        .isEqualTo("text/plain; charset=UTF-8");
+    assertThat(connection.getRequestProperty("Content-Length")).isEqualTo("2");
+    assertBytes(connection.getOutputStream().toByteArray(), "hi");
+  }
+
   @Test public void post() throws Exception {
     TypedString body = new TypedString("hi");
     Request request = new Request("POST", HOST + "/foo/bar/", null, body);