How to create a CRUD REST API in Spring Boot

REST APIs are a very important layer in many Spring applications. Specially if you need to provide interfaces for other services in your ecosystem such as front-end applications or to external systems, such as customer systems.

In this post, we will discuss how to start building your REST API using Spring Boot in order to perform CRUD operations. In our example, we will create an endpoint which will be used to create, read, update and delete ‘tickets’ in a not so imaginary ticket management system.

Adding the required dependencies

In order to build the REST API, you will need to add a dependency for the Spring-Web library. You can easily do this in Spring-boot by adding the following dependency to your POM.xml file.

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

Since we will be doing CRUD operations using a relational database, we will also need a dependency for Spring-data.

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>

Finally, we will also add a dependency to Lombok in order to eliminate boilerplate code.

		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<version>1.18.10</version>
		</dependency>

Creating the data model and repository

Before we start performing CRUD operations via the REST API, we will need to create the corresponding Spring data model and repository in order to interact with the database. We will not go into much detail here. However, you can read more about Spring data and JPA in our tutorial series here. We will also be using Lombok to eliminate any boilerplate code such as getters and setters. You can get up to speed with Lombok in our Lombok annotations guide.

Let us start by creating a simple data model called “Ticket”.

package com.nullbeans.customerservice.incidentmanagement.data.models;

import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Entity;
import javax.persistence.ManyToOne;


@Data
@NoArgsConstructor
@Entity
public class Ticket{

    @Id
    @GeneratedValue
    public long id;

    public String title;

    public String description;

    public String status;

}

Now let us create the repository required to interact with the database.

package com.nullbeans.customerservice.incidentmanagement.data.repositories;

import com.nullbeans.customerservice.incidentmanagement.data.models.Ticket;
import org.springframework.data.repository.CrudRepository;

public interface TicketRepository extends CrudRepository<Ticket, Long> {
}

Make sure that you have configured Spring boot to recognize where your data models reside.

Creating a REST Model

When offering an external API, it is always a good idea to interact with external systems using dedicated classes. Let us create a dedicated REST model for the Ticket entity.

package com.nullbeans.customerservice.incidentmanagement.rest.models;

import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
public class TicketRestModel {
    
    public long identifier;
    
    public String title;

    public String description;

    public String status;

}

The reason why you need a different REST model than the one you used as the database model is to avoid having any dependencies between your external facing interfaces and your internal database model.

Imagine having to create a new field or changing something in your database model. Every time you do that, you will also have to update any external systems that utilize your API.

What is even worse is that you might break compatibility with other systems which you do not have control over, such as customer systems.

Therefore, it is a great idea to have a different rest model, even if it means having some redundancy.

Using SoapUI to test the REST controller

Before we start, I would like to introduce a handy little tool called SoapUI. With SoapUI, you can test REST Endpoints, which is very handy when first learning REST and how to create REST controllers.

An example SoapUI project for our REST interface

You can download the open source version for free from this page. Note that once you have completed development of your REST controllers, you should test your API using Spring integration tests and Mokito tests in Java.

The reason we used this tool in this tutorial is to demonstrate how the REST interface works and to familiarize everyone with the different aspects that go into a REST web call.

Creating the REST Controller

A REST controller in Spring can be used to represent a REST-endpoint that your application offers to outside systems. A REST-endpoint is an API which can be used to perform different management and business operations in your system.

In Spring, this can easily be achieved by annotating a class using the @RestController.

What is the @RestController annotation?

The @RestController annotation is an annotation from Spring-Web which is used to mark a class or an interface as a “web controller”. This is equivalent to using the @Controller annotation which makes your class itself a Spring component (bean). Additionally, the @RestController annotation will cause the binding of the return values of any bean methods annotated with @RequestMapping to the web response body.

In order to map the controller to a specific http endpoint path, we will also need to configure the path using the @RequestMapping annotation.

In the end, our class will look as follows:

package com.nullbeans.customerservice.incidentmanagement.rest.controllers;

import com.nullbeans.customerservice.incidentmanagement.data.models.Ticket;
import com.nullbeans.customerservice.incidentmanagement.data.repositories.TicketRepository;

@RestController
@RequestMapping(path = "/api/tickets")
public class TicketController {

    private TicketRepository ticketRepository;

    public TicketController(TicketRepository ticketRepository){
        this.ticketRepository = ticketRepository;
    }

//..... to be continued..

This configuration will map the TicketController class to the http endpoint resource “/api/tickets”. So if your server is running on “localhost:8081”, then the endpoint will be reachable on the HTTP URL “http://localhost:8081/api/tickets”.

What is a ResponseEntity

A ResponseEntity is a class from Spring-web which is used to represent a response to an HTTP request. In other words, it is used as the return type of controller methods which serve REST requests. It is usually preferred to use a ResponseEntity object as the response because it offers us a lot of control over the response we sent to the REST endpoint caller.

With a ResponseEntity object, you can control:

