Spring Boot 4 + Hibernate 7 CRUD Application: Step-by-Step Tutorial

Spring Boot 4 and Hibernate 7 provide a powerful combination for developing modern database-driven Java applications. Spring Boot simplifies application configuration, while Hibernate handles object-relational mapping between Java objects and relational database tables.

In this Spring Boot 4 + Hibernate 7 CRUD tutorial, we will build a complete CRUD application step by step using Spring Boot 4, Spring Data JPA, Hibernate 7, and MySQL.

CRUD represents four fundamental database operations:

  • C – Create
  • R – Read
  • U – Update
  • D – Delete

By the end of this tutorial, you will have a working REST API capable of adding, retrieving, updating, and deleting student records from a MySQL database.


Technologies Used

We will use:

Java 17+
Spring Boot 4
Spring Data JPA
Hibernate ORM 7
MySQL
Maven
Spring MVC
REST API

You can use IntelliJ IDEA, Eclipse, Spring Tools, or another Java IDE.


Understanding the Application

Our application will manage student information.

Each student will contain:

ID
Name
Email
Course

The application architecture will be:

Client
   ↓
REST Controller
   ↓
Service
   ↓
Repository
   ↓
Spring Data JPA
   ↓
Hibernate 7
   ↓
MySQL Database

This layered architecture separates different responsibilities and makes the application easier to maintain.


What is Hibernate?

Hibernate is an Object-Relational Mapping or ORM framework for Java.

Normally, a Java application and a relational database represent information differently.

Java uses objects:

Student student = new Student();
student.setName("Juhi");
student.setCourse("MCA");

A relational database uses tables:

students
--------------------------------
id | name | email | course
--------------------------------
1  | Juhi | ...   | MCA

Hibernate maps Java objects to relational database records.

Conceptually:

Java Object
     ↓
Hibernate ORM
     ↓
SQL
     ↓
Database Table

This reduces the amount of SQL and JDBC code developers need to write manually.


What is Spring Data JPA?

Spring Data JPA provides a higher-level abstraction for accessing relational databases using Jakarta Persistence.

Instead of manually implementing common operations such as:

INSERT
SELECT
UPDATE
DELETE

we can create a repository interface.

For example:

public interface StudentRepository
        extends JpaRepository<Student, Long> {
}

This single interface gives us methods such as:

save()
findAll()
findById()
deleteById()

Hibernate acts as the ORM provider underneath this persistence layer.


Step 1: Create the Spring Boot Project

Open Spring Initializr.

Create a project with:

Project: Maven
Language: Java
Spring Boot: 4.x
Group: com.programmingempire
Artifact: studentcrud
Name: studentcrud
Packaging: Jar
Java: 17 or later

Step 2: Add Dependencies

Add the following dependencies:

Spring Web
Spring Data JPA
MySQL Driver

You may optionally add:

Spring Boot DevTools

Generate and download the project.

Extract it and open it in your IDE.


Step 3: Examine pom.xml

The important dependencies will be similar to:

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

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

<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
</dependency>

Do not manually add a random Hibernate version to the project.

Spring Boot manages compatible dependency versions for the selected Spring Boot release.

Spring Data JPA will use the Hibernate ORM version managed by Spring Boot.


Step 4: Create the MySQL Database

Start MySQL.

Create a database:

CREATE DATABASE studentdb;

Verify it:

SHOW DATABASES;

You should see:

studentdb

We do not need to manually create the student table because Hibernate can generate it from our entity definition during development.


Step 5: Configure the Database Connection

Open:

src/main/resources/application.properties

Add:

spring.datasource.url=jdbc:mysql://localhost:3306/studentdb
spring.datasource.username=root
spring.datasource.password=your_password

spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

Replace:

your_password

with your MySQL password.

For example:

spring.datasource.password=root123

Never publish a real production database password in source code or a public repository.


Understanding ddl-auto

We used:

spring.jpa.hibernate.ddl-auto=update

During development, this allows Hibernate to update the database schema according to entity definitions.

Other commonly encountered values include:

none
validate
update
create
create-drop

update is convenient for learning and local development.

For production systems, database migrations should normally be managed deliberately using tools such as Flyway or Liquibase instead of relying on automatic schema changes.


Step 6: Create the Package Structure

Inside:

src/main/java/com/programmingempire/studentcrud

create:

controller
entity
repository
service
exception

Our structure becomes:

