111

When I try to use a non-standard HTTP Method like PATCH with URLConnection:

    HttpURLConnection conn = (HttpURLConnection) new URL("http://example.com").openConnection();
    conn.setRequestMethod("PATCH");

I get an exception:

java.net.ProtocolException: Invalid HTTP method: PATCH
at java.net.HttpURLConnection.setRequestMethod(HttpURLConnection.java:440)

Using a higher level API like Jersey generates the same error. Is there a workaround to issue a PATCH HTTP request?

kavai77
  • 6,282
  • 7
  • 33
  • 48

16 Answers16

67

There are a lot of good answers, so here is mine (not work in jdk12):

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;

public class SupportPatch {
    public static void main(String... args) throws IOException {
        allowMethods("PATCH");

        HttpURLConnection conn = (HttpURLConnection) new URL("http://example.com").openConnection();
        conn.setRequestMethod("PATCH");
    }

    private static void allowMethods(String... methods) {
        try {
            Field methodsField = HttpURLConnection.class.getDeclaredField("methods");

            Field modifiersField = Field.class.getDeclaredField("modifiers");
            modifiersField.setAccessible(true);
            modifiersField.setInt(methodsField, methodsField.getModifiers() & ~Modifier.FINAL);

            methodsField.setAccessible(true);

            String[] oldMethods = (String[]) methodsField.get(null);
            Set<String> methodsSet = new LinkedHashSet<>(Arrays.asList(oldMethods));
            methodsSet.addAll(Arrays.asList(methods));
            String[] newMethods = methodsSet.toArray(new String[0]);

            methodsField.set(null/*static field*/, newMethods);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new IllegalStateException(e);
        }
    }
}

It also uses reflection, but instead of hacking into every connection object we're hacking HttpURLConnection#methods static field which is used in the checks internally.

okutane
  • 13,754
  • 10
  • 59
  • 67
  • 8
    Very good answer, should be the accepted one because it solves the actual problem and does not suggests a workaround which depends on the receiving server – Feirell Feb 04 '18 at 14:22
  • 1
    indeed this solution works as a charm, as the one that uses the override property is dependent on the server (and in my case did not work)... – Amichai Ungar Jul 09 '18 at 11:20
  • Does this still work with Java 9? Or does the module thing restricts it – CLOVIS Dec 04 '18 at 10:40
  • @CLOVIS honestly, I don't know. If you're able to try it, please share results and I will update my answer. :) – okutane Dec 04 '18 at 12:39
  • 6
    Tried that with JDK12, but I got "java.lang.NoSuchFieldException: modifiers" – Kin Cheung Sep 05 '19 at 06:43
  • 2
    Reason: https://stackoverflow.com/questions/56039341/get-declared-fields-of-java-lang-reflect-fields-in-jdk12 – Kin Cheung Sep 05 '19 at 06:55
  • Tried this solution, and still getting `ProtocolException: Invalid HTTP method: PATCH`. – Acroyear Feb 03 '20 at 18:11
  • Just encountered a very strange behaviour: With this method trying shoot a PATCH request while using a preflight to bypass CORS, when I use specific domain in Access-Control-Allow-Origin it works, when changed to * its throwing the same notorious error FYI – Li3ro Sep 12 '21 at 06:59
  • This just helped me out a lot. I guess you'd call this a "monkey PATCH"? – Joshua Taylor Mar 01 '23 at 18:20
61

Yes there is workaround for this. Use

X-HTTP-Method-Override

. This header can be used in a POST request to “fake” other HTTP methods. Simply set the value of the X-HTTP-Method-Override header to the HTTP method you would like to actually perform. So use following code.

conn.setRequestProperty("X-HTTP-Method-Override", "PATCH");
conn.setRequestMethod("POST");
Asaph
  • 159,146
  • 25
  • 197
  • 199
