Reputation: 2260
I have the following definition for PersonDTO:
public class PersonDTO
{
private String id
private String firstName;
private String lastName;
private String maritalStatus;
}
Here is a sample record :
{
"id": 1,
"firstName": "John",
"lastName": "Doe",
"maritalStatus": "married"
}
Now, John Doe gets divorced. So I need to send a PATCH request to this URL:
http://localhost:8080/people/1
With the following request body:
{
"maritalStatus": "divorced"
}
I cannot figure out how to do it. Here is what I tried so far:
// Create Person
PersonDTO person = new PersonDTO();
person.setMaritalStatus("Divorced");
// Create HttpEntity
final HttpEntity<ObjectNode> requestEntity = new HttpEntity<>(person);
// Create URL (for eg: localhost:8080/people/1)
final URI url = buildUri(id);
ResponseEntity<Void> responseEntity = restTemplate.exchange(url, HttpMethod.PATCH, requestEntity, Void.class);
Here are the problems with the above:
1) As I am setting only MaritalStatus, the other fields would all be null. So if I print out the request, it will look like this:
{
"id": null,
"firstName": "null",
"lastName": "null",
"maritalStatus": "married" // I only need to update this field.
}
Does that mean that I have to a GET before I do a PATCH?
2) I am getting the following stack trace:
08:48:52.717 ERROR c.n.d.t.s.PersonServiceImpl - Unexpected Exception :
org.springframework.web.client.ResourceAccessException: I/O error on PATCH request for "http://localhost:8080/people/1":Invalid HTTP method: PATCH; nested exception is java.net.ProtocolException: Invalid HTTP method: PATCH
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:580) ~[spring-web-4.1.6.RELEASE.jar:4.1.6.RELEASE]
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:545) ~[spring-web-4.1.6.RELEASE.jar:4.1.6.RELEASE]
at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:466) ~[spring-web-4.1.6.RELEASE.jar:4.1.6.RELEASE]
at com.sp.restclientexample..service.PersonServiceImpl.doPatch(PersonServiceImpl.java:75) ~[classes/:na]
at com.sp.restclientexample..service.PatchTitle.itDoPatch(PatchTitle.java:53) [test-classes/:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_20]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_20]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_20]
at java.lang.reflect.Method.invoke(Method.java:483) ~[na:1.8.0_20]
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) [junit-4.12.jar:4.12]
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) [junit-4.12.jar:4.12]
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) [junit-4.12.jar:4.12]
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) [junit-4.12.jar:4.12]
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:73) [spring-test-4.1.6.RELEASE.jar:4.1.6.RELEASE]
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:82) [spring-test-4.1.6.RELEASE.jar:4.1.6.RELEASE]
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:73) [spring-test-4.1.6.RELEASE.jar:4.1.6.RELEASE]
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) [junit-4.12.jar:4.12]
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:224) [spring-test-4.1.6.RELEASE.jar:4.1.6.RELEASE]
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:83) [spring-test-4.1.6.RELEASE.jar:4.1.6.RELEASE]
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) [junit-4.12.jar:4.12]
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) [junit-4.12.jar:4.12]
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) [junit-4.12.jar:4.12]
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) [junit-4.12.jar:4.12]
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) [junit-4.12.jar:4.12]
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) [spring-test-4.1.6.RELEASE.jar:4.1.6.RELEASE]
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:68) [spring-test-4.1.6.RELEASE.jar:4.1.6.RELEASE]
at org.junit.runners.ParentRunner.run(ParentRunner.java:363) [junit-4.12.jar:4.12]
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:163) [spring-test-4.1.6.RELEASE.jar:4.1.6.RELEASE]
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50) [.cp/:na]
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) [.cp/:na]
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459) [.cp/:na]
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675) [.cp/:na]
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382) [.cp/:na]
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192) [.cp/:na]
Caused by: java.net.ProtocolException: Invalid HTTP method: PATCH
at java.net.HttpURLConnection.setRequestMethod(HttpURLConnection.java:440) ~[na:1.8.0_20]
at sun.net.www.protocol.http.HttpURLConnection.setRequestMethod(HttpURLConnection.java:517) ~[na:1.8.0_20]
at org.springframework.http.client.SimpleClientHttpRequestFactory.prepareConnection(SimpleClientHttpRequestFactory.java:209) ~[spring-web-4.1.6.RELEASE.jar:4.1.6.RELEASE]
at org.springframework.http.client.SimpleClientHttpRequestFactory.createRequest(SimpleClientHttpRequestFactory.java:138) ~[spring-web-4.1.6.RELEASE.jar:4.1.6.RELEASE]
at org.springframework.http.client.support.HttpAccessor.createRequest(HttpAccessor.java:76) ~[spring-web-4.1.6.RELEASE.jar:4.1.6.RELEASE]
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:565) ~[spring-web-4.1.6.RELEASE.jar:4.1.6.RELEASE]
... 33 common frames omitted
Appreciate any pointers from folks who have written client applications to consume a Restful webservice using Spring's RestTemplate.
For completeness, let me also state that we use SpringDataRest for our backend restful webservices.
SGB
Upvotes: 70
Views: 110220
Reputation: 5929
2024 version
I was able to fix this without the need for extra libraries with spring-web at version 6.1.5
Simply use the JdkClientHttpRequestFactory
. So something like:
final var restTemplate = new RestTemplate(new JdkClientHttpRequestFactory());
Upvotes: 9
Reputation: 37016
Solution for spring boot 3:
I faced the same issue for my spring boot 3 but neither of solution helped me.
Eventually I realized that this answer is working, but library should be
Implementation("org.apache.httpcomponents.client5:httpclient5:5.2.1")
or even (to allow spring boot choose the version it likes)
Preferred option:
Implementation("org.apache.httpcomponents.client5:httpclient5")
instead of:
implementation("org.apache.httpcomponents:httpclient:4.4.1")
Upvotes: 6
Reputation: 25
On the off chance someone is looking at this in or after 2023
The httpClient library has been moved to httpclient5
implementation 'org.apache.httpcomponents.client5:httpclient5:5.2.1'
Latest version can be found here: https://mvnrepository.com/artifact/org.apache.httpcomponents.client5/httpclient5
Upvotes: 0
Reputation: 5093
WebClient
offers a modern alternative to the RestTemplate
with efficient support for both sync and async, as well as streaming scenarios. You can implement this with WebClient.
Configure WebClient
@Configuration
public class WebConfig {
public WebClient.Builder webClientBuilder() {
return WebClient.builder();
}
}
Then you can call method
//Constructor Injection
public YourClassName(WebClient.Builder webClientBuilder) {
this.webClientBuilder = webClientBuilder;
}
webClientBuilder.build()
.patch()
.uri("http://localhost:8081/api/v1/customers/{id}", id)
.contentType(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Void.class)
.block();
Dependency for pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
Upvotes: 0
Reputation: 110
this will work if verified answer dose works
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
HttpClient httpClient = HttpClientBuilder.create().build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
restTemplate.setRequestFactory(requestFactory);
return restTemplate;
}
Upvotes: 0
Reputation: 2644
For me solved by adding below line:
restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());
Upvotes: 14
Reputation: 113
I have added the below code in the java file. It worked for me.
String url="Your API URL";
RestTemplate restTemplate = new RestTemplate();
HttpClient httpClient = HttpClientBuilder.create().build();
restTemplate.setRequestFactory(new
HttpComponentsClientHttpRequestFactory(httpClient));
HttpHeaders reqHeaders = new HttpHeaders();
reqHeaders.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> requestEntity = new HttpEntity<String>(requestJson, reqHeaders);
ResponseEntity<String> responseEntity=restTemplate.exchange(url, HttpMethod.PATCH,
requestEntity, String.class);
Also, need to add the below dependency in the pom.xml file.
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
Upvotes: 7
Reputation: 31
I created a generic method to do this when there are linked resources involved:
public void patch(M theEntity, Integer entityId, String linkName, URI linkUri) {
ObjectMapper objectMapper = getObjectMapperWithHalModule();
ObjectNode linkedNode = (ObjectNode) objectMapper.valueToTree(theEntity);
linkedNode.put(linkName, linkUri.getPath());
HttpEntity<ObjectNode> requestEntity = new HttpEntity<>(linkedNode);
restTemplate.exchange(uri + "/" + entityId, HttpMethod.PATCH, requestEntity, Void.class);
}
private ObjectMapper getObjectMapperWithHalModule() {
if(objectMapperHal == null) {
objectMapperHal = new ObjectMapper();
objectMapperHal.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapperHal.registerModule(new Jackson2HalModule());
}
return objectMapperHal;
}
Feel free to look at an implementation of this example at my full jal+json implementation
Upvotes: 1
Reputation: 1556
I solved this problem just adding a new HttpRequestFactory to my restTemplate instance. Like this
RestTemplate restTemplate = new RestTemplate();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setConnectTimeout(TIMEOUT);
requestFactory.setReadTimeout(TIMEOUT);
restTemplate.setRequestFactory(requestFactory);
For TestRestTemplate, add
@Autowired
private TestRestTemplate restTemplate;
@Before
public void setup() {
restTemplate.getRestTemplate().setRequestFactory(new HttpComponentsClientHttpRequestFactory());
}
PS: You will need add httpClient component in your project
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.4.1</version>
</dependency>
Upvotes: 144
Reputation: 2452
If you have an older spring version than 3.1.0, then you don't have the PATCH method in the HttpMethods. You can still use HttpClient from apache. Here is a short example on how I did it:
try {
//This is just to avoid ssl hostname verification and to trust all, you can use simple Http client also
CloseableHttpClient httpClient = HttpClientBuilder.create().setSSLContext(new SSLContextBuilder().loadTrustMaterial(null, TrustAllStrategy.INSTANCE).build())
.setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE).build();
HttpPatch request = new HttpPatch(REST_SERVICE_URL);
StringEntity params = new StringEntity(JSON.toJSONString(payload), ContentType.APPLICATION_JSON);
request.setEntity(params);
request.addHeader(org.apache.http.HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
request.addHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
//You can use other authorization method, like user credentials
request.addHeader(HttpHeaders.AUTHORIZATION, OAuth2AccessToken.BEARER_TYPE + " " + accessToken);
HttpResponse response = httpClient.execute(request);
String statusCode = response.getStatusLine().getStatusCode();
} catch (Exception ex) {
// handle exception here
}
The equivalent of this, with RestTemplate would be:
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.add("Authorization", OAuth2AccessToken.BEARER_TYPE + " " + accessToken);
final HttpEntity<String> entity = new HttpEntity<String>(JSON.toJSONString(payload), headers);
RestTemplate restTemplate = new RestTemplate();
try {
ResponseEntity<String> response = restTemplate.exchange(REST_SERVICE_URL, HttpMethod.PATCH, entity, String.class);
String statusCode = response.getStatusCode();
} catch (HttpClientErrorException e) {
// handle exception here
}
Also, make sure the payload only contains the values you need to change, and make sure you are sending the request to the right URL. (this can be in some cases something ending like /api/guest/{id} )
Upvotes: 0
Reputation: 651
For cases where RestTemplate
is built from a RestTemplateBuilder
, constructor for the custom RestClient can be written as,
public PersonRestClient(RestTemplateBuilder restTemplateBuilder) {
this.restTemplate = restTemplateBuilder.requestFactory(new HttpComponentsClientHttpRequestFactory()).build();
}
Also, the org.apache.httpcomponents.httpclient
dependency needs to added to pom.
Upvotes: 10