HttpURLConnection Invalid HTTP method: PATCH
Asked Answered
G

16

118

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?

Gerstner answered 6/8, 2014 at 14:32 Comment(0)
I
62

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");
Intercross answered 10/9, 2015 at 13:24 Comment(7)
This only works if the receiving end supports it. It still sends a "POST" down the line.Allanallana
If the receiver support it, then (to me) it is the cleanest way to proceed.Upset
This method works when using HttpUrlConnection to call the Firebase REST API.Koerner
@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.)Feltie
first line is enough, after "X-HTTP-Method-Override", "POST" will be used by defaultSeppala
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://mcmap.net/q/100293/-httpurlconnection-invalid-http-method-patch for a better an actual fixDebussy
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.Spirituality
D
72

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.

Delastre answered 20/9, 2017 at 13:48 Comment(9)
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 serverDebussy
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)...Hintze
Does this still work with Java 9? Or does the module thing restricts itShitty
@Shitty honestly, I don't know. If you're able to try it, please share results and I will update my answer. :)Delastre
Tried that with JDK12, but I got "java.lang.NoSuchFieldException: modifiers"Showily
Reason: #56039841Showily
Tried this solution, and still getting ProtocolException: Invalid HTTP method: PATCH.Nosing
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 FYISholley
This just helped me out a lot. I guess you'd call this a "monkey PATCH"?Loats
I
62

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");
Intercross answered 10/9, 2015 at 13:24 Comment(7)
This only works if the receiving end supports it. It still sends a "POST" down the line.Allanallana
If the receiver support it, then (to me) it is the cleanest way to proceed.Upset
This method works when using HttpUrlConnection to call the Firebase REST API.Koerner
@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.)Feltie
first line is enough, after "X-HTTP-Method-Override", "POST" will be used by defaultSeppala
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://mcmap.net/q/100293/-httpurlconnection-invalid-http-method-patch for a better an actual fixDebussy
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.Spirituality
G
42

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>
Gerstner answered 6/8, 2014 at 14:32 Comment(2)
Why do you call Patch non standard? It's very much part of the standard...Destruction
This should be the answer, but with the non-standard statement removedSpirituality
L
29

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.

Lamaism answered 24/10, 2017 at 10:18 Comment(4)
for Spring dev this is the cleanest solution : return new RestTemplate(new (HttpComponentsClientHttpRequestFactory ));Adopted
Thank you @hirosht. Cleanest and simplest way to solve it in a spring-based app.Suribachi
I like to add the because reason to my Gradle dependency to make it obvious why it's needed, given that no imported statements may even be using it: implementation('org.apache.httpcomponents:httpclient') { because('java.net.HttpURLConnection does not support PATCH') }Flite
Adding the httpclient dependency is enough for SpringbootAvron
W
15

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();
Wendelin answered 24/9, 2020 at 14:25 Comment(1)
Exactly, this is the official replacement for the deprecated java.net.HttpURLConnection.Suite
C
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);
    }
}
Cogitative answered 15/11, 2016 at 9:46 Comment(5)
If you're already using reflection, why not just add "PATCH" method by rewriting java.net.HttpURLConnection#methods value once per application lifetime?Delastre
Good point. However, my answer is only for showing how the suggested solution should work, not to show another solutionCogitative
@okutane, could you please provide little hint how can we re-write to methods? because I have seen few post talking about delegates in itElectromagnetic
@Dhamayanthi I've posted separate answer for it.Delastre
could you please share the link?Electromagnetic
E
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.

Elyseelysee answered 25/1, 2016 at 22:51 Comment(1)
Not valid. The line terminator in HTTP is specified as \r\n, not as whatever println() provides.Spannew
E
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.

Esprit answered 9/3, 2017 at 23:52 Comment(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.Phaih
H
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.

Homogeny answered 13/9, 2019 at 17:6 Comment(0)
M
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);
 }
Metamorphic answered 21/9, 2016 at 23:6 Comment(1)
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.Leicestershire
H
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

Holguin answered 22/9, 2016 at 17:41 Comment(1)
Thank's for your answer. But what do you think is better, use direct httpUrlConnection or Jersey Client?Esprit
A
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().

Amino answered 1/12, 2016 at 14:6 Comment(0)
O
0

I got mine with Jersey client. The workaround was:

Client client = ClientBuilder.newClient();
client.property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true);
Okun answered 15/4, 2018 at 9:46 Comment(0)
D
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.

Donoghue answered 17/4, 2018 at 9:23 Comment(0)
M
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

Midwest answered 11/5, 2019 at 10:5 Comment(0)
B
-1

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.

Bivalent answered 16/5, 2017 at 18:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.