RestTemplate PATCH request
Asked Answered
N

11

67

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

Nunciata answered 4/4, 2015 at 14:9 Comment(2)
which version of spring is this?Gragg
here is the WHY you have to include HttpComponentsClientHttpRequestFactory : The standard JDK HTTP library does not support HTTP PATCH. You need to use the Apache HttpComponents or OkHttp request factory doc linkChamplain
S
143

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>
Saprophagous answered 22/4, 2015 at 16:20 Comment(9)
To save some lines you can do the RestTemplate initialisation in one line new RestTemplate(new HttpComponentsClientHttpRequestFactory())Voiceless
@Voiceless Do you know why this is required?Anarchism
I think with "this" you mean the fact that you have to set the HttpComponentsClientHttpRequestFactory. This commment (jira.spring.io/browse/…) answers your question.Voiceless
Still a problem in 2021, WTF. But thanks for your solution :)Doyen
This answer could be improved significantly by including context around why this is required and/or why it is a "fix".Twoway
Using Spring Boot 3, I had to use this dependency instead org.apache.httpcomponents.client5:httpclient5:5.2.1Pilau
Solution for spring boot 3: https://mcmap.net/q/293489/-resttemplate-patch-requestSanalda
@Taoufik Mohdit why haven't you created ans answer=). You would save 4 hours of my lifeSanalda
@Sanalda Sorry this took you so long :-). At the time, I thought the dependency adjustment didn't warrant a separate answerPilau
A
14

For me solved by adding below line:

restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());
Albanian answered 1/9, 2021 at 12:9 Comment(1)
Worked for me as well. Can anyone please explain what's going on here?Equerry
M
10

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.

Mccahill answered 7/3, 2018 at 13:1 Comment(4)
for springboot 2 its .requestFactory(HttpComponentsClientHttpRequestFactory.class)Polypeptide
@Polypeptide Thanks for pointing out Spring boot 2 way of configuring it.Nadya
If you use spring-boot v2.3, then its configured automatically, if you add the dependency to your project.Republicanism
Do you still need to add the dependency if it comes transitively? I'm asking this because I do have it in my classpath, but not declared in my pom.Lakeishalakeland
S
7

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>
Salute answered 24/11, 2020 at 6:2 Comment(0)
S
5

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")
Sanalda answered 26/7, 2023 at 16:20 Comment(0)
P
1

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

Phototransistor answered 16/9, 2019 at 11:27 Comment(0)
H
1

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>
Herbivorous answered 20/2, 2023 at 19:46 Comment(0)
R
1

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());
Roosevelt answered 16/7 at 11:10 Comment(0)
C
0

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} )

Concealment answered 29/11, 2018 at 18:19 Comment(0)
T
0

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;
}
Teferi answered 24/1, 2023 at 15:42 Comment(0)
Q
0

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

Quintie answered 24/7, 2023 at 16:14 Comment(1)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Punch

© 2022 - 2024 — McMap. All rights reserved.