Spring Boot - Transaction Management Using @Transactional Annotation
Last Updated :
20 Aug, 2025
In enterprise applications, transactions ensure data consistency and integrity. A transaction is a sequence of operations performed as a single logical unit of work. Either all operations within a transaction succeed (commit) or none of them do (rollback).
Spring Boot simplifies transaction management by using the @Transactional annotation, which is built on top of Spring’s declarative transaction management. It frees developers from writing boilerplate code for managing transactions manually.
What is Transaction Management?
Transaction management is the process of coordinating database operations to follow the ACID properties:
- Atomicity: All or nothing.
- Consistency: Database remains valid before and after transaction.
- Isolation: Concurrent transactions do not affect each other.
- Durability: Changes persist even after system failures.
Example: In a banking system, if you transfer money:
- Debit amount from one account.
- Credit amount to another account.
Both operations should succeed or fail together.
@Transactional Annotation
The @Transactional annotation:
- Automatically starts a transaction when a method is called.
- Commits the transaction if the method completes successfully.
- Rolls back the transaction if a runtime exception occurs.
- Reduces boilerplate transaction-handling code.
Note: If you use spring-boot-starter-data-jpa, Spring Boot auto-configures transaction management, so @EnableTransactionManagement isn’t required. Add it only if you’re not using JPA starter or need custom transaction management.
In this example, we will create an application to store user information along with his address information and will use spring transaction management to resolve the transaction break problem.
Step-by-Step Implementation of Transaction Management
Step 1: Create A Spring Boot Project
Create a Spring Boot project using Spring Initializr.
Select the following:
- Project: Maven
- Spring Boot: 3.x
- Dependencies: Spring Web, Spring Data JPA, MySQL Driver, Lombok
Step 2: Add Dependencies
We will add the required dependencies for our spring boot application.
.jpg)
Now, we will configure the database in our application. We will be using the following configurations and add them to our application.properties file.
server.port=9090
# Database Configuration
spring.datasource.url=jdbc:mysql://localhost:3306/employee_db
spring.datasource.username=root
spring.datasource.password=root
# JPA Configuration
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL57Dialect
Note: Please add your database username & password along with the database path.
Step 4: Create Model Classes
We will create two model classes: Employee and Address with a @OneToOne relationship.
Employee.java
Java
package com.example.transactionmanagement.model;
import jakarta.persistence.*;
import lombok.*;
@Entity
@Table(name = "emp_info")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
}
Address.java
Java
package com.example.transactionmanagement.model;
import jakarta.persistence.*;
import lombok.*;
@Entity
@Table(name = "add_info")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Address {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String address;
// One-to-one mapping: one employee has one address
@OneToOne
private Employee employee;
}
Step 5: Create Repository Layer
In this step, we will create a Repository Layer. For this, we will be creating EmployeeRepository and AddressRepository and will be extending JpaRepository<T, ID> for performing database-related queries.
EmployeeRepository.java
Java
package com.example.transactionmanagement.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import com.example.transactionmanagement.model.Employee;
public interface EmployeeRepository extends JpaRepository<Employee, Integer> {
}
AddressRepository.java
Java
package com.example.transactionmanagement.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import com.example.transactionmanagement.model.Address;
public interface AddressRepository extends JpaRepository<Address, Long> {
}
Step 6: Create a Service Layer
You can use @Transactional annotation in service layer which will result interacting with the database. In this step, we will create a service layer for our application and add business logic to it. For this, we will be creating two classes EmployeeService and AddressService. In EmployeeService class we are throwing an exception.
EmployeeService.java
Java
package com.example.transactionmanagement.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.example.transactionmanagement.model.Address;
import com.example.transactionmanagement.model.Employee;
import com.example.transactionmanagement.repository.EmployeeRepository;
@Service
public class EmployeeService {
@Autowired
private EmployeeRepository employeeRepository;
@Autowired
private AddressService addressService;
@Transactional(rollbackFor = Exception.class)
public Employee addEmployee(Employee employee) throws Exception {
Employee employeeSavedToDB = this.employeeRepository.save(employee);
// Create address for employee
Address address = new Address();
address.setAddress("Varanasi");
address.setEmployee(employee);
// This may throw an exception intentionally for testing rollback
if (employee.getName().equalsIgnoreCase("error")) {
throw new RuntimeException("Simulated Exception: Forcing rollback!");
}
this.addressService.addAddress(address);
return employeeSavedToDB;
}
}
AddressService.java
Java
package com.example.transactionmanagement.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.example.transactionmanagement.model.Address;
import com.example.transactionmanagement.repository.AddressRepository;
@Service
public class AddressService {
@Autowired
private AddressRepository addressRepository;
public Address addAddress(Address address) {
return this.addressRepository.save(address);
}
}
Step 7: Create Controller
In this step, we will create a controller for our application. For this, we will create a Controller class and add all the mappings to it.
Controller.java
Java
package com.example.transactionmanagement.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import com.example.transactionmanagement.model.Employee;
import com.example.transactionmanagement.service.EmployeeService;
@RestController
@RequestMapping("/api/employee")
public class EmployeeController {
@Autowired
private EmployeeService employeeService;
@PostMapping("/add")
public ResponseEntity<?> saveEmployee(@RequestBody Employee employee) {
try {
Employee employeeSaved = employeeService.addEmployee(employee);
return ResponseEntity.ok(employeeSaved);
} catch (Exception e) {
return ResponseEntity.badRequest().body("Transaction failed: " + e.getMessage());
}
}
}
Step 8: Enable Transaction Management in Main Class
TransactionManagementApplication.java
Java
package com.example.transactionmanagement;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@SpringBootApplication
@EnableTransactionManagement
public class TransactionManagementApplication{
public static void main(String[] args) {
SpringApplication.run(TransactionManagementApplication.class, args);
}
}
Step 9: Running Our Application
In this step, we will run our application. Once, we run our application using hibernate mapping in our database required tables will be created.