com.programmingempire.studentcrud
│
├── controller
│   └── StudentController.java
│
├── entity
│   └── Student.java
│
├── repository
│   └── StudentRepository.java
│
├── service
│   └── StudentService.java
│
├── exception
│   └── StudentNotFoundException.java
│
└── StudentcrudApplication.java

Step 7: Create the Student Entity

Create:

Student.java

inside the entity package.

package com.programmingempire.studentcrud.entity;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

@Entity
@Table(name = "students")
public class Student {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private String email;
    private String course;

    public Student() {
    }

    public Student(String name, String email, String course) {
        this.name = name;
        this.email = email;
        this.course = course;
    }

    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 getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getCourse() {
        return course;
    }

    public void setCourse(String course) {
        this.course = course;
    }
}

Understanding @Entity

The annotation:

@Entity

tells the persistence framework that this Java class represents a persistent entity.

Hibernate can map instances of this class to rows in a database table.


Understanding @Table

We specified:

@Table(name = "students")

Therefore, the entity is mapped to a database table named:

students

Understanding @Id

Every entity requires an identifier.

@Id
private Long id;

marks id as the primary-key property.


Understanding @GeneratedValue

We use:

@GeneratedValue(strategy = GenerationType.IDENTITY)

to indicate that the database generates the identifier using an identity-style strategy.

Therefore, we do not manually assign an ID when creating a new student.


Step 8: Create the Repository

Create:

StudentRepository.java

inside the repository package.

package com.programmingempire.studentcrud.repository;

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

import com.programmingempire.studentcrud.entity.Student;

public interface StudentRepository
        extends JpaRepository<Student, Long> {
}

That is all we need for the basic CRUD repository.


How Does JpaRepository Work?

JpaRepository already provides common operations.

Examples include:

save()
findAll()
findById()
delete()
deleteById()
count()
existsById()

Therefore, we don’t have to manually implement basic SQL statements.

Conceptually:

StudentRepository
       ↓
JpaRepository
       ↓
Spring Data JPA
       ↓
Hibernate
       ↓
SQL
       ↓
MySQL

Step 9: Create a Custom Exception

Create:

StudentNotFoundException.java

inside exception.

package com.programmingempire.studentcrud.exception;

public class StudentNotFoundException extends RuntimeException {

    public StudentNotFoundException(Long id) {
        super("Student not found with id: " + id);
    }
}

We will use this when a requested student does not exist.


Step 10: Create the Service Layer

Create:

StudentService.java

inside service.

package com.programmingempire.studentcrud.service;

import java.util.List;

import org.springframework.stereotype.Service;

import com.programmingempire.studentcrud.entity.Student;
import com.programmingempire.studentcrud.exception.StudentNotFoundException;
import com.programmingempire.studentcrud.repository.StudentRepository;

@Service
public class StudentService {

    private final StudentRepository repository;

    public StudentService(StudentRepository repository) {
        this.repository = repository;
    }

    public List<Student> getAllStudents() {
        return repository.findAll();
    }

    public Student getStudentById(Long id) {
        return repository.findById(id)
                .orElseThrow(() ->
                        new StudentNotFoundException(id));
    }

    public Student createStudent(Student student) {
        return repository.save(student);
    }

    public Student updateStudent(Long id, Student details) {

        Student student = getStudentById(id);

        student.setName(details.getName());
        student.setEmail(details.getEmail());
        student.setCourse(details.getCourse());

        return repository.save(student);
    }

    public void deleteStudent(Long id) {

        Student student = getStudentById(id);

        repository.delete(student);
    }
}

Why Use a Service Layer?

Technically, a controller could call the repository directly.

However, a service layer provides a better architecture:

Controller
    ↓
Service
    ↓
Repository

Business logic belongs in the service rather than being mixed with HTTP request-handling code.

This becomes especially valuable as an application grows.


Why Constructor Injection?

Notice:

private final StudentRepository repository;

public StudentService(StudentRepository repository) {
    this.repository = repository;
}

This is constructor injection.

Spring detects the required dependency and provides an implementation of StudentRepository.

Constructor injection is generally preferred because dependencies are explicit and can be declared final.


Step 11: Create the REST Controller

Create:

StudentController.java

inside controller.

package com.programmingempire.studentcrud.controller;

import java.net.URI;
import java.util.List;

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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.programmingempire.studentcrud.entity.Student;
import com.programmingempire.studentcrud.service.StudentService;

@RestController
@RequestMapping("/api/students")
public class StudentController {

    private final StudentService service;

