HTTP Methods in Spring RESTful Services
Representational state transfer (REST) is a software architectural style that defines a set of constraints to be used for creating Web services. Web services that conform to the REST architectural style, called RESTful Web services (or simply RESTful services).
RESTful services enable us to develop any kind of application involving all possible CRUD (create, retrieve, update, delete) operations. We should utilize the different HTTP verbs which correspond to CRUD operations. The primary or most-commonly-used HTTP methods are GET, POST, PUT, PATCH, and DELETE. In performing these operations in RESTful services, there are guidelines or principles that suggest using a specific HTTP method on a specific type of call made to the server.
Below is a table summarizing primary HTTP methods and it's recommendations for RESTful services:
HTTP Verb | CRUD | Sample | Description | Result |
---|---|---|---|---|
GET | Read | /books | list all books. Pagination, sorting and filtering is used for big lists. | 200 (OK) |
/books/{id} | get specific book by id | 200 (OK) 404 (Not Found) - id not found or invalid | ||
POST | Create | /books | create new book | 201 (Created) - return a Location header with a link to the newly-created resource /books/{id}. 409 (Conflict) - indicated that resource already exists |
/books/id | Return 405 (Method Not Allowed), avoid using POST on single resource | |||
PUT | Update/Replace | /books | Return 405 (Method Not Allowed), unless you want to replace every resource in the entire collection of resource - use with caution. | |
/books/{id} | Update a book | 200 (OK) or 204 (No Content) - indicate successful completion of the request 201 (Created) - if new resource is created and creating new resource is allowed 404 (Not Found) - if id not found or invalid and creating new resource is not allowed. | ||
PATCH | Partial Update | /books | Return 405 (Method Not Allowed), unless you want to modify the collection itself.. | |
/books/{id} | Partial update a book | 200 (OK) or 204 (No Content) - indicate successful completion of the request 404 (Not Found) - if id not found or invalid | ||
DELETE | Delete | /books | Return 405 (Method Not Allowed), unless you want to delete the whole collection - use with caution | |
/books/{id} | Delete a book | 200 (OK) or 204 (No Content) or 202 (Accepted) 404 (Not Found) - if id not found or invalid |
GET
The GET method used to retrieve information from the REST API and not to modify it in any way. GET should be a safe and idempotent method. Safe (and idempotent) methods can be cached.
- If the resource is found on the server then it must return HTTP response code 200 (OK) – along with response body (can be XML, JSON or (possible) RAW)
- In case resource is NOT found on server then it must return 404 (Not Found).
- In case that GET request itself is not correctly formed then server will return 400 (Bad Request)..
Let's check following example
@GetMapping("/rest/v1/books")
public ResponseEntity<List<Book>> findAllBooks() {
List<Book> books = bookService.findAll();
return ResponseEntity.ok(books); // return 200, with json body
}
@GetMapping("/rest/v1/books/{bookId}")
public ResponseEntity<Book> findBookById(@PathVariable long bookId) {
try {
Book book = bookService.findById(bookId);
return ResponseEntity.ok(book); // return 200, with json body
} catch (ResourceNotFoundException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null); // return 404, with null body
}
}
All REST method examples in this article are implemented with Spring Framework. ResponseEntity is available since Spring 3.0.2, which help to set response with headers, body and status code (it's an extension of HttpEntity that adds a HttpStatus status code). The examples will only focusing on what to return, not the complete function in real-life "enterprise" REST APIs. So, we will not drill down unto other area like database access, services, etc.
Method findAllBooks() will return resource collection (books). Method findBookById(bookId) return a specified single resource (book based on bookId). Both methods are annotated with @GetMapping which map HTTP GET requests onto specific handler methods. ResourceNotFoundException is one defined exception to handle if item requested (in REST term: resource) is not found. If resource not found, return 404 (HttpStatus.NOT_FOUND) with response body set to null for consistency as ResourceEntity<Book>.
Sample curl for GET collection:
curl -X GET "http://localhost:8080/spring-rest-examples/rest/v1/books" -H "accept: */*"
Response body - 200 (OK)
[ { "id": 1, "oclc": "1827184", "isbn10": null, "isbn13": null, "title": "The Hobbit" }, { "id": 2, "oclc": "1487587", "isbn10": null, "isbn13": null, "title": "The Lord of The Rings" }, { "id": 3, "oclc": "3318634", "isbn10": "0048231398", "isbn13": null, "title": "The Silmarillion" }, { "id": 4, "oclc": "7207376", "isbn10": null, "isbn13": null, "title": "The Lion, the Witch, and The Wardrobe" }, { "id": 5, "oclc": "2812448", "isbn10": null, "isbn13": "9780006716792", "title": "Prince Caspian" }, { "id": 6, "oclc": "2805288", "isbn10": null, "isbn13": "9780006716808", "title": "The Voyage of the Dawn Treader" }, { "id": 7, "oclc": "1304139", "isbn10": null, "isbn13": "9780006716815", "title": "The Silver Chair" }, { "id": 8, "oclc": "2801054", "isbn10": null, "isbn13": "9780006716785", "title": "The Horse and His Boy" }, { "id": 9, "oclc": "2497740", "isbn10": null, "isbn13": "9780006716839", "title": "The Magician's Nephew" }, { "id": 10, "oclc": "752428300", "isbn10": null, "isbn13": "9780006716822", "title": "The Last Battle" } ]
Sample curl for GET single resource:
curl -X GET "http://localhost:8080/spring-rest-examples/rest/v1/books/5" -H "accept: */*"
Response body - 200 (OK)
{ "id": 5, "oclc": null, "isbn10": null, "isbn13": "9780006716792", "title": "Prince Caspian" }
POST
POST should only be used to create a new resource. In some cases, POST method is used to update entity but we have more proper HTTP Method to do this (PUT). POST API call is not idempotent nor safe and should be used with care. Invoking two identical POST requests will result in creation of two different resources with same information but different resource id
- If a resource is created on the server, the response ideally should be HTTP response code 201 (Created), a Location header (URI) and contain an entity which describes the status of the request and refer to the new resource
- In case of successful creation but the resource can't be identified by a URI, then use either 200 (OK) or 204 (No Content).
- If the resource (after certain validation) is deemed as already exists, then return 409 (Conflict)
- Is suggested to use POST method for collection and to return 405 (Method Not Allowed) on single resource
Result for this method is not cacheable. We need to include Cache-Control or Expires header if we want to use cache for POST method. In relation with GET with cached result, POST (and PUT, PATCH, and DELETE) should invalidate the cache.
@PostMapping("/rest/v1/books")
public ResponseEntity<Book> addBook(@RequestBody Book book) throws URISyntaxException {
try {
Book newBook = bookService.save(book);
return ResponseEntity.created(new URI("/rest/v1/books/" + newBook.getId()))
.body(book);
} catch (ResourceAlreadyExistsException ex) {
// log exception first, then return Conflict (409)
logger.error(ex.getMessage());
return ResponseEntity.status(HttpStatus.CONFLICT).build();
} catch (BadResourceException ex) {
// log exception first, then return Bad Request (400)
logger.error(ex.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
}
}
Method addBook(...) is annotated with @PostMapping, mapping HTTP POST requests onto specific handler methods. After successful creation of a resource, HTTP response code 201 (Created) is returned with Book information in the response body (which include the new id generated for this resource), with 'Location' set in the header.
Sample curl
curl -X POST "http://localhost:8080/spring-rest-examples/rest/v1/books" -H "accept: */*" -H "Content-Type: application/json" -d "{ \"isbn10\": null, \"isbn13\": null, \"oclc\": \"23033258\", \"title\": \"Mere Christianity\"}"
Response body - 201 (Created)
{ "id": 11, "oclc": "23033258", "isbn10": null, "isbn13": null, "title": "Mere Christianity" }
Response headers
cache-control: no-cache, no-store, max-age=0, must-revalidate content-type: application/json;charset=UTF-8 date: Thu, 12 Sep 2019 15:29:32 GMT expires: 0 location: /rest/v1/books/11 pragma: no-cache transfer-encoding: chunked x-content-type-options: nosniff x-frame-options: DENY x-xss-protection: 1; mode=block
PUT
The PUT method is used to update existing resource. PUT method is idempotent, client can send the same request multiple time, and it will have same result as sending once.
- In case of successful update then use HTTP response code 200 (OK) or 204 (No Content). 204 (No Content) normally is use if the action has been performed but the response does not include an entity (applied too for PATCH and DELETE). Personally, I choose 200 (OK) indicating a successful update.
- In case that the resource doesn't exist, then either create a new resource or reject the PUT request by return 404 (Not Found). Personally I choose to return 404 (Not Found) since more proper HTTP Method POST is used to create new resource - try to make it consistent.
- If a new resource created by PUT request, the response should be 201 (Created)
If PUT try to modify cached resources, then the cache must be invalidated. Responses to this method are not cacheable.
@PutMapping("/rest/v1/books/{bookId}")
public ResponseEntity<Void> updateBook(@RequestBody Book book, @PathVariable long bookId) {
try {
book.setId(bookId);
bookService.update(book);
return ResponseEntity.ok().build();
} catch (ResourceNotFoundException ex) {
// log exception first, then return Not Found (404)
logger.error(ex.getMessage());
return ResponseEntity.notFound().build();
} catch (BadResourceException ex) {
// log exception first, then return Bad Request (400)
logger.error(ex.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
}
}
Method updateBook(...) is annotated with @PutMapping. After successful update, HTTP response code 200 (OK) is returned. If the given URL not represent any existing resource, then 404 (Not found).
Sample curl
curl -X PUT "http://localhost:8080/spring-rest-examples/rest/v1/books/1" -H "accept: */*" -H "Content-Type: application/json" -d "{ \"id\": 1, \"oclc\": 1827184, \"isbn10\": null, \"isbn13\": null, \"title\": \"The Hobbit - There and Back Again\" },"
The difference between the POST and PUT can be stated as this: POST requests are made on resource collections whereas PUT requests are made on an single resource.
PUT request is used for completely replacing resource at the given URL with the given data (FULL update). For PARTIAL update, we use PATCH method.
PATCH
PATCH requests are used to make partial update on a resource. A PATCH request is one of the lesser-known HTTP methods. Support for PATCH in browsers, servers and web applications are not universal, this can post some challenges. Request payload for PATCH is can be challenging and, but the common approach is to send a delta (diff) rather than the entire resource.
- In case of successful update then use HTTP response code 200 (OK) or 204 (No Content). Same as PUT, I choose 200 (OK) indicating a successful update.
- In case that the resource doesn't exist, return 404 (Not Found). Avoid creation of new resource based on partial data/information.
If PATCH try to modify cached resources, then the cache must be invalidated. Responses to this method are not cacheable. In my example below, I only using String as my payload to update title.
@PatchMapping("/rest/v1/books/{bookId}")
public ResponseEntity<Void> updateBookTitle(@RequestBody String title, @PathVariable long bookId) {
try {
bookService.updateTitle(bookId, title);
return ResponseEntity.ok().build();
} catch (ResourceNotFoundException ex) {
// log exception first, then return Not Found (404)
logger.error(ex.getMessage());
return ResponseEntity.notFound().build();
} catch (BadResourceException ex) {
// log exception first, then return Bad Request (400)
logger.error(ex.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
}
}
Method updateBookTitle(...) is annotated with @PatchMapping. After successful update, HTTP response code 200 (OK) is returned. If the given URL not represent any existing resource, then 404 (Not found).
Sample curl
curl -X PATCH "http://localhost:8080/spring-rest-examples/rest/v1/books/1" -H "accept: */*" -H "Content-Type: application/json" -d "The Hobbit"
DELETE
DELETE method are used to delete a resource identified by the request URI.
- A successful deletion should return HTTP response code 200 (OK) or 204 (No Content). DELETE can be a long-running or asynchronous request, so return 202 (Accepted) if the action has been queued.
- In case that the resource doesn't exist, return 404 (Not Found)
DELETE operations are idempotent. If you DELETE a resource, it’s removed from the collection. Although calling DELETE on a resource on subsequent time will return a 404 (Not Found), the resource still (or already) removed from collection.
If DELETE try to delete a cached resource, then the cache must be invalidated. Responses to this method are not cacheable.
@DeleteMapping(path="/rest/v1/books/{bookId}")
public ResponseEntity<Void> deleteBookById(@PathVariable long bookId) {
try {
bookService.deleteById(bookId);
return ResponseEntity.ok().build();
} catch (ResourceNotFoundException ex) {
logger.error(ex.getMessage());
return ResponseEntity.notFound().build();
}
}
Method deleteBookById(...) is annotated with @DeleteMapping. After successful delete, HTTP response code 200 (OK) is returned. If the given URL not represent any existing resource, then 404 (Not found).
Sample curl
curl -X DELETE "http://localhost:8080/spring-rest-examples/rest/v1/books/2" -H "accept: */*"
Notes
You can replace @GetMapping, @PostMapping, @PutMapping, @PatchMapping, @DeleteMapping with @RequestMapping with specified method parameter.
Safe methods: HTTP methods that do not modify resources, or to be precise: it won't change the result representation. It is still possible, that safe methods do change things on a server or resource, but this should not reflect in a different representation. Safe methods can be cached, prefetched without any repercussions to the resource
Idempotent methods: HTTP methods that can be called many times without different outcomes. It would not matter if the method is called only once, or multiple times over, the result should be the same. Again, this only applies to the result representation, not the resource itself.