Hibernate/JPA Not Throwing Error on Delete with Foreign Key Constraint

I’m trying to write some unit tests for a many to many relationship with an association table with a couple extra columns. Realistically if I try to delete an ingredient and then flush the db it should throw an exception bc of the foreign key constraint. The test IS NOT THROWING. When I try to do this manually in my Postgres db, it does fail. My tests cases are using H2. Uncertain if that might be why. The bizarre thing is that even though it’s not throwing, I checker the count after delete is called and it doesn’t seem like anything is deleted either? Hibernate seems to be silently ignoring it.

Main question: why is this there no exception thrown in the unit test?

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Table(uniqueConstraints = @UniqueConstraint(columnNames = { "recipe_id", "ingredient_id" }))
public class RecipeIngredient {

  public static enum Unit {
    KILOGRAM, GRAM, MILLIGRAM, OUNCE, POUND, MILLILITER, LITER,
    TEASPOON, TABLESPOON, FLUID_OUNCE, CUP, PINT, QUART, GALLON,
    PIECE, SLICE, CLOVE, STICK, CAN, UNSPECIFIED,
    BOTTLE, PACK, DASH, PINCH, DROP;
  }

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

  @ToString.Exclude
  @ManyToOne(optional = false)
  @JoinColumn(
    name = "recipe_id",
    referencedColumnName = "recipeId",
    nullable = false,
    foreignKey = @ForeignKey(name = "recipe_id_fkey")
  )
  private Recipe recipe;

  @ToString.Exclude
  @ManyToOne(optional = false, cascade = { CascadeType.PERSIST, CascadeType.MERGE })
  @JoinColumn(
    name = "ingredient_id",
    referencedColumnName = "ingredientId",
    nullable = false,
    foreignKey = @ForeignKey(name = "ingredient_id_fkey")
  )
  private Ingredient ingredient;

  @Enumerated(EnumType.STRING)
  private Unit unit;

  @NotNull
  @Positive
  @Column(nullable = false)
  private Double quantity;
}
  @Test
  public void shouldFailToDeleteIngredient(){
    Recipe recipe = getRecipe();
    testEntityManager.persist(recipe);
    testEntityManager.flush();
    
    assertThrows(PersistenceException.class, () -> {
        ingredientRepository.deleteAll();
        ingredientRepository.flush();
        testEntityManager.flush();  // Forces DB constraint to be evaluated
    });
  }
  • Tried deleting manually in the postgres db, it does error out there.
  • Checking the count instead of trying to assert that an exception returns doesn’t show any evidence of any records being deleted by ingredientRepository.deletAll();
  • I was expecting this test to assert that an exception was in fact thrown.
  • there are foreign key constraints on my posgres table when I run the server itself- so i’d assume it’d be the same for H2? Uncertain.

You’re absolutely right to suspect H2, and that’s the root of the issue.

The reason you’re not seeing a PersistenceException in your test is because H2 does not enforce foreign key constraints by default. This means your test is running against a database that is more permissive than Postgres, and that permissiveness is hiding the problem you’re expecting to test.

Here’s what’s happening:

  1. In Postgres: Deleting an Ingredient that’s referenced in the RecipeIngredient association table fails as expected due to the foreign key constraint.
  2. In H2 (your test): Foreign key constraints are not enforced by default. So when you call ingredientRepository.deleteAll(), H2 allows it silently. Hibernate may skip deleting records if it internally detects constraint issues, but H2 doesn’t throw because it’s not validating the constraint.
  3. Count not decreasing: Hibernate is likely deferring deletion or skipping based on its session state or because it’s detecting an orphaned constraint that can’t be satisfied — but since the DB doesn’t throw, neither does Hibernate. However, because it’s not flushed properly or skipped, you still see the count unchanged.

How to fix it:

You need to enable foreign key constraints in H2. Add the following to your H2 JDBC URL in your test configuration:

jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;MODE=PostgreSQL;DATABASE_TO_UPPER=false;INIT=SET REFERENTIAL_INTEGRITY TRUE

Or, explicitly run the SQL in your test setup:

@BeforeEach
public void enableFkConstraints() {
    testEntityManager.getEntityManager()
        .createNativeQuery("SET REFERENTIAL_INTEGRITY TRUE")
        .executeUpdate();
}

You can also disable integrity and re-enable it manually like:

testEntityManager.getEntityManager()
    .createNativeQuery("SET REFERENTIAL_INTEGRITY FALSE").executeUpdate();
// truncate or reset tables
testEntityManager.getEntityManager()
    .createNativeQuery("SET REFERENTIAL_INTEGRITY TRUE").executeUpdate();

Summary:

  • H2 does not enforce foreign key constraints by default, which is why you’re not seeing an exception.
  • Postgres does, which is why the same deletion fails in production.
  • Fix: Enable referential integrity in H2 to match production behavior.

Let me know if you want help updating your test config to do this cleanly.