    public StudentController(StudentService service) {
        this.service = service;
    }

    @GetMapping
    public List<Student> getAllStudents() {
        return service.getAllStudents();
    }

    @GetMapping("/{id}")
    public Student getStudent(@PathVariable Long id) {
        return service.getStudentById(id);
    }

    @PostMapping
    public ResponseEntity<Student> createStudent(
            @RequestBody Student student) {

        Student savedStudent =
                service.createStudent(student);

        URI location = URI.create(
                "/api/students/" + savedStudent.getId());

        return ResponseEntity
                .created(location)
                .body(savedStudent);
    }

    @PutMapping("/{id}")
    public Student updateStudent(
            @PathVariable Long id,
            @RequestBody Student student) {

        return service.updateStudent(id, student);
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteStudent(
            @PathVariable Long id) {

        service.deleteStudent(id);

        return ResponseEntity.noContent().build();
    }
}

Our CRUD API is now ready.


Understanding the REST Endpoints

The application exposes:

OperationHTTP MethodURL
Create studentPOST/api/students
Get all studentsGET/api/students
Get student by IDGET/api/students/{id}
Update studentPUT/api/students/{id}
Delete studentDELETE/api/students/{id}

This follows common REST API conventions.


Step 12: Run the Application

Open:

StudentcrudApplication.java

It should look similar to:

package com.programmingempire.studentcrud;

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

@SpringBootApplication
public class StudentcrudApplication {

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

Run the application.

You should see messages indicating that the embedded web server has started.

By default, the application is available at:

http://localhost:8080

Step 13: Create a Student

Send a POST request to:

http://localhost:8080/api/students

Request body:

{
  "name": "Juhi",
  "email": "juhi@example.com",
  "course": "MCA"
}

The response will look similar to:

{
  "id": 1,
  "name": "Juhi",
  "email": "juhi@example.com",
  "course": "MCA"
}

Hibernate creates the corresponding database record.

Conceptually:

POST Request
     ↓
StudentController
     ↓
StudentService
     ↓
StudentRepository
     ↓
Hibernate
     ↓
INSERT
     ↓
MySQL

Step 14: Add More Students

Create another record:

{
  "name": "Cherry",
  "email": "cherry@example.com",
  "course": "BCA"
}

And another:

{
  "name": "Kanchan",
  "email": "kanchan@example.com",
  "course": "MCA"
}

Now our database contains multiple records.


Step 15: Read All Students

Send:

GET http://localhost:8080/api/students

Possible response:

[
  {
    "id": 1,
    "name": "Juhi",
    "email": "juhi@example.com",
    "course": "MCA"
  },
  {
    "id": 2,
    "name": "Cherry",
    "email": "cherry@example.com",
    "course": "BCA"
  },
  {
    "id": 3,
    "name": "Kanchan",
    "email": "kanchan@example.com",
    "course": "MCA"
  }
]

This is the Read part of CRUD.


Step 16: Read a Student by ID

Send:

GET http://localhost:8080/api/students/1

Response:

{
  "id": 1,
  "name": "Juhi",
  "email": "juhi@example.com",
  "course": "MCA"
}

Spring extracts 1 from the URL using:

@PathVariable Long id

Step 17: Update a Student

Suppose student 1 changes course.

Send:

PUT http://localhost:8080/api/students/1

Request body:

{
  "name": "Juhi",
  "email": "juhi@example.com",
  "course": "MCA - Cloud Computing"
}

The service retrieves the existing entity, updates its fields and saves it.

Response:

{
  "id": 1,
  "name": "Juhi",
  "email": "juhi@example.com",
  "course": "MCA - Cloud Computing"
}

This is the Update operation.


Step 18: Delete a Student

Send:

DELETE http://localhost:8080/api/students/2

If the operation succeeds, the application returns:

204 No Content

Student 2 is removed from the database.

This completes the four CRUD operations.


Step 19: Verify the Database

Open MySQL and execute:

USE studentdb;

SELECT * FROM students;

You should see the records created through the REST API.

This demonstrates that the Java entities are being persisted in MySQL.


Step 20: Add Better Exception Handling

Our custom exception currently indicates when a student does not exist, but we should convert it into an appropriate HTTP response.

Create:

GlobalExceptionHandler.java

inside the exception package.

package com.programmingempire.studentcrud.exception;

import java.util.Map;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(StudentNotFoundException.class)
    public ResponseEntity<Map<String, String>>
            handleStudentNotFound(
                    StudentNotFoundException ex) {

        return ResponseEntity
                .status(HttpStatus.NOT_FOUND)
                .body(Map.of("message", ex.getMessage()));
    }
}

Now request:

GET http://localhost:8080/api/students/999

Instead of an inappropriate successful response, the API returns:

404 Not Found

with JSON similar to:

{
  "message": "Student not found with id: 999"
}

Step 21: Create a Custom Repository Query

Spring Data JPA can automatically generate queries from method names.

Suppose we want all students enrolled in a particular course.

Add to StudentRepository:

List<Student> findByCourse(String course);

The complete repository becomes:

package com.programmingempire.studentcrud.repository;

import java.util.List;

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

import com.programmingempire.studentcrud.entity.Student;

public interface StudentRepository
        extends JpaRepository<Student, Long> {

    List<Student> findByCourse(String course);
}

Spring Data interprets:

findByCourse

and generates the required database query.

No SQL is necessary.


Step 22: Add the Service Method

Add:

public List<Student> getStudentsByCourse(
        String course) {

    return repository.findByCourse(course);
}

Step 23: Add the Search Endpoint

Add to the controller:

@GetMapping("/course/{course}")
public List<Student> getByCourse(
        @PathVariable String course) {

    return service.getStudentsByCourse(course);
}

Now request:

GET http://localhost:8080/api/students/course/MCA

The application returns students whose course is MCA.


Hibernate SQL

Because we configured:

spring.jpa.show-sql=true

you can observe SQL generated by Hibernate in the console.

For example, when a student is saved, Hibernate may generate an insert statement.

When students are retrieved, Hibernate generates a select.

This demonstrates an important concept:

Java Repository Operation
          ↓
Spring Data JPA
          ↓
Hibernate ORM
          ↓
Generated SQL
          ↓
MySQL

We work primarily with Java objects while Hibernate handles much of the SQL interaction.


CRUD Operation Mapping

The complete mapping is:

CREATE
POST /api/students
      ↓
repository.save()

READ
GET /api/students
      ↓
repository.findAll()

READ ONE
GET /api/students/{id}
      ↓
repository.findById()

UPDATE
PUT /api/students/{id}
      ↓
repository.save()

DELETE
DELETE /api/students/{id}
      ↓
repository.delete()

Spring Boot 4 + Hibernate 7 Application Flow

Our complete architecture is:

