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:
| Operation | HTTP Method | URL |
|---|---|---|
| Create student | POST | /api/students |
| Get all students | GET | /api/students |
| Get student by ID | GET | /api/students/{id} |
| Update student | PUT | /api/students/{id} |
| Delete student | DELETE | /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
Examples of Array Functions in PHP
Registration Form Using PDO in PHP
Inserting Information from Multiple CheckBox Selection in a Database Table in PHP
- Angular
- ASP.NET
- C
- C#
- C++
- CSS
- Dot Net Framework
- HTML
- IoT
- Java
- JavaScript
- Kotlin
- PHP
- Power Bi
- Python
- Scratch 3.0
- TypeScript
- VB.NET