sagar
  • 1,900
  • 5
  • 30
  • 45
  • 47
    This only works if the receiving end supports it. It still sends a "POST" down the line. – Joseph Jaquinta Oct 29 '15 at 21:19
  • 3
    If the receiver support it, then (to me) it is the cleanest way to proceed. – Maxime T Nov 20 '15 at 12:09
  • 5
    This method works when using HttpUrlConnection to call the Firebase REST API. – Andrew Kelly Dec 30 '16 at 14:17
  • 1
    @DuanBressan the protocol should not be an issue as long as the server supports either or both (it should only accept connections to HTTPS though.) – Alexis Wilke Sep 07 '17 at 16:36
  • first line is enough, after "X-HTTP-Method-Override", "POST" will be used by default – BiScOtTiNo Nov 21 '17 at 13:19
  • 8
    This is not a valid answer because it does not fix the problem on javas side. The server has to allow you to use `POST` and has to understand the `X-HTTP-Method-Override` field. See https://stackoverflow.com/a/46323891/3647724 for a better an actual fix – Feirell Feb 04 '18 at 14:30
  • Not an answer at all honestly. Why bother with "X-HTTP-Method-Override" and not just "RandomHederNameMakePostGoPAtcHlol". PATCH is a part of the standard, the made up header is not. – Fulluphigh Nov 03 '21 at 20:27
41

There is a Won't Fix bug in OpenJDK for this: https://bugs.openjdk.java.net/browse/JDK-7016595

However, with Apache Http-Components Client 4.2+ this is possible. It has a custom networking implementation, thus using all standard HTTP methods like PATCH is possible. It even has a HttpPatch class supporting the patch method.

CloseableHttpClient httpClient = HttpClients.createDefault();
HttpPatch httpPatch = new HttpPatch(new URI("http://example.com"));
CloseableHttpResponse response = httpClient.execute(httpPatch);

Maven Coordinates:

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.2+</version>
</dependency>
Fulluphigh
  • 506
  • 1
  • 8
  • 21
kavai77
  • 6,282
  • 7
  • 33
  • 48
27

If the project is on Spring/Gradle; the following solution will workout.

For the build.gradle, add the following dependency;

compile('org.apache.httpcomponents:httpclient:4.5.2')

And define the following bean in your @SpringBootApplication class inside the com.company.project;

 @Bean
 public RestTemplate restTemplate() {
  HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
  requestFactory.setReadTimeout(600000);
  requestFactory.setConnectTimeout(600000);
  return new RestTemplate(requestFactory);
 }

This solutions worked for me.

hirosht
  • 932
  • 2
  • 17
  • 31
13

In java 11+ you can use the HttpRequest class to do what you want:

import java.net.http.HttpRequest;

HttpRequest request = HttpRequest.newBuilder()
               .uri(URI.create(uri))
               .method("PATCH", HttpRequest.BodyPublishers.ofString(message))
               .header("Content-Type", "text/xml")
               .build();
simonalexander2005
  • 4,338
  • 4
  • 48
  • 92
9

Reflection as described in this post and a related post does not work if you are using a HttpsURLConnection on Oracle's JRE, becausesun.net.www.protocol.https.HttpsURLConnectionImpl is using the method field from the java.net.HttpURLConnection of its DelegateHttpsURLConnection!

So a complete working solution is:

private void setRequestMethod(final HttpURLConnection c, final String value) {
    try {
        final Object target;
        if (c instanceof HttpsURLConnectionImpl) {
            final Field delegate = HttpsURLConnectionImpl.class.getDeclaredField("delegate");
            delegate.setAccessible(true);
            target = delegate.get(c);
        } else {
            target = c;
        }
        final Field f = HttpURLConnection.class.getDeclaredField("method");
        f.setAccessible(true);
        f.set(target, value);
    } catch (IllegalAccessException | NoSuchFieldException ex) {
        throw new AssertionError(ex);
    }
}
Community
  • 1
  • 1
