Spring Boot

Spring Boot Rest Example – Restful Webservices CRUD example

REST, or REpresentational State Transfer, is an architectural style to build applications by using APIs that allow the server to communicate with clients using the basic HTTP protocols.

Why REST is popular:
1. It allows the separation between the client and the server.
2. It doesn’t rely on a single technology or programming language.
3. You can build the scalable application or even integrate two different applications using REST APIs
4. REST is stateless and uses basic HTTP methods like GET, PUT, POST, and DELETE, etc. for communication between client and server.

Let’s create a simple spring boot rest example.

More Spring Boot Tutorials

  1. Spring Boot Swagger 2 Example

  2. Spring Boot Hello World Example

  3. Lazy Initialization in Spring Boot 2

For this example I will use below technologies and libraries :
Spring boot 2.1.6.RELEASE
Spring Data JPA
Java 1.8
Spring Web
H2 In-memory database
Maven 3.1.1
Spring Boot DevTools
Spring Boot Test

Spring Boot REST Example

To create the Spring Boot REST program, you have to follow below four steps:
1. Create a new project using the Spring Tool Suite or Spring Initializr Project.
2. Add dependencies in the Maven POM file.
3. Add a controller and expose REST APIs.
4. Add a service layer to communicate with Spring Data JPA’s CrudRepository interface.

Create a new Spring Boot Project

Spring boot rest example
Spring boot rest example
 Spring boot rest example dependencies
Spring boot rest example dependencies

As you could see in the above example, I have created a new project spring-boot-rest-example and added dependencies for Spring Data JPA, H2 Database, and Spring Web Starter.

You can verify those dependencies in pom.xml.

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.6.RELEASE</version>
		<relativePath /> <! – lookup parent from repository -->
	</parent>
	<groupId>com.codedelay.rest</groupId>
	<artifactId>spring-boot-rest-example</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>spring-boot-rest-example</name>
	<description>Hello world example project for Spring Boot REST APIs </description>

	<properties>
		<java.version>1.8</java.version>
		<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
	</properties>

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

		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
			<optional>true</optional>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

Maven Dependency Tree