As we can see in logs, our table has been created. We can also confirm it by looking at our database.

Step 10: Test the Application
- Use Postman to send a request:POST http://localhost:9090/api/employee/add
- Body (JSON):
{
"name": "ankur"
}

As we can see in the above response we have added an employee. We can also check our database for employee data and address data.

Similarly, we can also check for address data.

Problem Without Transaction Management
If we don’t use transaction management, partial data may persist in the database even if an error occurs.
Example: In the EmployeeService class, we deliberately initialize the Address object as null. This will cause a NullPointerException when we try to save the address, but the employee record will still be saved in the database.
Java
@Service
public class EmployeeService {
@Autowired
private EmployeeRepository employeeRepository;
@Autowired
private AddressService addressService;
public Employee addEmployee(Employee employee) throws Exception {
Employee employeeSavedToDB = this.employeeRepository.save(employee);
// Address object initialized as null
Address address = null;
address.setId(123L); // This will throw NullPointerException
address.setAddress("Varanasi");
address.setEmployee(employee);
// Save address
this.addressService.addAddress(address);
return employeeSavedToDB;
}
}
Now, we will delete our table from the database and again run our application and will request the application to create an employee.
As we can see in the above media file, we have initialized the address object as null and requested the application, we have an employee created in the database but the address information is not, as we have received a null pointer exception. But, this is not good practice in transaction management, as employees should be saved only when the address is saved and vice-versa.
Applying Transaction Management
To solve this, we use @Transactional.
- If any exception occurs, both Employee and Address will roll back.
- Add @EnableTransactionManagement in the main class.
TransactionManagementApplication.java
Java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@SpringBootApplication
@EnableTransactionManagement
public class TransactionManagementApplication {
public static void main(String[] args) {
SpringApplication.run(TransactionManagementApplication.class, args);
}
}
Update EmployeeService:
Java
@Service
public class EmployeeService {
@Autowired
private EmployeeRepository employeeRepository;
@Autowired
private AddressService addressService;
@Transactional(rollbackFor = Exception.class)
public Employee addEmployee(Employee employee) throws Exception {
Employee employeeSavedToDB = this.employeeRepository.save(employee);
// Intentional null to simulate failure
Address address = null;
address.setId(123L);
address.setAddress("Varanasi");
address.setEmployee(employee);
this.addressService.addAddress(address);
return employeeSavedToDB;
}
}
Running Application
1. Run the Spring Boot application.
2. When making a request to add an employee:
- Without @Transactional : Employee saved, Address failed.
- With @Transactional: Both Employee and Address rolled back (nothing saved).
Note: This ensures data consistency: either both tables are updated or neither is updated.
As we can see in the above media file, this time the employee data do not get stored in the database, nor did the address data. This way the spring has handled the transaction that both employees and address data gets stored or no data gets stored.
What is the main purpose of a transaction in an application?
-
Speed up database operations
-
-
-
Explanation:
Transactions guarantee ACID behavior like atomicity and consistency.
When does Spring automatically roll back a transaction by default?
-
When a checked exception occurs
-
When a runtime exception occurs
-
-
Explanation:
Spring rolls back transactions on unchecked (runtime) exceptions.
Which layer is the best place to put @Transactional?
Explanation:
Service layer is ideal because it contains business logic involving multiple DB operations.
Quiz Completed Successfully
Your Score : 2/3
Accuracy : 0%
Login to View Explanation
1/3
1/3
< Previous
Next >
Explore
Spring Boot Basics and Prerequisites
Spring Boot Core
Spring Boot with REST API
Spring Boot with Database and Data JPA
Spring Boot with Kafka
Spring Boot with AOP