This guide aims to help you create from scratch a CRUD RESTful API with Spring Boot. We'll create a entity and develop its endpoints accordingly with a Rest Controller and a Service class. User We'll cover the following in this tutorial : topics Building our class with its own repository and particular attributes. User Creating (Create, Read, Update, Delete) endpoints for our class with a respective and . CRUD User Controller Service Differences between , , and from request methods. GET POST PUT DELETE HTTP Meaning of all annotations used to make the code cleaner, easier to read and easier to maintain. used to make this tutorial : Tools as our IDE of choice IntelliJ 3.0+ as build tool Maven 1.8+ JDK Getting started We’ll first need to our project with Spring Initializr. bootstrap This can be done by going to , by creating a and choosing instead of the standard Java option in the . http://start.spring.io/ or if you’re using IntelliJ New Project Spring Initializr quick menu Be sure to to work with, so be sure to include : select our chosen dependencies Lombok Spring WEB Rest Repositories HAL Browser Spring Data JPA H2 Database This step can be done by writing the dependencies in the file present in our : also pom.xml project folder 4.0.0 org.springframework.boot spring-boot-starter-parent 2.3.0.RELEASE com.users-api demo 0.0.1-SNAPSHOT Users API CodeFiction Spring API Project 1.8 org.springframework.boot spring-boot-starter-data-jpa org.springframework.boot spring-boot-starter-web org.springframework.data spring-data-rest-hal-browser com.h2database h2 runtime org.projectlombok lombok true org.springframework.boot spring-boot-starter-test test org.junit.vintage junit-vintage-engine org.springframework.boot spring-boot-maven-plugin <?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" < > modelVersion </ > modelVersion < > parent < > groupId </ > groupId < > artifactId </ > artifactId < > version </ > version < /> relativePath <!-- lookup parent from repository --> </ > parent < > groupId </ > groupId < > artifactId </ > artifactId < > version </ > version < > name </ > name < > description </ > description < > properties < > java.version </ > java.version </ > properties < > dependencies < > dependency < > groupId </ > groupId < > artifactId </ > artifactId </ > dependency < > dependency < > groupId </ > groupId < > artifactId </ > artifactId </ > dependency < > dependency < > groupId </ > groupId < > artifactId </ > artifactId </ > dependency < > dependency < > groupId </ > groupId < > artifactId </ > artifactId < > scope </ > scope </ > dependency < > dependency < > groupId </ > groupId < > artifactId </ > artifactId < > optional </ > optional </ > dependency < > dependency < > groupId </ > groupId < > artifactId </ > artifactId < > scope </ > scope < > exclusions < > exclusion < > groupId </ > groupId < > artifactId </ > artifactId </ > exclusion </ > exclusions </ > dependency </ > dependencies < > build < > plugins < > plugin < > groupId </ > groupId < > artifactId </ > artifactId </ > plugin </ > plugins </ > build </ > project Creating our User entity class and UserRepository interface The first step coding-wise, is to create our class. User src/main/java/com/usersapi/domain/user/User.java { (strategy = GenerationType.IDENTITY) Long id; String name; String email; } @Data @NoArgsConstructor @AllArgsConstructor @Entity public class User implements Serializable @Id @GeneratedValue private private private 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 : Tells that our class can be represented by a in the database, with each instance representing a . @Entity table row : Specifies that this attribute is the primary key of this . @Id entity : Informs the that the strategy used to generate the value of our primary key is by when a new user is created. @GeneratedValue(strategy = GenerationType.IDENTITY) persistence layer auto-increasing As for the rest, this is when Lombok starts to show up in our application. @ : Creates , , , and methods for our class. Data toString equals hashCode getters setters : Creates a with . @AllArgsConstructor class constructor all arguments : Creates an empty class constructor with all arguments. @NoArgsConstructor Next step is creating our interface which will to access the methods necessary to manipulate the table. UserRepository extend JpaRepository User We’ll also need to pass the class that represents our model and the of the primary key as generic arguments. type src/main/java/com/usersapi/domain/user/UserRepository.java {} @Repository public < , > interface UserRepository extends JpaRepository User Long The 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. @Repository Configuration of our in-memory database and inputting mock data into User table In our file, we’ll need to specify the data source for our Spring application. src/main/resources/application.properties spring.datasource.url=jdbc:h2:mem:usersapi Some notes on what we are doing : : Stands for , it’s a set of class and interfaces written in Java to send SQL instructions to any operational database. jdbc Java Database Connectivity : Our of choice. h2 in-memory database To create for our class, we’ll need to create a file and input our data there. mock data User data.sql src/main/resources/data.sql ; ; ; COMMIT; 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 and a class to implement its logic. Our endpoints will be : Rest Controller Service : When given a valid HTTP request in , create a specific user. Create POST /users : When given a valid HTTP request in , retrieve the details of a specific user by its . Detail GET /users/{id} id : When given a valid HTTP request in , retrieve the details of all users. List GET /users : When given a valid HTTP request in , update the details of a specific user by its . Update PUT /users/{id} id : When given a valid HTTP request in , delete a specific user by its . Delete DELETE /users/{id} id Creating a new user with the POST request Since our class is the one that will implement the logic of our controller, we might as well start with it : Service src/main/java/com/usersapi/endpoints/create/CreateUserService.java { UserRepository repository; { repository.save(user); } } @Service public class CreateUserService @Autowired User public createNewUser (User user) return : This annotation is a specialization of the annotation, it is used to mark the class as a . @Service @Component service provider : Our annotation, autowiring our application is fundamental when building it. @Autowired dependency injection As for our : controller src/main/java/com/usersapi/endpoints/create/CreateUserController.java ( ) { CreateUserService service; (HttpStatus.CREATED) { User createdUser = service.createNewUser(user); URI uri = ServletUriComponentsBuilder.fromCurrentRequest() .path( ) .buildAndExpand(createdUser.getId()) .toUri(); ResponseEntity.created(uri).body(createdUser); } } @RestController @RequestMapping "/users" public class CreateUserController @Autowired @PostMapping @ResponseStatus ResponseEntity<User> public createNewUser_whenPostUser (@RequestBody User user) "/{id}" return : Combines the and annotations, which eliminates the need to annotate every request handling method of the controller class with . @RestController @Controller @ResponseBody @ResponseBody : The main purpose of this annotation here is implementing our but it map our HTTP request onto our method if it wasn’t being done by the . @RequestMapping(“/users”) URL handler could also already @PostMapping : Maps our POST request onto our method. @PostMapping : Straightforward way to set the status code of our HTTP response. @ResponseStatus : 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 interpreted as a view name). @RequestBody not Building the structure of our GET requests : Detail and List When retrieving the details of specific user, we’ll need to fetch him by its and manage the error possibility caused by a scenario that he doesn’t exist. one id src/main/java/com/usersapi/endpoints/detail/DetailUserService.java { UserRepository repository; { Optional<User> user = repository.findById(id); (!user.isPresent()) { UserNotFoundException(id); } { repository.findById(id); } } } public class DetailUserService @Autowired Optional<User> public listUser (Long id) if throw new else return No new annotations here. Our method will come from the following : UserNotFoundException src/main/java/com/usersapi/endpoints/detail/UserNotFoundException.java { { ( + id + ); } } public class UserNotFoundException extends RuntimeException public UserNotFoundException (Long id) super "Could not find user with id " "." When an is thrown, this extra tidbit of Spring MVC configuration will be used to render an . UserNotFoundException HTTP 404 response src/main/java/com/usersapi/endpoints/detail/UserNotFoundAdvice.java { (UserNotFoundException.class) (HttpStatus.NOT_FOUND) { ex.getMessage(); } } @ControllerAdvice public class UserNotFoundAdvice @ResponseBody @ExceptionHandler @ResponseStatus String userNotFoundHandler (UserNotFoundException ex) return : Intercepts our exception. @ControllerAdvice : Configures the advice to only respond if an is thrown. @ExceptionHandler UserNotFoundException : Signals that this advice is rendered straight into the response body. @ResponseBody As for our , if the contact is found, we’ll return it with a . Controller HTTP 200 response src/main/java/com/usersapi/endpoints/detail/DetailUserController.java ( ) { DetailUserService service; (HttpStatus.OK) ResponseEntity<Optional<User>> listUser_whenGetUser( Long id) { ResponseEntity.ok().body(service.listUser(id)); } } @RestController @RequestMapping "/users/{id}" public class DetailUserController @Autowired @GetMapping @ResponseStatus public @PathVariable return : Similar to the annotation, maps our GET request onto our method. @GetMapping @PostMapping : Binds method parameter with the path variable . @PathVariable id /{id} When listing or retrieving all users, no exceptions will need to be handled, so this can be done by simply calling the repository. src/main/java/com/usersapi/endpoints/list/ListUserService.java { UserRepository repository; { repository.findAll(); } } @Service public class ListUserService @Autowired List<User> public listAllUsers () return src/main/java/com/usersapi/endpoints/list/ListUserController.java ( ) { ListUserService service; (HttpStatus.OK) ResponseEntity<List<User>> listAllUsers_whenGetUsers() { ResponseEntity.ok().body(service.listAllUsers()); } } @RestController @RequestMapping "/users" public class ListUserController @Autowired @GetMapping @ResponseStatus public return Updating an existing user with the PUT request When updating an by its , we need to check if it exists the same way we did with the method. user id listUser Next, we’ll find the that needs to be updated and save it with its new parameters. Our class should be something like : user Service src/main/java/com/usersapi/endpoints/update/UpdateUserService.java { UserRepository repository; { Optional<User> userOptional = repository.findById(id); (!userOptional.isPresent()) { UserNotFoundException(id); } { repository.findById(id); repository.save(user); } } } @Service public class UpdateUserService @Autowired User public updateUser (Long id, User user) if throw new else return As for building our it should be pretty straightforward by now, since our Service contains the logic behind our endpoint implementation. Controller All we need to do is pass our soon to be changed and its as parameters, also the annotation to handle the HTTP request. user id @PutMapping src/main/java/com/usersapi/endpoints/update/UpdateUserController.java ( ) { UpdateUserService service; (HttpStatus.OK) { ResponseEntity.ok().body(service.updateUser(id, user)); } } @RestController @RequestMapping "/users/{id}" public class UpdateUserController @Autowired @PutMapping @ResponseStatus ResponseEntity<User> public updateUser_whenPutUser (@RequestBody User user, @PathVariable Long id) return Removing an user from the database with the DELETE request Removing an existing 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 in case it’s found. user id In case it doesn’t exist, we’ll need to throw our method. UserNotFoundException src/main/java/com/usersapi/endpoints/delete/DeleteUserService.java { UserRepository repository; { Optional<User> userOptional = repository.findById(id); (!userOptional.isPresent()) { UserNotFoundException(id); } { repository.deleteById(id); } } } @Service public class DeleteUserService @Autowired public void deleteUser (Long id) if throw new else src/main/java/com/usersapi/endpoints/delete/DeleteUserController.java ( ) { DeleteUserService service; (HttpStatus.NO_CONTENT) { service.deleteUser(id); } } @RestController @RequestMapping "/users/{id}" public class DeleteUserController @Autowired @DeleteMapping @ResponseStatus public void deleteUser_whenDeleteUser (@PathVariable Long id) Endnotes Throughout this tutorial, you have engaged in various steps to build a RESTful API/Web Service with CRUD operations, check out our for more tutorials like this, our next steps with this API will be : website Testing the execution of our endpoints with . Postman Implementing in our API for documentation and quick interface purposes. Swagger Unit testing our endpoints controllers and services with and . JUnit Mockito Integration testing our whole API. Source code Available on our . github page