[INFO] com.codedelay.rest:spring-boot-rest-example:jar:0.0.1-SNAPSHOT
[INFO] +- org.springframework.boot:spring-boot-starter-data-jpa:jar:2.1.6.RELEASE:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter-aop:jar:2.1.6.RELEASE:compile
[INFO] |  |  +- org.springframework:spring-aop:jar:5.1.8.RELEASE:compile
[INFO] |  |  \- org.aspectj:aspectjweaver:jar:1.9.4:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter-jdbc:jar:2.1.6.RELEASE:compile
[INFO] |  |  +- com.zaxxer:HikariCP:jar:3.2.0:compile
[INFO] |  |  \- org.springframework:spring-jdbc:jar:5.1.8.RELEASE:compile
[INFO] |  +- javax.transaction:javax.transaction-api:jar:1.3:compile
[INFO] |  +- javax.xml.bind:jaxb-api:jar:2.3.1:compile
[INFO] |  |  \- javax.activation:javax.activation-api:jar:1.2.0:compile
[INFO] |  +- org.hibernate:hibernate-core:jar:5.3.10.Final:compile
[INFO] |  |  +- org.jboss.logging:jboss-logging:jar:3.3.2.Final:compile
[INFO] |  |  +- javax.persistence:javax.persistence-api:jar:2.2:compile
[INFO] |  |  +- org.javassist:javassist:jar:3.23.2-GA:compile
[INFO] |  |  +- net.bytebuddy:byte-buddy:jar:1.9.13:compile
[INFO] |  |  +- antlr:antlr:jar:2.7.7:compile
[INFO] |  |  +- org.jboss:jandex:jar:2.0.5.Final:compile
[INFO] |  |  +- com.fasterxml:classmate:jar:1.4.0:compile
[INFO] |  |  +- org.dom4j:dom4j:jar:2.1.1:compile
[INFO] |  |  \- org.hibernate.common:hibernate-commons-annotations:jar:5.0.4.Final:compile
[INFO] |  +- org.springframework.data:spring-data-jpa:jar:2.1.9.RELEASE:compile
[INFO] |  |  +- org.springframework.data:spring-data-commons:jar:2.1.9.RELEASE:compile
[INFO] |  |  +- org.springframework:spring-orm:jar:5.1.8.RELEASE:compile
[INFO] |  |  +- org.springframework:spring-context:jar:5.1.8.RELEASE:compile
[INFO] |  |  +- org.springframework:spring-tx:jar:5.1.8.RELEASE:compile
[INFO] |  |  +- org.springframework:spring-beans:jar:5.1.8.RELEASE:compile
[INFO] |  |  \- org.slf4j:slf4j-api:jar:1.7.26:compile
[INFO] |  \- org.springframework:spring-aspects:jar:5.1.8.RELEASE:compile
[INFO] +- org.springframework.boot:spring-boot-starter-web:jar:2.1.6.RELEASE:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter:jar:2.1.6.RELEASE:compile
[INFO] |  |  +- org.springframework.boot:spring-boot-starter-logging:jar:2.1.6.RELEASE:compile
[INFO] |  |  |  +- ch.qos.logback:logback-classic:jar:1.2.3:compile
[INFO] |  |  |  |  \- ch.qos.logback:logback-core:jar:1.2.3:compile
[INFO] |  |  |  +- org.apache.logging.log4j:log4j-to-slf4j:jar:2.11.2:compile
[INFO] |  |  |  |  \- org.apache.logging.log4j:log4j-api:jar:2.11.2:compile
[INFO] |  |  |  \- org.slf4j:jul-to-slf4j:jar:1.7.26:compile
[INFO] |  |  +- javax.annotation:javax.annotation-api:jar:1.3.2:compile
[INFO] |  |  \- org.yaml:snakeyaml:jar:1.23:runtime
[INFO] |  +- org.springframework.boot:spring-boot-starter-json:jar:2.1.6.RELEASE:compile
[INFO] |  |  +- com.fasterxml.jackson.core:jackson-databind:jar:2.9.9:compile
[INFO] |  |  |  +- com.fasterxml.jackson.core:jackson-annotations:jar:2.9.0:compile
[INFO] |  |  |  \- com.fasterxml.jackson.core:jackson-core:jar:2.9.9:compile
[INFO] |  |  +- com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:2.9.9:compile
[INFO] |  |  +- com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:2.9.9:compile
[INFO] |  |  \- com.fasterxml.jackson.module:jackson-module-parameter-names:jar:2.9.9:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter-tomcat:jar:2.1.6.RELEASE:compile
[INFO] |  |  +- org.apache.tomcat.embed:tomcat-embed-core:jar:9.0.21:compile
[INFO] |  |  +- org.apache.tomcat.embed:tomcat-embed-el:jar:9.0.21:compile
[INFO] |  |  \- org.apache.tomcat.embed:tomcat-embed-websocket:jar:9.0.21:compile
[INFO] |  +- org.hibernate.validator:hibernate-validator:jar:6.0.17.Final:compile
[INFO] |  |  \- javax.validation:validation-api:jar:2.0.1.Final:compile
[INFO] |  +- org.springframework:spring-web:jar:5.1.8.RELEASE:compile
[INFO] |  \- org.springframework:spring-webmvc:jar:5.1.8.RELEASE:compile
[INFO] |     \- org.springframework:spring-expression:jar:5.1.8.RELEASE:compile
[INFO] +- com.h2database:h2:jar:1.4.199:runtime
[INFO] +- org.springframework.boot:spring-boot-starter-test:jar:2.1.6.RELEASE:test
[INFO] |  +- org.springframework.boot:spring-boot-test:jar:2.1.6.RELEASE:test
[INFO] |  +- org.springframework.boot:spring-boot-test-autoconfigure:jar:2.1.6.RELEASE:test
[INFO] |  +- com.jayway.jsonpath:json-path:jar:2.4.0:test
[INFO] |  |  \- net.minidev:json-smart:jar:2.3:test
[INFO] |  |     \- net.minidev:accessors-smart:jar:1.2:test
[INFO] |  |        \- org.ow2.asm:asm:jar:5.0.4:test
[INFO] |  +- junit:junit:jar:4.12:test
[INFO] |  +- org.assertj:assertj-core:jar:3.11.1:test
[INFO] |  +- org.mockito:mockito-core:jar:2.23.4:test
[INFO] |  |  +- net.bytebuddy:byte-buddy-agent:jar:1.9.13:test
[INFO] |  |  \- org.objenesis:objenesis:jar:2.6:test
[INFO] |  +- org.hamcrest:hamcrest-core:jar:1.3:test
[INFO] |  +- org.hamcrest:hamcrest-library:jar:1.3:test
[INFO] |  +- org.skyscreamer:jsonassert:jar:1.5.0:test
[INFO] |  |  \- com.vaadin.external.google:android-json:jar:0.0.20131108.vaadin1:test
[INFO] |  +- org.springframework:spring-core:jar:5.1.8.RELEASE:compile
[INFO] |  |  \- org.springframework:spring-jcl:jar:5.1.8.RELEASE:compile
[INFO] |  +- org.springframework:spring-test:jar:5.1.8.RELEASE:test
[INFO] |  \- org.xmlunit:xmlunit-core:jar:2.6.2:test
[INFO] \- org.springframework.boot:spring-boot-devtools:jar:2.1.6.RELEASE:runtime (optional)
[INFO]    +- org.springframework.boot:spring-boot:jar:2.1.6.RELEASE:compile
[INFO]    \- org.springframework.boot:spring-boot-autoconfigure:jar:2.1.6.RELEASE:compile

Controller to expose REST APIs

For this tutorial, we will CRUD APIs for User Management System.

By using these APIs we can add, retrieve, update, or delete the user details from the database.

To create a User Management System, let’s focus on writing 5 basic APIs

HTTP GET /getAll will return a list of all user details.
HTTP GET /find/{id} will return a user’s detail by an id.
HTTP POST /add is to add a user into the database.
HTTP PUT /update/{id} can be used to update a user based on an id.
HTTP DELETE /delete/{id} can be used to delete a user from the database.

Let’s create a controller (UserController) to expose REST endpoints.

package com.codedelay.rest.controller;

import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import com.codedelay.rest.entity.User;
import com.codedelay.rest.service.UserManageService;

@RestController
@RequestMapping("/api/user")
public class UserController {
	@Autowired
	private UserManageService mService;
	
	@GetMapping("/getAll")
	public Iterable<User> getAllUsers() {
		return mService.getAllUsers();
	}
	
	@PostMapping("/add")
	@ResponseStatus(HttpStatus.CREATED)
	public User addUser(@Valid @RequestBody User user) {
		return mService.addUser(user);
	}
	
	@GetMapping("/find/{id}")
	public User findUserById(@PathVariable("id") int id) {
		return mService.findUserById(id);
	}
	
	@PutMapping("/update/{id}")
	public User addOrUpdateUserById(@RequestBody User user, @PathVariable("id") int id) {
		return mService.addOrUpdateUserById(user, id);
	}
	
	@DeleteMapping("/delete/{id}")
	public void deleteUser(@PathVariable("id") int id) {
		mService.deleteUser(id);
	}
}

Service layer

The service layer acts as an intermediate layer between a controller and a repository class.

package com.codedelay.rest.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.codedelay.rest.dao.UserRepository;
import com.codedelay.rest.entity.User;
import com.codedelay.rest.exception.UserNotFoundException;

@Service
public class UserManageService {
	@Autowired
	private UserRepository mRepository;

	public Iterable<User> getAllUsers() {
		return mRepository.findAll();
	}

	public User addUser(User user) {
		return mRepository.save(user);
	}

	public User findUserById(int id) {
		return mRepository.findById(id).get();
	}

	public User addOrUpdateUserById(User user, int id) {
		return mRepository.findById(id).map(x -> {
			x.setUser_name(user.getUser_name());
			x.setPassword(user.getPassword());
			return mRepository.save(x);
		}).orElseGet(() -> {
			user.setUser_id(id);
			return mRepository.save(user);
		});
	}

	public void deleteUser(int id) {
		mRepository.deleteById(id);
	}
}

Entity class

Now, let’s create an entity class which is a simple POJO class annotated with JPA annotations.

Entity class also represents a table in the database.

In our case, we will create a User class inside com.codedelay.rest.entity package.

package com.codedelay.rest.entity;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "user_details")
public class User {
	
	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private int user_id;
	
	@Column(unique = true, nullable = false, length = 10)
	private String user_name;
	
	@Column(nullable = false, length = 12, updatable = true)
	private String password;

	public int getUser_id() {
		return user_id;
	}

	public void setUser_id(int user_id) {
		this.user_id = user_id;
	}

	public String getUser_name() {
		return user_name;
	}

	public void setUser_name(String user_name) {
		this.user_name = user_name;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

}

Write the Repository Interface.

package com.codedelay.rest.dao;

import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

import com.codedelay.rest.entity.User;

public interface UserRepository extends CrudRepository<User, Integer> {
}

UserRepository interface extends CrudRepository.

CrudRepository is a magical interface from Spring Data JPA.

It allows writing simple CRUD functions without writing a single line of code.

Exception handling in Spring Boot REST

A good REST API also covers exception scenarios.

Let discuss one simple scenario.

What will happen if HTTP GET /find/{id} doesn’t find a particular user in the database?

It should throw an exception. Isn’t it?

Let’s add one more class UserNotFoundException class in com.codedelay.rest.exception package.

package com.codedelay.rest.exception;

public class UserNotFoundException extends RuntimeException  {
	public UserNotFoundException(int id) {
		super("User id not found : " + id);
	}
}

Now add the Service class to throw the UserNotFoundException exception if there are no user details available in the database for that particular user id.

public User findUserById(int id) {
		return mRepository.findById(id).orElseThrow(() -> new UserNotFoundException(id));
	}

It is not sufficient to throw java exception.

We have to return some HTTP error when UserNotFoundException occurs.

For this, let’s create a class GlobalExceptionHandler which will return HttpStatus.NOT_FOUND error when UserNotFoundException occurs.

package com.codedelay.rest.exception;

import java.io.IOException;

import javax.servlet.http.HttpServletResponse;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class GlobalExceptionHandler {
	@ExceptionHandler(UserNotFoundException.class)
    public void handleUserNotFoundError(HttpServletResponse response) throws IOException {
        response.sendError(HttpStatus.NOT_FOUND.value());
    }
}

Conclusion

In the above example, we have seen how to create a simple spring boot rest example.

To test this code, let’s add a few dummy data.

package com.codedelay.rest;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import com.codedelay.rest.dao.UserRepository;
import com.codedelay.rest.entity.User;
import com.codedelay.rest.service.UserManageService;

@SpringBootApplication
public class SpringBootRestDemoApp{
	public static void main(String[] args) {
		SpringApplication.run(SpringBootRestDemoApp.class, args);
	}

	
	@Bean
    CommandLineRunner initDatabase(UserRepository repository) {
		User user1 = new User();
		user1.setUser_name("test");
		user1.setPassword("test123");
		
		User user2 = new User();
		user2.setUser_name("user");
		user2.setPassword("user456");
		
		User user3 = new User();
		user3.setUser_name("admin");
		user3.setPassword("root123");
		
        return args -> {
            repository.save(user1);
            repository.save(user2);
            repository.save(user3);
        };
    }
}

Noe test in PostMan to see if the above APIs works or not.

/getAll REST API
/getAll REST API

Click to rate this post!
[Total: 0 Average: 0]

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.