rmuller
  • 12,062
  • 4
  • 64
  • 92
  • 1
    If you're already using reflection, why not just add "PATCH" method by rewriting java.net.HttpURLConnection#methods value once per application lifetime? – okutane Apr 10 '17 at 21:10
  • Good point. However, my answer is only for showing how the suggested solution should work, not to show another solution – rmuller Apr 11 '17 at 05:35
  • @okutane, could you please provide little hint how can we re-write to methods? because I have seen few post talking about delegates in it – Coder Sep 20 '17 at 07:05
  • @Dhamayanthi I've posted separate answer for it. – okutane Sep 20 '17 at 13:49
  • could you please share the link? – Coder Oct 04 '17 at 10:55
3

I had the same exception and wrote sockets solution (in Groovy) but I translate in the answer form to Java for you:

String doInvalidHttpMethod(String method, String resource){
        Socket s = new Socket(InetAddress.getByName("google.com"), 80);
        PrintWriter pw = new PrintWriter(s.getOutputStream());
        pw.println(method +" "+resource+" HTTP/1.1");
        pw.println("User-Agent: my own");
        pw.println("Host: google.com:80");
        pw.println("Content-Type: */*");
        pw.println("Accept: */*");
        pw.println("");
        pw.flush();
        BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
        String t = null;
        String response = ""; 
        while((t = br.readLine()) != null){
            response += t;
        }
        br.close();
        return response;
    }

I think it works in Java. You have to change the server and port number remember change the Host header too and maybe you have to catch some exception.

halfer
  • 19,824
  • 17
  • 99
  • 186
3

Using the answer:

HttpURLConnection Invalid HTTP method: PATCH

I'm created a sample request and work like a charm:

public void request(String requestURL, String authorization, JsonObject json) {

    try {

        URL url = new URL(requestURL);
        httpConn = (HttpURLConnection) url.openConnection();
        httpConn.setRequestMethod("POST");
        httpConn.setRequestProperty("X-HTTP-Method-Override", "PATCH");
        httpConn.setRequestProperty("Content-Type", "application/json");
        httpConn.setRequestProperty("Authorization", authorization);
        httpConn.setRequestProperty("charset", "utf-8");

        DataOutputStream wr = new DataOutputStream(httpConn.getOutputStream());
        wr.writeBytes(json.toString());
        wr.flush();
        wr.close();

        httpConn.connect();

        String response = finish();

        if (response != null && !response.equals("")) {
            created = true;
        }
    } 
    catch (Exception e) {
        e.printStackTrace();
    }
}

public String finish() throws IOException {

    String response = "";

    int status = httpConn.getResponseCode();
    if (status == HttpURLConnection.HTTP_OK || status == HttpURLConnection.HTTP_CREATED) {
        BufferedReader reader = new BufferedReader(new InputStreamReader(
                httpConn.getInputStream()));
        String line = null;
        while ((line = reader.readLine()) != null) {
            response += line;
        }
        reader.close();
        httpConn.disconnect();
    } else {
        throw new IOException("Server returned non-OK status: " + status);
    }

    return response;
}

I hope it help you.

Community
  • 1
  • 1
Duan Bressan
  • 627
  • 6
  • 13
  • 1
    Your solution works if the connected server do accept and interpret request header 'X-HTTP-Method-Override'. Therefore your solution can't be used in all cases. – Emmanuel Devaux Oct 05 '18 at 14:14
2

For anyone using Spring restTemplate looking for a detailed answer.

You will face the problem if you are using SimpleClientHttpRequestFactory as your restTemplate's ClientHttpRequestFactory.

From java.net.HttpURLConnection:

/* valid HTTP methods */
private static final String[] methods = {
    "GET", "POST", "HEAD", "OPTIONS", "PUT", "DELETE", "TRACE"
};

As PATCH is not a supported operation, this line of code from the same class will execute:

throw new ProtocolException("Invalid HTTP method: " + method);

I ended up using the same as what @hirosht suggested in his answer.

Johnu
  • 361
  • 3
  • 5
1

Another dirty hack solution is reflection:

