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.