SGB
SGB

Reputation: 2260

RestTemplate PATCH request

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

Answers (11)

Alfergon
Alfergon

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

gstackoverflow
gstackoverflow

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

Shane Lee
Shane Lee

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

Priyantha
Priyantha

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

kiran kumar
kiran kumar

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

Amir Keshavarz
Amir Keshavarz

Reputation: 2644

For me solved by adding below line:

restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());

Upvotes: 14

Vaibhav
Vaibhav

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

Gus
Gus

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

Tulio Gomez Rodrigues
Tulio Gomez Rodrigues

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

Dezso Gabos
Dezso Gabos

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

sompnd
sompnd

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

Related Questions