                 CLIENT
                    ↓
              HTTP REQUEST
                    ↓
          StudentController
                    ↓
            StudentService
                    ↓
          StudentRepository
                    ↓
            Spring Data JPA
                    ↓
              Hibernate 7
                    ↓
                   SQL
                    ↓
                  MySQL
                    ↓
              DATABASE DATA

The response travels back through the same application layers and is serialized into JSON for the client.


Why Don’t We Write SQL for Basic CRUD?

Without an ORM framework, database operations frequently involve code for:

Connection
PreparedStatement
SQL
ResultSet
Object conversion
Exception handling
Resource management

With Spring Data JPA and Hibernate, much of this infrastructure is handled automatically.

Instead of writing an SQL statement manually, we can often write:

repository.save(student);

or:

repository.findAll();

This significantly reduces repetitive persistence code.


Hibernate 7 and Spring Boot 4

Spring Boot manages the supported Hibernate version through its dependency-management system.

Therefore, when using:

Spring Boot 4
+
Spring Data JPA

developers generally should not manually force a Hibernate version unless they have a specific compatibility requirement and understand the consequences.

Allowing Spring Boot to manage dependency versions reduces compatibility problems.


Important Development vs Production Difference

For this tutorial we used:

spring.jpa.hibernate.ddl-auto=update

This is convenient for learning.

A production application should normally use controlled database migrations.

For example:

Application
     ↓
Flyway / Liquibase
     ↓
Versioned Migration Scripts
     ↓
Database

This gives teams explicit control over schema changes.


Common Errors

MySQL Connection Error

Check:

spring.datasource.url
spring.datasource.username
spring.datasource.password

Also verify that MySQL is running.


Unknown Database

If you receive an error indicating that studentdb does not exist, run:

CREATE DATABASE studentdb;

Port 8080 Already in Use

Change the port:

server.port=9090

Then access:

http://localhost:9090/api/students

Table Not Created

Verify:

spring.jpa.hibernate.ddl-auto=update

Also check the console for persistence or database errors.


Repository Not Detected

Keep the application’s packages underneath the package containing the main @SpringBootApplication class.

For example:

com.programmingempire.studentcrud
    ├── controller
    ├── service
    ├── repository
    └── entity

This allows Spring’s default component scanning to discover the components.


Advantages of Spring Boot 4 + Hibernate 7

This combination provides several advantages:

