question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Doc: Can't issue PATCH request using RestTemplate with SimpleClientHttpRequestFactory [SPR-15052]

See original GitHub issue

Behrang Saeedzadeh opened SPR-15052 and commented

This test case fails with at least OpenJDK 1.8.0_101 under Ubuntu (4.8.0-30-generic):

    @Test
    @DirtiesContext
    public void testPatchMethod() {
        final HttpEntity<Object> request = new HttpEntity<>("<echo>Hello</echo>");

        restTemplate.exchange("/patchy/echo", PATCH, request, String.class);
    }

with the following exception:

org.springframework.web.client.ResourceAccessException: I/O error on PATCH request for "http://localhost:44661/persons/1": Invalid HTTP method: PATCH; nested exception is java.net.ProtocolException: Invalid HTTP method: PATCH

This is due to HttpURLConnection only allowing the following HTTP methods:

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

PayPal guys have implemented a workaround for their own API here:

	 /**
     * Workaround for a bug in {@code HttpURLConnection.setRequestMethod(String)}
     * The implementation of Sun/Oracle is throwing a {@code ProtocolException}
     * when the method is other than the HTTP/1.1 default methods. So to use {@code PATCH}
     * and others, we must apply this workaround.
     *
     * See issue http://java.net/jira/browse/JERSEY-639
     */
    private static void setRequestMethodViaJreBugWorkaround(final HttpURLConnection httpURLConnection, final String method) {
        try {
            httpURLConnection.setRequestMethod(method); // Check whether we are running on a buggy JRE
        } catch (final ProtocolException pe) {
            try {
                final Class<?> httpURLConnectionClass = httpURLConnection.getClass();
				AccessController
						.doPrivileged(new PrivilegedExceptionAction<Object>() {
							public Object run() throws NoSuchFieldException,
									IllegalAccessException {
								try {
									httpURLConnection.setRequestMethod(method);
									// Check whether we are running on a buggy
									// JRE
								} catch (final ProtocolException pe) {
									Class<?> connectionClass = httpURLConnection
											.getClass();
									Field delegateField = null;
									try {
										delegateField = connectionClass
												.getDeclaredField("delegate");
										delegateField.setAccessible(true);
										HttpURLConnection delegateConnection = (HttpURLConnection) delegateField
												.get(httpURLConnection);
										setRequestMethodViaJreBugWorkaround(
												delegateConnection, method);
									} catch (NoSuchFieldException e) {
										// Ignore for now, keep going
									} catch (IllegalArgumentException e) {
										throw new RuntimeException(e);
									} catch (IllegalAccessException e) {
										throw new RuntimeException(e);
									}
									try {
										Field methodField;
										while (connectionClass != null) {
											try {
												methodField = connectionClass
														.getDeclaredField("method");
											} catch (NoSuchFieldException e) {
												connectionClass = connectionClass
														.getSuperclass();
												continue;
											}
											methodField.setAccessible(true);
											methodField.set(httpURLConnection,
													method);
											break;
										}
									} catch (final Exception e) {
										throw new RuntimeException(e);
									}
								}
								return null;
							}
						});
            } catch (final PrivilegedActionException e) {
                final Throwable cause = e.getCause();
                if (cause instanceof RuntimeException) {
                    throw (RuntimeException) cause;
                } else {
                    throw new RuntimeException(cause);
                }
            }
        }
}

Another alternative is to use Apache Http-Components Client 4.2+ instead.

I have attached a test case reproducing the issue (see org.behrang.howto.bugreport.PatchyControllerTest#testPatchMethod).


Affects: 4.3.5

Attachments:

Issue Links:

  • #19910 Enhance RestTemplate to support PATCH out of the box

Referenced from: commits https://github.com/spring-projects/spring-framework/commit/048098119efedbea0461539623188ed2af785bb7, https://github.com/spring-projects/spring-framework/commit/20aaa8841c56ed0ddd55e6f7e8f807acf6699f57

0 votes, 5 watchers

Issue Analytics

  • State:closed
  • Created 7 years ago
  • Reactions:1
  • Comments:7

github_iconTop GitHub Comments

3reactions
spring-projects-issuescommented, Jan 11, 2019

Kazuki Shimizu commented

Is this specific to TestRestTemplate, as your title suggests?

No. I tried to send PATCH request using RestTemplate with SimpleClientHttpRequestFactory. As result, it is same result as follow: I think this behavior is known limitation.

...
Caused by: java.net.ProtocolException: Invalid HTTP method: PATCH
	at java.net.HttpURLConnection.setRequestMethod(HttpURLConnection.java:440)
	at sun.net.www.protocol.http.HttpURLConnection.setRequestMethod(HttpURLConnection.java:519)
	at org.springframework.http.client.SimpleClientHttpRequestFactory.prepareConnection(SimpleClientHttpRequestFactory.java:216)
...

If you want to use the PATCH method, you can use the RestTemplate with some OSS libraries as follow:

  • Apache HttpComponents HttpClient
  • OkHttp 3
  • OkHttp 2
  • Netty 4

I tried to send a PATCH request using HttpComponentsClientHttpRequestFactory, Netty4ClientHttpRequestFactory, OkHttp3ClientHttpRequestFactory and OkHttpClientHttpRequestFactory. These work fine.

Thanks.

2reactions
spring-projects-issuescommented, Jan 11, 2019

Kazuki Shimizu commented

As one alternative… If you use the HiddenHttpMethodFilter on your application(or target web application), you can access using POST method with _method=patch parameter as follow:

RestTemplate restTemplate = new RestTemplate();
restTemplate.postForObject("http://localhost:8080/persons/1?_method=patch", requestBody, String.class);
Read more comments on GitHub >

github_iconTop Results From Across the Web

json - RestTemplate PATCH request - Stack Overflow
I solved this problem just adding a new HttpRequestFactory to my restTemplate instance. Like this RestTemplate restTemplate = new ...
Read more >
Spring Boot - RestTemplate PATCH request fix - Rake's Artifacts
Http PATCH using RestTemplate in Spring Boot. ... In Spring Boot, you make a simple http request as below: 1. Define RestTemplate bean....
Read more >
RestTemplate (Spring Framework 6.0.2 API)
Synchronous client to perform HTTP requests, exposing a simple, template method API ... Create a new instance of the RestTemplate using default settings....
Read more >
Complete Guide to Spring RestTemplate - Reflectoring
In this tutorial, we will understand how to use RestTemplate for invoking REST APIs of different shapes. Example Code. This article is ...
Read more >
Rest Template : I/O error on PATCH request for “http ...
Rest Template : I/O error on PATCH request for “http://localhost:8080/api/xxx".. พระเอกของเราคือ HttpComponentsClientHttpRequestFactory นั่นเองครับ.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found