Handle POST and PUT requests with Spring Boot

By Daniel Vladimirov / a few months ago

In this tutorial we’ll handle POST and PUT requests sent to our server, we’ll create and update entities and use in-memory data structures to represent our database.

Spring Boot

Check out this tutorial on how to setup a basic Spring Boot application if you need to


If you are familiar with how to set up a Spring Boot application and want to just get the base source code for this tutorial, you can do so from GitHub

Web Application Skeleton – Tomcat (GitHub)

POST and PUT are one of the most common HTTP methods, usually used for creating and modifying data on the target server, which is also the main difference between POST/PUT and the GET method.

The PUT method shares an important property with the GET method – idempotence, which means that the method is considered safe for repeatable requests. The GET method, by convention, must only retrieve an entity from the server and never have side effects, the PUT method however is specified to serve two purposes – save an entity, if it doesn’t exist, and update an entity’s state with the state sent in the enclosing request.

Wikipedia

Idempotence is the property of certain operations in mathematics and computer science, that can be applied multiple times without changing the result beyond the initial application

So in this sense, the PUT request is also idempotent, since it doesn’t change the state of the entity beyond the initial update. Sure, this doesn’t really take into account that the entity state can be changed by another client in the meantime, but as far as the PUT method, performed by one client only, is concerned, it’s idempotent and safe for repeating the same request over and over.

The POST method however is not one of the idempotent methods and it’s not considered safe by the HTTP convention. The POST method is used to place a new entity, enclosed in the request, under the specified resource of the server. In other words – use POST to create an entity and use PUT to either create or update an entity. The POST method is not idempotent, because repeating the same request over and over again is expected to create a new entity every time, i.e. the method always has a side effect. This is the reason why sometimes you see this message from your browser

It wouldn’t be very nice if the action you took was to send someone money and you could by accident repeat this.

Results from the POST method can be cached, but this is rarely used and it relies on headers like Cache-Control and Expires.

Let’s dive into some examples of handling POST and PUT requests with a Spring Boot application.

What we’re going to create is an application that creates entities representing places you’ve visited and your opinion of them. Whenever you visit a place you’d create a new record and when you re-visit you just update the existing entity in the “database”.

We’re not going to use a real database for this tutorial, so that it focuses more on the POST and PUT methods and how they should be handled and not complicate it with more boilerplate code. We’re going to use an in-memory data structure to keep our data while the application is running.

Let’s start by defining our model. Create a class and name it Location and place it in an appropriate package

The @JsonInclude annotation is there to tell our JSON serializer that we don’t want our client to see properties that have null values, for example the updatedOn property will be null when you create a new location. We want to hide this from our response.

Next, create a class LocationRepository, that will serve as a container for our “database” and also provide methods to access it and save or update our entities.

The class is annotated with the Spring annotation @Repository, to communicate the class intent better and also provide under the hood the @Component annotation that will allow for this class to be discovered by Spring and initialized in the Dependency Injection container.  The “database” is simply a map the keys for which are the names of our locations, so we can get quick access any location entity by name. We will use a LinkedHashMap to ensure insertion order.

The updateProperties(…) method has the purpose to update only the properties we want to update – we don’t want to update the name or the date the location was originally inserted. Usually you will use for example another library for this purpose, although writing this yourself is also not so uncommon and it’s definitely faster than reflection (what most libraries use for this job). More on reflection in other tutorials.  Notice that we don’t check if such location exists before updating it, because we’ve moved this logic in our service layer. We want our database access layer to be clean from any business logic that is not appropriate for it.

To access the repository we’re going to create a service class – LocationService

The class is annotated with @Service for the same reasons as with the repository class. We’ve declared a date-time formatter for our dates, because the default format for the LocalDateTime object is quite overwhelming when serialized to JSON.