  • Reduced boilerplate database code
  • Automatic object-relational mapping
  • Repository abstraction
  • Transaction-management integration
  • Modern Jakarta Persistence support
  • Easy REST API development
  • Dependency management through Spring Boot
  • Support for enterprise application architectures
  • Integration with validation, security and cloud technologies

What Should We Add Next?

Our application is intentionally simple so that beginners can understand the complete CRUD flow.

A production-ready version could add:

Input Validation
DTOs
Pagination
Sorting
Filtering
Spring Security
JWT Authentication
OpenAPI Documentation
Database Migrations
Docker
Automated Tests
Logging
Observability

Each of these can be added gradually without changing the fundamental architecture.


Frequently Asked Questions

Does Spring Boot 4 use Hibernate 7?

Spring Boot 4’s Spring Data JPA stack uses the Hibernate ORM 7 generation managed through Spring Boot’s dependency management. The exact Hibernate maintenance version depends on the specific Spring Boot 4.x release.

Do I need to configure Hibernate manually?

Usually, no. Adding Spring Data JPA and configuring the datasource allows Spring Boot to auto-configure the persistence infrastructure.

What is the difference between JPA and Hibernate?

Jakarta Persistence defines the standard persistence API and mapping model. Hibernate ORM is a widely used implementation of that specification.

What is Spring Data JPA?

Spring Data JPA provides repository abstractions on top of Jakarta Persistence, reducing the amount of data-access code developers need to write.

Can Hibernate create database tables automatically?

Yes, Hibernate can create or update schemas according to configuration. This is convenient during development, although production applications normally use controlled migration tools.

Can I use PostgreSQL instead of MySQL?

Yes. Change the database driver and datasource configuration. The entity, service, controller, and much of the repository code can remain unchanged.

Is SQL knowledge still necessary when using Hibernate?

Yes. Hibernate reduces repetitive SQL coding, but understanding SQL, relational databases, indexes, joins, transactions, and query performance remains important for professional development.


Conclusion

In this tutorial, we developed a complete Spring Boot 4 + Hibernate 7 CRUD application using Spring Data JPA and MySQL.

We implemented all four fundamental operations:

CREATE → POST
READ   → GET
UPDATE → PUT
DELETE → DELETE

We also learned how the application’s layers work together:

REST Controller
      ↓
Service
      ↓
Repository
      ↓
Spring Data JPA
      ↓
Hibernate 7
      ↓
MySQL

The most important lesson is that Spring Boot, Spring Data JPA, and Hibernate solve different parts of the application.

Spring Boot simplifies application configuration and startup.

Spring Data JPA provides convenient repository abstractions.

Hibernate ORM maps Java entities to relational database data and performs the underlying persistence operations.

Once this basic architecture is understood, the same approach can be extended to build larger REST APIs, enterprise applications, microservices, and cloud-based Java systems.

The logical next step is to enhance this application with validation, DTOs, pagination, exception handling, and Spring Security.


Further Reading

Spring Boot 4 Tutorial for Beginners: Build Your First Application (2026)

Spring Boot 4 vs Spring Boot 3: What Changed?

Spring Framework 7: New Features Developers Should Know in 2026

Spring Boot 4 REST API with MySQL and Hibernate 7

What Is Claude AI? A Beginner’s Guide (2026)

Claude Code vs ChatGPT Codex: A Practical Comparison for Developers (2026)

What is MCP? A Beginner’s Guide with Python Examples

MCP vs REST API – Key Differences

Build Your First MCP Server in Python

Create an MCP Server for MySQL Database

Integrate OpenAI Agents with MCP

Security Risks in MCP Servers and How to Mitigate Them

What is n8n? A Beginner-Friendly Guide to Workflow Automation

How to Automatically Publish Blog Posts Using n8n (Step-by-Step Guide)

Top 10 Real-World Use Cases of n8n for Developers

Introduction to Django Framework and its Features

Django Practice Exercise

Examples of Array Functions in PHP

Basic Programs in PHP

Registration Form Using PDO in PHP

Inserting Information from Multiple CheckBox Selection in a Database Table in PHP

programmingempire

princites.com

Leave a Reply

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