RestTemplate is Spring’s synchronous HTTP client. A call such as getForEntity() sends a request and waits for the response before the calling thread continues. It is useful when maintaining an application that already uses it. For new synchronous clients, Spring Framework 7 recommends RestClient, which offers a fluent API; RestTemplate is deprecated in that framework version but remains available.
This tutorial uses Spring Boot 4.1.2 and one appointment API to show what the common RestTemplate methods send, return, and do when a request fails.
1. Add the Dependencies and Create a Client
The example includes a small HTTP server so that every client call can be run locally. The REST client starter provides Boot’s RestTemplateBuilder, while the MVC starter provides the appointment endpoint. The test starter supports the runnable integration tests linked at the end of this article.
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.1.2</version>
</parent>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-restclient</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
Boot supplies a RestTemplateBuilder that we inject into AppointmentClient. Calling build() creates the client once for this service. Subsequent service methods use that client for their requests.
@Service
public class AppointmentClient {
private final RestTemplate restTemplate;
public AppointmentClient(RestTemplateBuilder builder) {
this.restTemplate = builder.build();
}
}
The server accepts a title in CreateAppointment and returns an Appointment containing its assigned ID. These records are nested in AppointmentController and define the JSON shape used throughout the example. The client source uses the fully qualified nested names; the shorter names in the snippets below assume imports for both records.
public record CreateAppointment(String title) {}
public record Appointment(long id, String title) {}
The local server exposes POST /appointments to create a record and GET, PUT, and DELETE /appointments/{id} to work with it. It stores records in memory, so restarting the application clears them. The client code works the same way with a persistent service that has the same HTTP contract.
2. Create an Appointment with postForEntity()
Start by creating a record, because the server will then give us a real ID for the later requests. In the following snippets, client is an injected AppointmentClient and baseUrl is the server address, such as http://localhost:8080. postForEntity() serializes the request record as JSON, makes a POST request, and returns a ResponseEntity. That return type contains the status, headers, and deserialized response body.
public ResponseEntity<Appointment> create(String baseUrl, String title) {
return restTemplate.postForEntity(
baseUrl + "/appointments",
new CreateAppointment(title),
Appointment.class);
}
ResponseEntity<Appointment> created =
client.create(baseUrl, "Dental checkup");
long id = created.getBody().id();
The API returns 201 Created, an Appointment body with the generated ID, and a Location header such as /appointments/101. Use postForEntity() when the caller needs the status or headers as well as the created resource. The related JSON POST request guide covers additional request forms.
3. Read the Appointment with getForEntity() or getForObject()
Now the ID returned by POST can be substituted into the URL template. getForEntity() returns the full HTTP response, which is useful when status or headers matter.
public ResponseEntity<Appointment> find(String baseUrl, long id) {
return restTemplate.getForEntity(
baseUrl + "/appointments/{id}",
Appointment.class, id);
}
ResponseEntity<Appointment> found = client.find(baseUrl, id);
System.out.println(found.getStatusCode());
System.out.println(found.getBody().title());
For the created appointment, those lines print 200 OK and Dental checkup. The final id argument replaces {id}; it is not appended to the URL a second time.
When only the response body is needed, getForObject() returns the deserialized Appointment directly:
public Appointment body(String baseUrl, long id) {
return restTemplate.getForObject(
baseUrl + "/appointments/{id}",
Appointment.class, id);
}
Appointment appointment = client.body(baseUrl, id);
System.out.println(appointment.title()); // Dental checkup
Neither method turns a 404 response into a normal Appointment value. With the default error handler, a missing appointment raises HttpClientErrorException.NotFound. The GET request guide has more response mapping examples.
4. Replace and Delete an Appointment
The API’s PUT endpoint replaces the appointment title and returns 204 No Content. RestTemplate.put() returns void, so the client does not receive a new Appointment from that call. Read the resource again when the application needs the server’s current representation.
public void replace(String baseUrl, long id, String title) {
restTemplate.put(baseUrl + "/appointments/{id}",
new CreateAppointment(title), id);
}
client.replace(baseUrl, id, "Annual checkup");
System.out.println(client.body(baseUrl, id).title()); // Annual checkup
The subsequent GET returns the new title. That observation verifies the server updated its state; the lack of a return value from put() alone does not show what the server stored.
The DELETE endpoint also returns 204 No Content, and RestTemplate.delete() returns void. A subsequent GET makes the removal observable:
public void remove(String baseUrl, long id) {
restTemplate.delete(baseUrl + "/appointments/{id}", id);
}
client.remove(baseUrl, id);
// A later client.find(baseUrl, id) throws HttpClientErrorException.NotFound.
The convenience put() and delete() methods do not expose the successful response status. Use exchange() when a caller must inspect that status or response headers.
5. Send Headers with exchange()
Suppose a caller adds an X-Request-Id header so it can correlate a request with a server response. exchange() accepts the HTTP method and an HttpEntity carrying request headers. It returns a ResponseEntity containing the response headers and body.
public ResponseEntity<Appointment> findWithRequestId(
String baseUrl, long id, String requestId) {
HttpHeaders headers = new HttpHeaders();
headers.set("X-Request-Id", requestId);
return restTemplate.exchange(
baseUrl + "/appointments/{id}",
HttpMethod.GET,
new HttpEntity<>(headers),
Appointment.class,
id);
}
ResponseEntity<Appointment> response =
client.findWithRequestId(baseUrl, id, "trace-456");
System.out.println(response.getHeaders().getFirst("X-Request-Id"));
The example server echoes that header, so the last line prints trace-456. A different server may accept the request ID without echoing it; the returned header here is part of this example API’s contract. For common headers on every request, an interceptor is more suitable than repeating exchange() at each call site.
6. Handle HTTP Errors
The example server rejects a blank title with 400 Bad Request and an unknown ID with 404 Not Found. By default, RestTemplate raises an exception for these 4xx responses instead of returning the ResponseEntity expected by a successful call. Catch the specific condition that the application can handle, and read its status for diagnostics.
try {
client.find(baseUrl, 999_999);
} catch (HttpClientErrorException.NotFound ex) {
System.out.println(ex.getStatusCode()); // 404 NOT_FOUND
}
A missing resource can be normal in some workflows; the caller may then show a not-found message. A 400 response to a create request usually means the caller sent invalid data and should correct it. Catching every RestClientException and returning null would hide the difference between these cases and transport failures.
The integration tests make both outcomes explicit:
assertThrows(HttpClientErrorException.BadRequest.class,
() -> client.create(baseUrl, " "));
assertThrows(HttpClientErrorException.NotFound.class,
() -> client.find(baseUrl, 999_999));
7. Choose the Method and Client
Choose a RestTemplate method from the response information the caller actually needs. These are the methods used in this tutorial:
| Need | Method | Successful return |
|---|---|---|
Create and inspect status or Location |
postForEntity() | ResponseEntity |
| Read body, status, and headers | getForEntity() | ResponseEntity |
| Read only the body | getForObject() | T |
| Replace or remove without inspecting response | put() or delete() | void |
| Control method and request headers | exchange() | ResponseEntity |
Spring Framework 7 documents RestTemplate as deprecated in favor of RestClient. Existing RestTemplate code still runs, and there is no need to rewrite a working client simply to follow this tutorial. For a new synchronous client, start with RestClient. Use WebClient when the application specifically needs its reactive, nonblocking model. If requests need credentials or connection limits, configure those concerns in the client rather than copying options into each operation; the Basic authentication guide and timeout guide cover them separately.
8. FAQs
8.1. Does getForObject() return null for a 404 response?
No. The default RestTemplate error handler treats 404 as an error and raises HttpClientErrorException.NotFound. A successful response without a body is a different case; the body returned by getForObject() can then be null.
8.2. How do I inspect the status returned by PUT or DELETE?
Use exchange() with HttpMethod.PUT or HttpMethod.DELETE. The convenience put() and delete() methods return void, so they cannot expose the successful status to the caller.
8.3. Is RestTemplate still usable in Spring Boot 4?
Yes. Spring Boot 4.1.2 can build and use a RestTemplate, as the runnable example demonstrates. Spring Framework 7 deprecates the API in favor of RestClient for new synchronous HTTP clients.
9. Conclusion
For an existing RestTemplate client, select the method by the data the caller needs from the HTTP response. Inspect ResponseEntity when status or headers matter, and handle 4xx responses as errors. For new synchronous client code, use Spring’s RestClient API.
Happy Learning!
The complete Spring Boot 4.1.2 example and tests are on GitHub.
Hi,
RestTemplate usage is explained only for endpoint URI with the default port (8080). Will there be any changes if I want to use RestTemplate for a URI with a specific port (Ex: 4433)?. Thanks in advance.
Regards,
Vinni
Please configure different UriTemplateHandler instances and use them.
Hi Lokesh,
Your tutorials are really very helpful……Can you please let me know how can we use OAuth 2.0 in Spring Security.
I will write a detailed tutorial on oauth2 very soon.
Hi, Does Spring RestTemplate support DELETE with Body?
No, entity body is not supported for DELETE requests.
How we can add OAuth token in Header while calling GET for
restTemplate.getForObject(uri, EmployeeListVO.class);
Please reply ASAP
Look around this: OAuth2RestTemplate and https://stackoverflow.com/questions/27864295/how-to-use-oauth2resttemplate
Hi Lokesh, I’m looking forward to implement the Spring OAuth2 example using OAuth2RestTemplate. I goggled lot to find the solution, but did not find any working example. Finally I got the URL (which is very nice)
https://stackoverflow.com/questions/23486186/access-tokens-using-2-legged-oauth-2-0-and-apache-oauthclient. I’m trying to implement the solution-2, but not sure how we can integrate with Facebook + Spring OAuth2 using rest template. Could you please help me ASAP ?
Thanks,
Neha Sharma
Hi Lokesh – Would you like to give any suugestions on above question?
Thanks,
Neha Sharma
As of now, I am also not aware of how it should be done. I will try.