Hackernoon logoBuilding a CRUD RESTful API/Web Service with Spring Boot [A How-To Guide] by@gabrielpulga

Building a CRUD RESTful API/Web Service with Spring Boot [A How-To Guide]

Author profile picture

@gabrielpulgaGabriel Pulga

Electrical engineering student from Brazil passionate about learning and teaching people.

This guide aims to help you create from scratch a CRUD RESTful API with Spring Boot. We'll create a User entity and develop its endpoints accordingly with a Rest Controller and a Service class.
We'll cover the following topics in this tutorial :
  • Building our User class with its own repository and particular attributes.
  • Creating CRUD (Create, Read, Update, Delete) endpoints for our User class with a respective Controller and Service.
  • Differences between GET, POST, PUT and DELETE from HTTP request methods.
  • Meaning of all annotations used to make the code cleaner, easier to read and easier to maintain.
Tools used to make this tutorial :
  • IntelliJ as our IDE of choice
  • Maven 3.0+ as build tool
  • JDK 1.8+

Getting started

We’ll first need to bootstrap our project with Spring Initializr.
This can be done by going to http://start.spring.io/ or if you’re using IntelliJ, by creating a New Project and choosing Spring Initializr instead of the standard Java option in the quick menu.
Be sure to select our chosen dependencies to work with, so be sure to include :
  • Lombok
  • Spring WEB
  • Rest Repositories HAL Browser
  • Spring Data JPA
  • H2 Database
This step can also be done by writing the dependencies in the pom.xml file present in our project folder :
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <relativePath/> <!-- lookup parent from repository -->
    <name>Users API</name>
    <description>CodeFiction Spring API Project</description>

Creating our User entity class and UserRepository interface

