I want to execute a PATCH request supported by our server for testing using Jersey client. My code is as below, but I get com.sun.jersey.api.client.ClientHandlerException: java.net.ProtocolException: HTTP method PATCH doesn't support output
exception. Can someone let me know whats wrong with the code below?
String complete_url = "http://localhost:8080/api/request";
String request = "[{\"op\":\"add\", \"path\":\"/name\", \"value\":\"Hello\"}]";
DefaultClientConfig config = new DefaultClientConfig();
config.getProperties().put(URLConnectionClientHandler.PROPERTY_HTTP_URL_CONNECTION_SET_METHOD_WORKAROUND, true);
Client client = Client.create(config);
WebResource resource = client.resource(complete_url);
ClientResponse response = resource.header("Authorization", "Basic xyzabCDef")
.type(new MediaType("application", "json-patch+json"))
.method("PATCH", ClientResponse.class, request);
Here is the full exception,
com.sun.jersey.api.client.ClientHandlerException: java.net.ProtocolException: HTTP method PATCH doesn't support output
at com.sun.jersey.client.urlconnection.URLConnectionClientHandler.handle(URLConnectionClientHandler.java:155)
at com.sun.jersey.api.client.Client.handle(Client.java:652)
at com.sun.jersey.api.client.WebResource.handle(WebResource.java:682)
at com.sun.jersey.api.client.WebResource.access$200(WebResource.java:74)
at com.sun.jersey.api.client.WebResource$Builder.method(WebResource.java:634)
at com.acceptance.common.PatchTest.patch(PatchTest.java:42)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Caused by: java.net.ProtocolException: HTTP method PATCH doesn't support output
at sun.net.www.protocol.http.HttpURLConnection.getOutputStream(HttpURLConnection.java:1021)
at com.sun.jersey.client.urlconnection.URLConnectionClientHandler$1$1.getOutputStream(URLConnectionClientHandler.java:238)
at com.sun.jersey.api.client.CommittingOutputStream.commitStream(CommittingOutputStream.java:117)
at com.sun.jersey.api.client.CommittingOutputStream.write(CommittingOutputStream.java:89)
at sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:202)
at sun.nio.cs.StreamEncoder.implFlushBuffer(StreamEncoder.java:272)
at sun.nio.cs.StreamEncoder.implFlush(StreamEncoder.java:276)
at sun.nio.cs.StreamEncoder.flush(StreamEncoder.java:122)
at java.io.OutputStreamWriter.flush(OutputStreamWriter.java:212)
at java.io.BufferedWriter.flush(BufferedWriter.java:236)
at com.sun.jersey.core.util.ReaderWriter.writeToAsString(ReaderWriter.java:191)
at com.sun.jersey.core.provider.AbstractMessageReaderWriterProvider.writeToAsString(AbstractMessageReaderWriterProvider.java:128)
at com.sun.jersey.core.impl.provider.entity.StringProvider.writeTo(StringProvider.java:88)
at com.sun.jersey.core.impl.provider.entity.StringProvider.writeTo(StringProvider.java:58)
at com.sun.jersey.api.client.RequestWriter.writeRequestEntity(RequestWriter.java:300)
at com.sun.jersey.client.urlconnection.URLConnectionClientHandler._invoke(URLConnectionClientHandler.java:217)
at com.sun.jersey.client.urlconnection.URLConnectionClientHandler.handle(URLConnectionClientHandler.java:153)
Jersey ClientBuilder. JAX-RS Client API is a designed to allow fluent programming model. To create jersey client follow these steps – Use ClientBuilder. newClient() static method.
The PATCH request method is used to modify a resource on the server partially. To send a PATCH request to the server, you need to use the HTTP PATCH method and include the request data in the body of the HTTP message. The Content-Type request header must indicate the data type in the body.
Jersey is a REST-client, featuring full JAX-RS implementation, neat fluent API and a powerfull filter stack. Apache Http Client is a HTTP-client, perfect in managing low-level details like timeouts, complex proxy routes and connection polling. They act on a different levels of your protocol stack.
FYI - just in case anyone runs into this in Jersey 2, see the HttpUrlConnectorProvider documentation
and use the SET_METHOD_WORKAROUND property as follows:
Client jerseyClient = ClientBuilder.newClient()
.property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true)
... etc ...
Took me forever to find this - figured I could help short circuit the learning curve for others.
If you're using HttpsUrlConnection
(note the 's') - then setting the HttpUrlConnectorProvider.SET_METHOD_WORKAROUND
won't work. Keep reading for a detailed solution.
In my case, setting HttpUrlConnectorProvider.SET_METHOD_WORKAROUND
property caused a NoSuchFieldException
since my HttpUrlConnection
instance was actually of type: sun.net.www.protocol.https.HttpsURLConnectionImpl
and it's super: javax.net.ssl.HttpsURLConnection
(which inherits from HttpUrlConnection
).
So when Jackson code try to get the method field from my connection instance super (instance of javax.net.ssl.HttpsURLConnection
) 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 PROPFIND}
* 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>() {
@Override
public Object run() throws NoSuchFieldException, IllegalAccessException {
final Field methodField = httpURLConnectionClass.getSuperclass().getDeclaredField("method");
methodField.setAccessible(true);
methodField.set(httpURLConnection, method);
return null;
}
});
} catch (final PrivilegedActionException e) {
final Throwable cause = e.getCause();
if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
} else {
throw new RuntimeException(cause);
}
}
}
}
We get a NoSuchFieldException
stating that a field named method
doesn't exist (since getDeclaredFields() brings all the fields, regardless of their accessibility but only for the current class, not any base classes that the current class might be inheriting from).
So I looked into Java's HttpUrlConnection code and saw that the allowed methods are specified by an private static String[]:
/* Adding PATCH to the valid HTTP methods */
private static final String[] methods = {
"GET", "POST", "HEAD", "OPTIONS", "PUT", "DELETE", "TRACE"
};
The solution was to change this methods array using reflection:
try {
Field methodsField = HttpURLConnection.class.getDeclaredField("methods");
methodsField.setAccessible(true);
// get the methods field modifiers
Field modifiersField = Field.class.getDeclaredField("modifiers");
// bypass the "private" modifier
modifiersField.setAccessible(true);
// remove the "final" modifier
modifiersField.setInt(methodsField, methodsField.getModifiers() & ~Modifier.FINAL);
/* valid HTTP methods */
String[] methods = {
"GET", "POST", "HEAD", "OPTIONS", "PUT", "DELETE", "TRACE", "PATCH"
};
// set the new methods - including patch
methodsField.set(null, methods);
} catch (SecurityException | IllegalArgumentException | IllegalAccessException | NoSuchFieldException e) {
e.printStackTrace();
}
Since methods field is static, changing its value works for any concrete instance that is extending HttpUrlConnection
including HttpsUrlConnection
.
Side note: I would prefer Java to add the PATCH method to the JDK or from Jackson to perform the field look up through the entire hierarchy in their workaround.
Anyway, I hope this solution will save you some time.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With