In our previous post “how to create a CRUD REST API in Spring Boot”, we discussed how to create a basic REST interface in Spring boot using the different HTTP verb mapping annotations @GetMapping, @DeleteMapping, @PutMapping and @PostMapping. These mappings correspond to the HTTP methods GET, DELETE, PUT and POST respectively.
However, we have not yet discussed how to map the HTTP PATCH verb to a Spring REST endpoint.
In this tutorial, we will discuss how to use the @PatchMapping annotation in Spring, what is it used for, and the key difference between HTTP PUT and HTTP POST.
Before we start, I would recommend that you check our previous post about REST in Spring Boot and our post regarding bean validation, as this tutorial builds on top of them.
What is the difference between HTTP PUT and HTTP PATCH
In order to have a clear picture of why we need the HTTP PATCH verb, we need to understand what HTTP PUT and HTTP PATCH are intended for.
How HTTP PUT and HTTP PATCH update existing data?
As mentioned in our previous tutorial, HTTP PUT is intended for updating a pre-existing item. The way an item is updated through HTTP PUT is by replacing the entire entity with the incoming one. This means that whatever document or unit of data that exists in your system will be completely overridden by the incoming data.
On the other hand, an HTTP PATCH request is meant to patch an existing item. A PATCH request is considered as a list of instructions that are executed to update existing data. This means that only the fields that are included in a PATCH request are updated.
Idempotence
Idempotence is a rather fancy word which is thrown around a lot when describing HTTP verbs. You can read more about the meaning of the term in this Wikipedia article.
Here at nullbeans.com, we hate fancy words, therefore let us simplify its meaning before moving forward.
When an HTTP verb is described as “Idempotent”, it means that the results of requests sent using such HTTP method are repeatable/reproducible. Take for example the following Java method:
int multiply(int x, int z){
return x*y;
}
One can say that the method multiply is idempotent. This is because everytime you call the method with the same values, the result will be the same. If you multiply 2 by 4, the result will always be 8.
In the HTTP/1.1 specification document, HTTP PUT is marked as idempotent. This means that you will need to implement your PUT mappings in a way that the same input will always produce the same result. However, in the HTTP PATCH method specification, HTTP PATCH is not idempotent. This means that it is up to you to decide if your REST endpoint handles PATCH requests in an idempotent way or not.
When to use PATCH and PUT
To answer this question, you need to realize that HTTP verbs are only tools among many other tools at your disposal. Having one does not mean that you cannot have the other. However, we can state the following:
- PUT is convenient when you need to deal with documents in an “atomic” way. This means that the document that is sent is replaced in its entirety.
- PUT is also convenient when you need idempotence. For example, in situations where you are not sure if your PUT request went through to your server or not (for example due to a network issue), you can simply resend the same PUT request, knowing that in the end the result will be the same (it doesn’t matter if your previous request went through or not).
- PATCH is convenient when dealing with large documents, for example, if you are modifying an entity that has 30 or more fields. In this case, it does not make sense to send all 30 fields when you only need to modify one. With PATCH, you can send only the fields that need to be modified, saving you some precious computing resources and network bandwidth.
Having said that, there is no reason not to have both methods implemented.
How to Configure HTTP PATCH in a REST controller in Spring
Configuring a REST controller to process HTTP PATCH requests is very similar to mapping other HTTP methods. All you need to do is to use the @PatchMapping annotation.
Since the PATCH method modifies preexisting items, we will also need to configure a path variable which will be used to identify the item being modified.
We will continue the example from our last tutorial which made use of the Ticket and the TicketRestModel classes.
@PatchMapping("/{ticketId}")
public ResponseEntity patchTicket(@PathVariable long ticketId, @RequestBody Map<String, Object> changes){
Unlike the POST and the PUT mappings, we will not be using the TicketRestModel as an input paramenter for this method. This is because of the different nature of the PATCH verb. We can summarize these reasons as follows:
- Unlike PUT and POST, PATCH only expects a subset of the document being modified, and not the whole document.
- If the REST model is used in a PATCH method, and some fields of the model are set to null, it will not be possible to differentiate between fields that were missing from the PATCH request and fields that we actually want to set to null.
- Since a patch indicates a collection of instructions that are used to modify a document, a Map object seems more appropriate here.
Our goal in the next few sections is to provide a simple implementation of the PATCH method in a Spring REST controller. Our implementation will also try to preserve the validation settings that we configured in our “validating REST requests” tutorial.
Overview of our implementation
Before we start our implementation, we would like to give you an overview of what we will do in the next few sections. This will help us to keep in mind the steps of our implementation. These steps can be summarized as follows:
- Once we receive the PATCH request, we will load the item to be edited from the database.
- This item will then be converted (or mapped) to an instance of the TicketRestModel. The REST DTO model is where the bean validation annotations are configured.
- We will then apply the patch to the REST model.
- Once the REST model instance has been updated, we will validate the instance using bean validation.
- If the instance passes the validation operation, then we will map the REST model instance back onto the persistence model instance.
- The persistence instance is saved.
Please note that this is just an example implementation. There are other ways to do this which might be more appropriate for your situation. However, our aim is to provide a starting point for those who are totally new to HTTP PATCH mapping in Spring.
Now, let us start with our implementation steps.
Mapping back and forth between the persistence model and REST model
In order to utilize the validation mechanisms we configured in our previous tutorial, we will need a mapping mechanism between the REST model and the persistence model.
In this post, we will keep it very simple by manually writing our own mapping methods. These methods can be added in your controller class or preferably in a “Mapper” class.
private TicketRestModel mapPersistenceModelToRestModel(Ticket ticket){
TicketRestModel ticketRestModel = new TicketRestModel();
ticketRestModel.setIdentifier(ticket.getId());
ticketRestModel.setTitle(ticket.getTitle());
ticketRestModel.setDescription(ticket.getDescription());
ticketRestModel.setStatus(ticket.getStatus());
return ticketRestModel;
}
private void mapRestModelToPersistenceModel(TicketRestModel ticketRestModel, Ticket ticket){
ticket.setTitle(ticketRestModel.getTitle());
ticket.setStatus(ticketRestModel.getStatus());
ticket.setDescription(ticketRestModel.getDescription());
ticket.setStatus(ticketRestModel.getStatus());
}
Please note that in this case, the model is quite simple. Therefore it is easy to write such mapping methods. However, in a real world scenario, you would probably encounter much larger and more complicated entities. In these cases, I would recommend using a mapping framework, such as Dozer.
Loading the data to be modified and applying the patch to it
Now that we can map back and forth between the different types of models, it is time to patch the persistence data with the incoming requests. Again, we will keep our implementation very simple. We will patch the REST model by looping through the contents of the incoming changes map.
@PatchMapping("/{ticketId}")
public ResponseEntity patchTicket(@PathVariable long ticketId, @RequestBody Map<String, Object> changes){
//Fetch the data from the database
Ticket ticket = ticketRepository.findById(ticketId).get();
//Map the persistent data to the REST dto
//This is done to enforce REST validation groups
TicketRestModel ticketRestModel = mapPersistenceModelToRestModel(ticket);
//apply the changes to the REST model.
changes.forEach(
(change, value) -> {
switch (change){
case "title": ticketRestModel.setTitle((String) value); break;
case "description": ticketRestModel.setDescription((String) value); break;
case "status": ticketRestModel.setStatus((String) value); break;
}
}
);
Notice that we applied the patch to the REST model. This is done in order to use the bean validation annotations to validate the modified data (more on that in the next section).
Also notice that we used a switch conditional statement in order to map the map items to model fields. In a real world scenario, you can use techniques such as Java reflection or a mapping framework to perform such mappings (if you prefer to).
Note that the advantage of doing the mapping in this way is that we only edit the data that is mentioned in the patch. For example, if we send the following patch request, only the title field will be edited.
{
"title":"My patched title"
}
This makes PATCH requests lightweight. This also makes PATCH more appropriate than PUT when the document to be edited is a large one. With PATCH, only the fields that need to be edited are sent, making the request faster and less network resource intensive.
Validating the modified data
Now that we have modified our REST model, we will need to validate it’s contents. Since we have manually created the REST model’s instance, we will need to manually trigger validation for it.
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
Set<ConstraintViolation<TicketRestModel>> violations = validator.validate(ticketRestModel, OnUpdate.class);
if(!violations.isEmpty()) {
//throw your REST business exception here
return ResponseEntity.badRequest().body(violations.toString());
}
Here, we create our own instance of the Java bean validator and use it to validate our instance. For simplicity, we created the validator inside the patch method. However, for performance reasons, it is highly recommend that you create your validator instance as part of the application/bean startup so you will not need to create a new instance of the validator everytime someone makes a patch call.
Using the validator.validate method will cause the validator to check the constraints that are configured in the model class. We can also activate the validation group OnUpdate by passing the class as an argument to the validate method. If there are any constraint violations, they will be added to the violations set.
Finally, we check the violations set. If it is not empty, we return an HTTP 400 (Bad Request) code back to the caller. For example, if we send a PATCH request with invalid data (such as a very short title), we will get an error such as this.
HTTP/1.1 400
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: text/plain;charset=UTF-8
Content-Length: 268
Date: Thu, 09 Apr 2020 06:38:15 GMT
Connection: close
[ConstraintViolationImpl{interpolatedMessage='size must be between 10 and 2147483647', propertyPath=title, rootBeanClass=class com.nullbeans.customerservice.incidentmanagement.rest.models.TicketRestModel, messageTemplate='{javax.validation.constraints.Size.message}'}]
Note that handling such errors is better done using ControllerAdvice which we will discuss in a future tutorial.
Saving the modified data
After the patched data has passed validation, it is time to save it into our database. To do so, we will need to map our data from the REST model to the persistence model.
mapRestModelToPersistenceModel(ticketRestModel, ticket);
ticketRepository.save(ticket);
return ResponseEntity.ok().build();
Our complete method code now looks as follows.
@PatchMapping("/{ticketId}")
public ResponseEntity patchTicket(@PathVariable long ticketId, @RequestBody Map<String, Object> changes){
//Fetch the data from the database
Ticket ticket = ticketRepository.findById(ticketId).get();
//Map the persistent data to the REST dto
//This is done to enforce REST validation groups
TicketRestModel ticketRestModel = mapPersistenceModelToRestModel(ticket);
//apply the changes to the REST model.
changes.forEach(
(change, value) -> {
switch (change){
case "title": ticketRestModel.setTitle((String) value); break;
case "description": ticketRestModel.setDescription((String) value); break;
case "status": ticketRestModel.setStatus((String) value); break;
}
}
);
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
Set<ConstraintViolation<TicketRestModel>> violations = validator.validate(ticketRestModel, OnUpdate.class);
if(!violations.isEmpty()) {
//throw your REST business exception here
return ResponseEntity.badRequest().body(violations.toString());
}
mapRestModelToPersistenceModel(ticketRestModel, ticket);
ticketRepository.save(ticket);
return ResponseEntity.ok().build();
}
Now, let us try it out by sending a PATCH request, which only modifies the “title” field. This can be done by calling the endpoint using the PATCH verb and with the (example) URI “yourserver:portnum/api/tickets/12”
{
"title":"My patched title"
}
The result of the call will look as follows.
HTTP/1.1 200
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Length: 0
Date: Thu, 09 Apr 2020 06:42:48 GMT
Keep-Alive: timeout=60
Connection: keep-alive
If we call the GET method of the endpoint on the same URI, we will get the following response document.
{
"identifier": 12,
"title": "My patched title",
"description": "No title is added in this request",
"status": "IN_PROGRESS"
}
Notice that only the title field has changed. All the other fields remain the same.
If you made it this far, then congratulations ๐ . You now have a functioning endpoint which accepts PATCH requests.
Summary
In this tutorial, we discussed how to implement a PATCH mapping in a Spring boot REST endpoint.
Note that parts of our implementation can be replaced by something such as JsonPatch or any other framework which can handle PATCH requests. However, our intention was to provide a starting point for those who would like to take advantage of the HTTP PATCH verb.
If you have any questions or if you liked (or disliked) this tutorial, then please let us know in the comments section below ๐
Leave a Reply
You must be logged in to post a comment.