The first step coding-wise, is to create our User class.
public class User implements Serializable {
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String email;
One thing that may come up to you as unclear, is why are we implementing serialization in our User class?
It’s due to the fact that serialization converts an object into a sequence of bytes which can then be saved to a database or transferred over a network. When working with our User entity, this may come in handy later on.
Notes about the annotations :
When working with JPA as our persistence layer, it’s necessary to map our API to persist our objects of choice.
  • @Entity : Tells that our class can be represented by a table in the database, with each instance representing a row.
  • @Id : Specifies that this attribute is the primary key of this entity.
  • @GeneratedValue(strategy = GenerationType.IDENTITY) : Informs the persistence layer that the strategy used to generate the value of our primary key is by auto-increasing when a new user is created.
As for the rest, this is when Lombok starts to show up in our application.
  • @Data : Creates toStringequalshashCodegetters and setters methods for our class.
  • @AllArgsConstructor : Creates a class constructor with all arguments.
  • @NoArgsConstructor : Creates an empty class constructor with all arguments.
Next step is creating our UserRepository interface which will extend JpaRepository to access the methods necessary to manipulate the User table.
We’ll also need to pass the class that represents our model and the type of the primary key as generic arguments.
public interface UserRepository extends JpaRepository<User, Long> {}
The @Repository annotation is from Spring and its purpose is simply to indicate that the class provides the mechanism for storage, retrieval, search, update and delete operation on objects.

Configuration of our in-memory database and inputting mock data into User table

In our src/main/resources/application.properties file, we’ll need to specify the data source for our Spring application.
Some notes on what we are doing :
  • jdbc : Stands for Java Database Connectivity, it’s a set of class and interfaces written in Java to send SQL instructions to any operational database.
  • h2 : Our in-memory database of choice.
To create mock data for our User class, we’ll need to create a data.sql file and input our data there.
INSERT INTO user(name) VALUES('Neo');
INSERT INTO user(name) VALUES('Owt');
INSERT INTO user(name) VALUES('Three');

Creating CRUD endpoints

For each endpoint, there will be a Rest Controller and a Service class to implement its logic. Our endpoints will be :
  • Create : When given a valid HTTP POST request in /users, create a specific user.
  • Detail : When given a valid HTTP GET request in /users/{id}, retrieve the details of a specific user by its id.
  • List : When given a valid HTTP GET request in /users, retrieve the details of all users.
  • Update : When given a valid HTTP PUT request in /users/{id}, update the details of a specific user by its id.
  • Delete : When given a valid HTTP DELETE request in /users/{id}, delete a specific user by its id.
Creating a new user with the POST request
Since our Service class is the one that will implement the logic of our controller, we might as well start with it :
public class CreateUserService {
    UserRepository repository;
    public User createNewUser(User user) {
        return repository.save(user);
  • @Service : This annotation is a specialization of the @Component annotation, it is used to mark the class as a service provider.
  • @Autowired : Our dependency injection annotation, autowiring our application is fundamental when building it.
As for our controller :
public class CreateUserController {
    CreateUserService service;
    public ResponseEntity<User> createNewUser_whenPostUser(@RequestBody User user) {
        User createdUser = service.createNewUser(user);
        URI uri = ServletUriComponentsBuilder.fromCurrentRequest()
        return ResponseEntity.created(uri).body(createdUser);
  • @RestController : Combines the @Controller and @ResponseBody annotations, which eliminates the need to annotate every request handling method of the controller class with @ResponseBody.
  • @RequestMapping(“/users”) : The main purpose of this annotation here is implementing our URL handler but it could also map our HTTP request onto our method if it wasn’t already being done by the @PostMapping.
  • @PostMapping : Maps our POST request onto our method.
  • @ResponseStatus : Straightforward way to set the status code of our HTTP response.
  • @RequestBody : Ties our method parameter to the body of our HTTP request. This annotation indicates that the return type should be written straight to the HTTP response body (and not interpreted as a view name).
Building the structure of our GET requests : Detail and List
When retrieving the details of one specific user, we’ll need to fetch him by its id and manage the error possibility caused by a scenario that he doesn’t exist.

public class DetailUserService {
    UserRepository repository;
    public Optional<User> listUser(Long id) {
        Optional<User> user = repository.findById(id);
        if (!user.isPresent()) {
            throw new UserNotFoundException(id);
        } else {
            return repository.findById(id);
No new annotations here. Our UserNotFoundException method will come from the following :
public class UserNotFoundException extends RuntimeException {
    public UserNotFoundException(Long id) {
        super("Could not find user with id " + id + ".");
When an UserNotFoundException is thrown, this extra tidbit of Spring MVC configuration will be used to render an HTTP 404 response.
public class UserNotFoundAdvice {
    String userNotFoundHandler(UserNotFoundException ex) {
        return ex.getMessage();
  • @ControllerAdvice : Intercepts our exception.
  • @ExceptionHandler : Configures the advice to only respond if an UserNotFoundException is thrown.
  • @ResponseBody : Signals that this advice is rendered straight into the response body.
As for our Controller, if the contact is found, we’ll return it with a HTTP 200 response.
public class DetailUserController {
    DetailUserService service;
    public ResponseEntity<Optional<User>> listUser_whenGetUser(@PathVariable Long id) {
        return ResponseEntity.ok().body(service.listUser(id));
  • @GetMapping : Similar to the @PostMapping annotation, maps our GET request onto our method.
  • @PathVariable : Binds method parameter id with the path variable /{id}.
When listing or retrieving all users, no exceptions will need to be handled, so this can be done by simply calling the repository.
public class ListUserService {
    UserRepository repository;
    public List<User> listAllUsers() {
        return repository.findAll();
public class ListUserController {
    ListUserService service;
    public ResponseEntity<List<User>> listAllUsers_whenGetUsers() {
        return ResponseEntity.ok().body(service.listAllUsers());
Updating an existing user with the PUT request
When updating an user by its id, we need to check if it exists the same way we did with the listUser method.
Next, we’ll find the user that needs to be updated and save it with its new parameters. Our Service class should be something like :
public class UpdateUserService {
    UserRepository repository;
    public User updateUser(Long id, User user) {
        Optional<User> userOptional = repository.findById(id);
        if (!userOptional.isPresent()) {
            throw new UserNotFoundException(id);
        } else {
            return repository.save(user);
As for building our Controller it should be pretty straightforward by now, since our Service contains the logic behind our endpoint implementation.
All we need to do is pass our soon to be changed user and its id as parameters, also the @PutMapping annotation to handle the HTTP request.
public class UpdateUserController {
    UpdateUserService service;
    public ResponseEntity<User> updateUser_whenPutUser(@RequestBody User user, @PathVariable Long id) {
        return ResponseEntity.ok().body(service.updateUser(id, user));
Removing an user from the database with the DELETE request
Removing an existing user with the delete request method is probably the simplest one. All we have to do is call the repository to delete an user by its id in case it’s found.
In case it doesn’t exist, we’ll need to throw our UserNotFoundException method.
public class DeleteUserService {
    UserRepository repository;
    public void deleteUser(Long id) {
        Optional<User> userOptional = repository.findById(id);
        if (!userOptional.isPresent()) {
            throw new UserNotFoundException(id);
        } else {
public class DeleteUserController {
    DeleteUserService service;
    public void deleteUser_whenDeleteUser(@PathVariable Long id) {


Throughout this tutorial, you have engaged in various steps to build a RESTful API/Web Service with CRUD operations, check out our website for more tutorials like this, our next steps with this API will be :
  • Testing the execution of our endpoints with Postman.
  • Implementing Swagger in our API for documentation and quick interface purposes.
  • Unit testing our endpoints controllers and services with JUnit and Mockito.
  • Integration testing our whole API.

Source code

Available on our github page.


The Noonification banner

Subscribe to get your daily round-up of top tech stories!