  • The response body. For example, you can attach data of the media type of your choosing, such as plain text or JSON.
  • HTTP response code: This one is very important. For example, instead of just replying with a 200 HTTP code (ok), you can reply with something more meaningful such as an HTTP status code 201 (created), when a new object has been created in your system, or a 202 (accepted), when you receive a request and acknowledge that it will be processed.
  • HTTP headers: You can also send additional data using HTTP headers. For example, you can send the location URI of a newly created object as a header when responding to a creation operation.

We will see examples of how to use the ResponseEntity object in the next few sections.

Mapping HTTP methods to controller methods

When doing CRUD operations over REST, we will need to adhere to the RESTful principles of REST API design. We will not go into great detail about RESTful and the advantages of following this design pattern in this tutorial. However, let us take a quick look into the different HTTP methods (also called HTTP verbs) and to which logical operation they map to:

  • GET: The HTTP GET method is typically used to fetch items. You usually provide the identifier of the item you would like to fetch as a path variable. This is a variable that you can see in the HTTP URI, as part of the path. For example, if you would like to fetch a ticket with the identifier 212, then the URI would look like: ” http://localhost:8081/api/tickets/212″.
  • POST: The HTTP POST method is typically used to create new items. This means that the contents in the HTTP request body will be handled as new data. This data will be saved by the system under a new key or unique identifier.
  • PUT: The HTTP PUT method is used to replace an existing item. This means that the caller will need to provide the identifier of the item being updated, along with the complete data of the item . The item identifier can be provided as a path variable.
  • PATCH: The HTTP PATCH method is used transmit and perform specific instructions to update an existing item. In other words, you would like to “patch” an existing item. With the HTTP PATCH method, you only need to send the specific subset of data that needs to be updated. To identify the item being updated, you will need to provide the resource’s ID as a path variable. To keep things in this post simple, we will provide an example of this HTTP verb in another tutorial.
  • DELETE: The HTTP DELETE method is used to delete an existing item. When using the delete method, the object identifier should be provided as a path variable and not in the request body.

Please note that these are not the only HTTP verbs that exist. However, other HTTP methods are out of scope for this post. If you are not familiar with the HTTP verbs, then I recommend that you take a look at the HTTP/1.1 specifications here.

Please also note that the RESTful design principles are only guidelines. While it is a good idea to practice them, it is also possible that you might need to break a rule or two during your implementation.

Now let us create our CRUD controller methods.

@GetMapping

The @GetMapping annotation from Spring is used to map HTTP GET requests to a specific controller method.

In the following method, we will do two things. First, we will configure the method to be mapped to HTTP GET and map a path variable to a method parameter.

    @GetMapping("/{ticketId}")
    public ResponseEntity<TicketRestModel> getTicketById(@PathVariable long ticketId){
        Ticket ticket = ticketRepository.findById(ticketId).get();
        TicketRestModel ticketRestModel = new TicketRestModel();
        ticketRestModel.setIdentifier(ticket.getId());
        ticketRestModel.setTitle(ticket.getTitle());
        ticketRestModel.setDescription(ticket.getDescription());
        ticketRestModel.setStatus(ticket.getStatus());
        return ResponseEntity.ok(ticketRestModel);
    }

Notice that we configured the annotation to map a path parameter and call it “ticketId”. By default, Spring will try to match the path variable name to the method parameter by name. However, if you would like to give them different names, then you can do it as follows.