private void setVerb(HttpURLConnection cn, String verb) throws IOException {

  switch (verb) {
    case "GET":
    case "POST":
    case "HEAD":
    case "OPTIONS":
    case "PUT":
    case "DELETE":
    case "TRACE":
      cn.setRequestMethod(verb);
      break;
    default:
      // set a dummy POST verb
      cn.setRequestMethod("POST");
      try {
        // Change protected field called "method" of public class HttpURLConnection
        setProtectedFieldValue(HttpURLConnection.class, "method", cn, verb);
      } catch (Exception ex) {
        throw new IOException(ex);
      }
      break;
  }
}

public static <T> void setProtectedFieldValue(Class<T> clazz, String fieldName, T object, Object newValue) throws Exception {
    Field field = clazz.getDeclaredField(fieldName);

    field.setAccessible(true);
    field.set(object, newValue);
 }
Dave Moten
  • 11,957
  • 2
  • 40
  • 47
  • Works for http connections, but not for https. The sun.net.www.protocol.https.HttpsURLConnectionImpl class uses a "delegate" field containing the actual URL connection. So must change there instead. – Per Cederberg Jan 29 '17 at 02:29
0

You can find a detailed solution that can work even if you don't have direct access to the HttpUrlConnection (like when working with Jersey Client here: PATCH request using Jersey Client

Community
  • 1
  • 1
Daniel L.
  • 5,060
  • 10
  • 36
  • 59
0

If your server is using ASP.NET Core, you can simply add the following code to specify the HTTP method using the header X-HTTP-Method-Override, as described in the accepted answer.

app.Use((context, next) => {
    var headers = context.Request.Headers["X-HTTP-Method-Override"];
    if(headers.Count == 1) {
        context.Request.Method = headers.First();
    }
    return next();
});

Simply add this code in Startup.Configure before your call to app.UseMvc().

Community
  • 1
  • 1
Peppe L-G
  • 7,351
  • 2
  • 25
  • 50
0

In emulator of API 16 I received an exception: java.net.ProtocolException: Unknown method 'PATCH'; must be one of [OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE].

While an accepted answer works, I want to add one detail. In new APIs PATCH works well, so in conjunction with https://github.com/OneDrive/onedrive-sdk-android/issues/16 you should write:

if (method.equals("PATCH") && Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
    httpConnection.setRequestProperty("X-HTTP-Method-Override", "PATCH");
    httpConnection.setRequestMethod("POST");
} else {
    httpConnection.setRequestMethod(method);
}

I changed JELLY_BEAN_MR2 to KITKAT after testing in API 16, 19, 21.

CoolMind
  • 26,736
  • 15
  • 188
  • 224
0

I got mine with Jersey client. The workaround was:

Client client = ClientBuilder.newClient();
client.property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true);
Daniel Jipa
  • 878
  • 11
  • 24
0

We have faced the same problem with slightly different behavior. We were using apache cxf library for making the rest calls. For us, PATCH was working fine till we were talking to our fake services which were working over http. The moment we integrated with actual systems (which were over https) we started facing the same issue with following stack trace.

java.net.ProtocolException: Invalid HTTP method: PATCH  at java.net.HttpURLConnection.setRequestMethod(HttpURLConnection.java:428) ~[na:1.7.0_51]   at sun.net.www.protocol.https.HttpsURLConnectionImpl.setRequestMethod(HttpsURLConnectionImpl.java:374) ~[na:1.7.0_51]   at org.apache.cxf.transport.http.URLConnectionHTTPConduit.setupConnection(URLConnectionHTTPConduit.java:149) ~[cxf-rt-transports-http-3.1.14.jar:3.1.14]

Issue was happening in this line of code

connection.setRequestMethod(httpRequestMethod); in URLConnectionHTTPConduit class of cxf library

Now the real reason for the failure is that

java.net.HttpURLConnection contains a methods variable which looks like below
/* valid HTTP methods */
    private static final String[] methods = {
        "GET", "POST", "HEAD", "OPTIONS", "PUT", "DELETE", "TRACE"
    };

