Using Spring Data in Spring Boot, when the application encounters a constraint violation, a DataIntegrityViolationException
is thrown.
However, when running a DataJpaTest
unit test, it throws something else, e.g. a PersistenceException
.
Expected: @DataJpaTest
uses the same exception translation as in the full application.
Sample config:
@RunWith(SpringRunner.class)
@DataJpaTest
@ImportAutoConfiguration(FlywayAutoConfiguration.class)
public class SampleTest {
...
}
Comment From: SingleShot
I would appreciate a suggested workaround if this is in fact a bug or limitation. Thanks!
Comment From: snicoll
@SingleShot that is interesting, thanks. I think that's a bug and the following should be a workaround for the time being (please let us know if that's the case)
@RunWith(SpringRunner.class)
@DataJpaTest
@ImportAutoConfiguration(PersistenceExceptionTranslationPostProcessor.class)
public class SampleTest {
...
}
(There is no need to import Flyway, that is done automatically)
Comment From: SingleShot
Hi Stéphane. Unfortunately using @ImportAutoConfiguration(PersistenceExceptionTranslationPostProcessor.class)
produces the same unexpected result for me. Thanks for pointing out that I do not need to import the Flyway auto-configuration. I was on Boot 1.5.6 but also tried 1.5.7. Thanks.
Comment From: snicoll
Alright can you share a sample that reproduces the problem?
Comment From: spring-projects-issues
If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.
Comment From: spring-projects-issues
Closing due to lack of requested feedback. If you would like us to look at this issue, please provide the requested information and we will re-open the issue.
Comment From: wimdeblauwe
I would like to re-open the issue. I created a small Spring Boot application that shows the problem using Spring Boot 3.5.4.
If you run BikeRepositoryIntegrationTest
(which is a @SpringBootTest
), there is no failure as DataIntegrityViolationException
is raised. When running BikeRepositoryTest
(which is a @DataJpaTest
), there is a test failure because there org.hibernate.exception.ConstraintViolationException
is thrown instead.
Test project: sb-issue-10310.zip
Comment From: nosan
@DataJpaTest
has a meta-annotation @Transactional
, whereas @SpringBootTest
does not, which leads to different
results. TransactionalTestExecutionListener
detects that the test is transactional and therefore starts a transaction before
the test method is executed, and rolls it back afterward.
In @SpringBootTest
, the test does not run within test transactional, so the save
operation is executed in a real
transaction, and the DataIntegrityViolationException
is thrown as expected, and no rollback occurs. (You can remove EntityManager.flush(...)
and still see the exception is thrown)
However, in @DataJpaTest
, the test runs within test transactional, and the save
operation is executed within a
transaction that is rolled back after the test method completes, and therefore the DataIntegrityViolationException
is
not thrown immediately.
You can update your test with the following code:
@Test
void testDuplicateName() {
Bike bike1 = new Bike(UUID.randomUUID(), "bike1");
Bike bike2 = new Bike(UUID.randomUUID(), "bike1");
repository.save(bike1);
assertThatExceptionOfType(DataIntegrityViolationException.class)
.isThrownBy(() -> repository.saveAndFlush(bike2));
}
However, you need to update BikeRepository
to extend JpaRepository<Bike, UUID>
instead of
CrudRepository<Bike, UUID>
to use the saveAndFlush
method.
If you run this test, you will see that TransactionalTestExecutionListener
fails to commit a transaction.
@DataJpaTest
@Import(TestcontainersConfiguration.class)
@Rollback(false)
class BikeRepositoryTest {
@Autowired
private BikeRepository repository;
@Test
void testDuplicateName() {
Bike bike1 = new Bike(UUID.randomUUID(), "bike1");
Bike bike2 = new Bike(UUID.randomUUID(), "bike1");
repository.save(bike1);
repository.save(bike2);
}
}
2025-08-08T17:27:44.272+03:00 WARN 5083 --- [sb-issue-10310] [ main] o.s.test.context.TestContextManager : Caught exception while invoking 'afterTestMethod' callback on TestExecutionListener [org.springframework.test.context.transaction.TransactionalTestExecutionListener] for test method [void com.wimdeblauwe.sb_issue_10310.BikeRepositoryTest.testDuplicateName()] and test instance [com.wimdeblauwe.sb_issue_10310.BikeRepositoryTest@5ba7391b]
org.springframework.dao.DataIntegrityViolationException: could not execute statement [ERROR: duplicate key value violates unique constraint "bike_name_key"
Detail: Key (name)=(bike1) already exists.] [insert into bike (name,id) values (?,?)]; SQL [insert into bike (name,id) values (?,?)]; constraint [bike_name_key]
Comment From: snicoll
Thanks @nosan. This was raised before, see for instance https://github.com/spring-projects/spring-boot/issues/28067. We can't force @SpringBootTest
tests to run in a transaction so I am afraid there's not much we can do about this.
Comment From: wimdeblauwe
Thanks for the information. If really needed, people can start new transactions, but then cleanup needs to be done manually. The test would look something like this:
@DataJpaTest
@Import({TestcontainersConfiguration.class})
class BikeRepositoryTest {
@Autowired
private BikeRepository repository;
@Autowired
private PlatformTransactionManager transactionManager;
@AfterEach
void tearDown() {
repository.deleteAll();
}
@Test
void testDuplicateName() {
Bike bike1 = new Bike(UUID.randomUUID(), "bike1");
Bike bike2 = new Bike(UUID.randomUUID(), "bike1");
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRES_NEW);
transactionTemplate.executeWithoutResult(status -> {
repository.save(bike1);
});
Assertions.assertThatExceptionOfType(DataIntegrityViolationException.class)
.isThrownBy(() -> {
transactionTemplate.executeWithoutResult(status -> {
repository.save(bike2);
});
});
}
}
Since bike1
is stored in a new transaction, it would not get rolled back. So I added the @AfterEach
to do the cleanup. The test works like this, but not sure if it is worth it :)
Comment From: nosan
Thanks for the information. If really needed, people can start new transactions, but then cleanup needs to be done manually.
I think annotating your test class with @Transactional(propagation = Propagation.NEVER)
😄 does the trick as well.
Comment From: wimdeblauwe
Thanks . A lot simpler indeed:
@DataJpaTest
@Import({TestcontainersConfiguration.class})
@Transactional(propagation = Propagation.NEVER)
class BikeRepositoryTest {
@Autowired
private BikeRepository repository;
@AfterEach
void tearDown() {
repository.deleteAll();
}
@Test
void testDuplicateName() {
Bike bike1 = new Bike(UUID.randomUUID(), "bike1");
Bike bike2 = new Bike(UUID.randomUUID(), "bike1");
repository.save(bike1);
Assertions.assertThatExceptionOfType(DataIntegrityViolationException.class)
.isThrownBy(() -> {
repository.save(bike2);
});
}
}
But it affects all the tests in the class if you have multiple. So if you have other tests, my initial idea might be better.