    @GetMapping("/{id}")
    public ResponseEntity<TicketRestModel> getTicketById(@PathVariable(name = "id") long ticketId){

Let us also a mention a few important points as these will apply to the next sections as well:

  • @PathVariable annotation is used to indicate that a method argument is a path variable passed by the REST endpoint caller.
  • Notice how we fetched the item from the database, and then we copied the item from the persistence model to the REST model. We will be doing a copy back and forth from the REST model to the database model in all our interactions.
  • We set the HTTP reply code of the response by using the ResponseEntity.ok method. There are many more static methods which you can use to build different responses, such as ResponseEntity.created, ResponseEntity.accepted, etc.
  • We inserted the entity that we copied from the database into the response’s body by passing it as an argument to the ResponseEntity.ok method.

Let us try out the method by making a GET call with a preexisting item ID. For example, we make a get call to the URI “http://localhost:8081/api/tickets/22”. The result’s content body will be a in JSON format and will look something like this.

{
   "identifier": 22,
   "title": "My First Ticket",
   "description": "I would like to learn REST in Spring",
   "status": "NEW"
}

If you are interested, the response headers will also 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-Type: application/json
Transfer-Encoding: chunked
Date: Fri, 27 Mar 2020 13:07:29 GMT
Keep-Alive: timeout=60
Connection: keep-alive

@PostMapping

The @PostMapping annotation is used to map the HTTP POST method to a Spring Controller’s method.

Let us implement our “create” controller method using the annotation.

    @PostMapping
    public ResponseEntity<TicketRestModel> addTicket(@RequestBody TicketRestModel ticketInput) throws URISyntaxException {

        Ticket ticket = new Ticket();
        ticket.setStatus("NEW");
        ticket.setTitle(ticketInput.getTitle());
        ticket.setDescription(ticketInput.getDescription());

        ticketRepository.save(ticket);

        ticketInput.setIdentifier(ticket.getId());
        ticketInput.setStatus(ticket.getStatus());
        
        return ResponseEntity.created(new URI(String.valueOf(ticket.getId()))).body(ticketInput);
    }

Please notice that we used the @RequestBody annotation to indicate that the method parameter should be obtained by deserializing the request body of the HTTP request. By default, request bodies in JSON format are supported.

Also notice that we added the location header with the URI (the ID) value of the newly created object into the response’s headers.

Let us try out the endpoint by sending a POST request to the URL “http://localhost:8081/api/tickets” with the following body.

{
"title":"My First Ticket",
"description":"I would like to learn REST in Spring"
}

The following response body will be obtained.

{
   "identifier": 11,
   "title": "My First Ticket",
   "description": "I would like to learn REST in Spring",
   "status": "NEW"
}

And the server will also send along the following headers.

HTTP/1.1 201 
Location: 11
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: application/json
Transfer-Encoding: chunked
Date: Thu, 26 Mar 2020 17:13:47 GMT
Keep-Alive: timeout=60
Connection: keep-alive

Notice the location header has the value 11, and the response code is 201.

@PutMapping

The @PutMapping annotation can be used to annotate controller methods in Spring to map the methods to REST-Endpoints and to serve callers who use the HTTP PUT verb.

Let us try out the annotation by creating an “updateTicket” method. In this example, we will not return a method body. We will only reply to the caller with an HTTP 200 OK response. We will also obtain the ticketId of the ticket that needs to be updated as a path variable.

    @PutMapping("/{ticketId}")
    public ResponseEntity<Void> updateTicket(@PathVariable long ticketId, @RequestBody TicketRestModel ticketInput){

        Ticket ticket = ticketRepository.findById(ticketId).get();
        ticket.setStatus(ticketInput.getStatus());
        ticket.setTitle(ticketInput.getTitle());
        ticket.setDescription(ticketInput.getDescription());

        ticketRepository.save(ticket);
        return ResponseEntity.ok().build();
    }

In this example, we have both a path variable and the request body as method arguments. These can be configured by using the corresponding annotations @PathVariable and @RequestBody.

Let us call the service using HTTP PUT with the URL “http://localhost:8081/api/tickets/11” and with the following JSON content.

{
"title":"My updated ticket",
"description":"I would like to learn REST in Spring"
}

We will get a response without a body and with the following headers.

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: Fri, 27 Mar 2020 12:17:33 GMT
Keep-Alive: timeout=60
Connection: keep-alive

@DeleteMapping

The @DeleteMapping is an annotation from Spring which can be used to annotate controller methods to allow them to intercept and serve HTTP DELETE requests that are sent to the REST endpoint.

Note that like the HTTP GET verb, we will use a path variable to obtain the id of the ticket.

    @DeleteMapping("/{ticketId}")
    public ResponseEntity<Void> deleteTicket(@PathVariable long ticketId){
        Ticket ticket = ticketRepository.findById(ticketId).get();
        ticketRepository.delete(ticket);
        return ResponseEntity.accepted().build();
    }

If we call the service on the URL “http://localhost:8081/api/tickets/11” using HTTP DELETE, we will get the following response.

HTTP/1.1 202 
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: Fri, 27 Mar 2020 12:24:14 GMT
Keep-Alive: timeout=60
Connection: keep-alive

Notice that there is no response body for this request. Also notice that our service replied with the HTTP status code 202 (accepted).

Summary and Important notes

In this post, we discussed how to create a basic REST controller in Spring to perform CRUD operations. I realize that this post introduced a lot of important concepts, such as HTTP Request verbs, HTTP response codes, RESTful APIs, Rest Controllers and models.

However, this post was meant as a starting point and you should not rush ahead to production with just the design from this tutorial. A few other things need to be kept in mind:

  • We did not perform any form of input validation in our service. For this, please check our post on how to validate REST calls in Spring Boot.
  • You should write automated tests for your webservices in Java and Spring. You should not rely solely on manual tests using SoapUI.
  • We did not secure our REST endpoint. This also needs to be configured.
  • Finally, we will also need to address error handling. For example, what if a user tries to fetch an object which does not exist?. What happens if a mandatory field is missing?

We will get into detail for each of these concepts in future posts, so please stay tuned 😉

How to create a CRUD REST API in Spring Boot
Scroll to top