And we can see that there is no PATCH method defined hence the error made sense. We tried lots of different thing and looked over stack overflow. The only reasonable answer was to use reflection to modify the methods variable to inject another value "PATCH". But somehow we were not convinced to use that as the solution was kind of hack and is too much work and might have impact as we had common library to make all connection and performing these REST calls.

But then we realized that cxf library itself is handling the exception and there is code written in the catch block to add the missing method using reflection.

try {
        connection.setRequestMethod(httpRequestMethod);
    } catch (java.net.ProtocolException ex) {
        Object o = message.getContextualProperty(HTTPURL_CONNECTION_METHOD_REFLECTION);
        boolean b = DEFAULT_USE_REFLECTION;
        if (o != null) {
            b = MessageUtils.isTrue(o);
        }
        if (b) {
            try {
                java.lang.reflect.Field f = ReflectionUtil.getDeclaredField(HttpURLConnection.class, "method");
                if (connection instanceof HttpsURLConnection) {
                    try {
                        java.lang.reflect.Field f2 = ReflectionUtil.getDeclaredField(connection.getClass(),
                                                                                     "delegate");
                        Object c = ReflectionUtil.setAccessible(f2).get(connection);
                        if (c instanceof HttpURLConnection) {
                            ReflectionUtil.setAccessible(f).set(c, httpRequestMethod);
                        }

                        f2 = ReflectionUtil.getDeclaredField(c.getClass(), "httpsURLConnection");
                        HttpsURLConnection c2 = (HttpsURLConnection)ReflectionUtil.setAccessible(f2)
                                .get(c);

                        ReflectionUtil.setAccessible(f).set(c2, httpRequestMethod);
                    } catch (Throwable t) {
                        //ignore
                        logStackTrace(t);
                    }
                }
                ReflectionUtil.setAccessible(f).set(connection, httpRequestMethod);
                message.put(HTTPURL_CONNECTION_METHOD_REFLECTION, true);
            } catch (Throwable t) {
                logStackTrace(t);
                throw ex;
            }
        }

Now this gave us some hopes, so we spent some time in reading the code and found that if we provide a property for URLConnectionHTTPConduit.HTTPURL_CONNECTION_METHOD_REFLECTION then we can make cxf to execute the exception handler and our work is done as by default the variable will be assigned to false due to below code

DEFAULT_USE_REFLECTION = 
        Boolean.valueOf(SystemPropertyAction.getProperty(HTTPURL_CONNECTION_METHOD_REFLECTION, "false"));

So here is what we had to do to make this work

WebClient.getConfig(client).getRequestContext().put("use.httpurlconnection.method.reflection", true);

or

WebClient.getConfig(client).getRequestContext().put(HTTPURL_CONNECTION_METHOD_REFLECTION, true);

Where WebClient is from cxf library itself.

Hope this answer helps some one.

Sanjay Bharwani
  • 3,317
  • 34
  • 31
0
 **CloseableHttpClient http = HttpClientBuilder.create().build();
            HttpPatch updateRequest = new HttpPatch("URL");
            updateRequest.setEntity(new StringEntity("inputjsonString", ContentType.APPLICATION_JSON));
            updateRequest.setHeader("Bearer", "auth");
            HttpResponse response = http.execute(updateRequest);
JSONObject result = new JSONObject(IOUtils.toString(response.getEntity().getContent()));**

maven plugin


> <dependency>
>                 <groupId>org.apache.httpcomponents</groupId>
>                 <artifactId>httpclient</artifactId>
>                 <version>4.3.4</version>
>                 <!-- Exclude Commons Logging in favor of SLF4j -->
>                 <exclusions>
>                     <exclusion>
>                         <groupId>commons-logging</groupId>
>                         <artifactId>commons-logging</artifactId>
>                     </exclusion>
>                 </exclusions> 
>             </dependency>

use this really it would helps you