What is special about this class in that it has a nested class ServiceResponse that is used to communicate the type of response we want to return from our service. Notice this is not an HTTP response. We use it in the update(…)  method, because this method is called from the PUT requests in the application, so it has the responsibility of deciding to create or update an entity. In this method we do several checks to decide which way to go. We have the name of the requested location from a path variable, supplied from the request URI. If such a location exists we update it, if it doesn’t we create it. This way we ensure the idempotence of our PUT method.

So, depending on the operation we’ve just performed (create or update), we return a specific ServiceResponse object that contains the type of action we just did and the resulting entity. This is just one example of numerous ways for you to handle this logic, I’m sure there probably was a more simple way to handle this, but I find this solution to better visualize what is happening. Also, you might ask why we didn’t just return a ResponseEntity with an HTTP status code and an entity. Well, I think separating layers is important, so It’s a good idea to keep the HTTP stuff in our controllers and just think of a way to move objects and data through the application with our own objects and conventions.

The last component we need for our example application is the controller, so create a class and name it LocationController

For handling POST requests our controller has the createLocation(…) method, which is pretty simple. It checks if a location with the same name already exists in our “database” and if it does it returns a 409 CONFLICT status to let the client know that their request conflicts with a resource on the server, otherwise it just saves the new location entity.

The updateLocation(…) handles the PUT requests to our application. Based on the ServiceResponse object it receives after calling the service layer, it decides what the status code for the response would be. In case we created an entity on this URI, we return a 201 CREATED status along with the new entity. In case we updated an entity, we return a 200 OK code along with the updated entity and just for a fallback we throw an exception in the default case of the switch statement.

The method also has a path variable which is used later on to identify the resource while it gets updated.

Last, the controller has two methods that handle GET requests – one to return a single entity and one to return all entities from the “database”.

Let’s try out the application. Start it up with the ApplicationConfiguration class.

Currently, I like to use POSTMAN for calling APIs, but you can use whatever tool you like.

Let’s create a new location entity by calling our endpoint with a POST method

http://localhost:8080/locations

You need to add the Content-Type http header with a value of application/json to your request and send the following body

You will receive a 201 CREATED status

along with the following JSON response, representing the newly created entity

Try the same request again, you should receive a 409 CONFLICT

Lets insert another location, change the body of your request to this (or whatever you like)

After you create this location, lets check what locations we currently have in the “database” by calling

http://localhost:8080/locations

with a GET method

You should receive an array with all the locations that were saved

Let’s update our Barcelona location, say we finally decided to move there.

Call the following URL with a PUT method and again set the Content-Type http header with a value of application/json

http://localhost:8080/locations/Barcelona

For your request body, use the following JSON

You will receive a 200 OK status code, meaning the entity was updated. The response should be

which is the same response you will receive if you call the URI for Barcelona with a GET method.

Let’s try our PUT method idempotence. We will create a new entity with the PUT method and then call it again multiple times. Just change the body of your PUT request to a JSON object representing a location that is not in the “database”, for example a local bar. Also, don’t forget to change the URI to whatever the name of the JSON entity is, in this case CrazyCoderBar

http://localhost:8080/locations/CrazyCoderBar

otherwise you will receive a 400 BAD REQUEST status code, because your JSON object’s “name” property doesn’t match the requested URI.

The local bar payload

You should receive a 201 CREATED status, since this entity (identified by the URI) didn’t exist in the database and the following response

Calling the PUT method again will have no other effect on this or any other entity, if you don’t change any of the properties in the request, which means that our PUT method is actually idempotent and will not confuse its clients.

So, this is all for this tutorial, we learned how to implement handling logic for POST and PUT requests, using Spring Boot, and what is the main difference between those HTTP methods. Remember that when you are developing a REST API, you are in full control of the implementation of your handlers and logic.

Let me know in the comments if you need something explained or something is not working properly. Any feedback appreciated.

Have fun.


About the author

Daniel Vladimirov

Software engineer and founder of simplyprogram.com

Click here to add a comment

Leave a comment: