Spring Boot CRUD Example with H2 Database

In this tutorial, we will learn how to build CRUD REST APIs using Spring Boot, Spring Data JPA, and H2 in-memory database.

Learn Spring boot at https://www.javaguides.net/p/spring-boot-tutorial.html

We’ll first build the REST APIs to create, retrieve, update, and delete a Product, and then test them using postman.

Spring Boot has taken the Spring framework to the next level. It has drastically reduced the configuration and setup time required for spring projects.

You can set up a project with almost zero configuration and start building the things that actually matter to your application.

High-Level Architecture

Add Maven Dependencies


The spring-boot-starter-web starter for building web, including RESTful, applications using Spring MVC. Uses Tomcat as the default embedded container.

The spring-boot-starter-data-jpa starter for using Spring Data JPA with Hibernate.

Configure H2 Database

By default, Spring Boot configures the application to connect to an in-memory store with the username sa and an empty password. However, we can change those parameters by adding the following properties to the application.properties file:
By design, the in-memory database is volatile and data will be lost when we restart the application.
We can change that behavior by using file-based storage. To do this we need to update the spring.datasource.url:
In this example, we will use a default configuration of the H2 database (we don't use the above configuration, the above configuration is just to know more about H2 database configuration with Spring boot). 

Create JPA Entity - Product.java

package net.javaguides.springboot.model;

import java.math.BigDecimal;
import java.util.Date;

import jakarta.persistence.*;

import org.hibernate.annotations.CreationTimestamp;

@Table(name = "products")
public class Product {

    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    @Column(name = "name")
    private String name;

    @Column(name = "description")
    private String description;

    @Column(name = "price")
    private BigDecimal price;

    private Date createdAt;

    private Date updatedAt;

    public long getId() {
        return id;
    public void setId(long id) {
        this.id = id;
    public String getName() {
        return name;
    public void setName(String name) {
        this.name = name;
    public String getDescription() {
        return description;
    public void setDescription(String description) {
        this.description = description;
    public BigDecimal getPrice() {
        return price;
    public void setPrice(BigDecimal price) {
        this.price = price;
    public Date getCreatedAt() {
        return createdAt;
    public void setCreatedAt(Date createdAt) {
        this.createdAt = createdAt;
    public Date getUpdatedAt() {
        return updatedAt;
    public void setUpdatedAt(Date updatedAt) {
        this.updatedAt = updatedAt;
In the above Product entity, the @Entity annotation marks the class as a JPA entity, and the @Table annotation specifies the name of the database table that this entity is mapped to. 

The @Id annotation marks the id field as the primary key of the entity, and the @GeneratedValue annotation specifies that the primary key values will be generated automatically by the database. 

The @Column annotation is used to specify the mapping between entity attributes and database columns. 

Finally, the class has getters and setters for each attribute, which are used by JPA to map the entity data to and from the database.

Create a Spring Data Repository - ProductRepository.java

package net.javaguides.springboot.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import net.javaguides.springboot.model.Product;

public interface ProductRepository extends JpaRepository<Product, Long> {


Create Custom Exception - ResourceNotFoundException.java

package net.javaguides.springboot.exception;

import org.springframework.web.bind.annotation.ResponseStatus;

public class ResourceNotFoundException extends RuntimeException {

    private static final long serialVersionUID = 1 L;

    public ResourceNotFoundException(String message) {

    public ResourceNotFoundException(String message, Throwable throwable) {
        super(message, throwable);

Service Layer (uses repository)

The service layer is optional – still recommended to perform additional business logic if any. Generally, we will connect with the repository here for crud operations. 


package net.javaguides.springboot.service;

import java.util.List;

import net.javaguides.springboot.model.Product;

public interface ProductService {
    Product createProduct(Product product);

    Product updateProduct(Product product);

    List < Product > getAllProduct();

    Product getProductById(long productId);

    void deleteProduct(long id);


package net.javaguides.springboot.service;

import java.util.List;
import java.util.Optional;

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

import net.javaguides.springboot.exception.ResourceNotFoundException;
import net.javaguides.springboot.model.Product;
import net.javaguides.springboot.repository.ProductRepository;

public class ProductServiceImpl implements ProductService {

    private ProductRepository productRepository;

    public Product createProduct(Product product) {
        return productRepository.save(product);

    public Product updateProduct(Product product) {
        Optional < Product > productDb = this.productRepository.findById(product.getId());

        if (productDb.isPresent()) {
            Product productUpdate = productDb.get();
            return productUpdate;
        } else {
            throw new ResourceNotFoundException("Record not found with id : " + product.getId());

    public List < Product > getAllProduct() {
        return this.productRepository.findAll();

    public Product getProductById(long productId) {

        Optional < Product > productDb = this.productRepository.findById(productId);

        if (productDb.isPresent()) {
            return productDb.get();
        } else {
            throw new ResourceNotFoundException("Record not found with id : " + productId);

    public void deleteProduct(long productId) {
        Optional < Product > productDb = this.productRepository.findById(productId);

        if (productDb.isPresent()) {
        } else {
            throw new ResourceNotFoundException("Record not found with id : " + productId);


Spring REST Controller - ProductController

Let's create a CRUD REST API for the Product resource:
package net.javaguides.springboot.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
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.RestController;

import net.javaguides.springboot.model.Product;
import net.javaguides.springboot.service.ProductService;

public class ProductController {

    private ProductService productService;

    public ResponseEntity < List < Product >> getAllProduct() {
        return ResponseEntity.ok().body(productService.getAllProduct());

    public ResponseEntity < Product > getProductById(@PathVariable long id) {
        return ResponseEntity.ok().body(productService.getProductById(id));

    public ResponseEntity < Product > createProduct(@RequestBody Product product) {
        return ResponseEntity.ok().body(this.productService.createProduct(product));

    public ResponseEntity < Product > updateProduct(@PathVariable long id, @RequestBody Product product) {
        return ResponseEntity.ok().body(this.productService.updateProduct(product));

    public HttpStatus deleteProduct(@PathVariable long id) {
        return HttpStatus.OK;
In the above ProductController, we're using the @RestController annotation to indicate that this is a controller that handles HTTP requests and returns data directly to the client. The @RequestMapping annotation specifies the base URL for all requests to this controller.

The ProductController is using the ProductService to perform operations on products. We're injecting this service into the controller using the @Autowired annotation.

Each endpoint is mapped to an HTTP method using the @GetMapping, @PostMapping, @PutMapping, or @DeleteMapping annotations. The @RequestBody annotation indicates that the incoming data is expected to be in JSON format.

Running Spring Boot Application

This spring boot application has an entry point Java class called SpringbootCrudHibernateExampleApplication.java with the public static void main(String[] args) method, which you can run to start the application.
package net.javaguides.springboot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

public class SpringbootCrudHibernateExampleApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootCrudHibernateExampleApplication.class, args);

Download Source Code from GitHub

The source code of this tutorial is available on my GitHub Repository.


  1. why do you use ResponseEntity and HttpStatus? and what's their role within the controller?


Post a Comment

Leave Comment