--- title: error / spring-jpa-flyway-circular-dependency-2026-06-23 source_type: error-note status: raw branch: feature-build-release-supply-chain-contract related_projects: [ca-skeleton] tags: [error, spring-boot, circular-dependency, flyway, jpa] created: 2026-06-23 updated: 2026-06-23 --- # Spring JPA-Flyway Circular Dependency during Context Initialization ## Parent - Parent branch note: [[raw/branch-notes/feature-build-release-supply-chain-contract]] ## Symptoms During application startup using `bootRun`, the application context failed to initialize with a circular dependency error: ```text Description: The dependencies of some of the beans in the application context form a cycle: entityManagerFactory defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class] ┌─────┐ | flyway defined in class path resource [org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration$FlywayConfiguration.class] ↑ ↓ | postgreSqlPersistenceConfig └─────┘ ``` When running without a local database connection, this circularity prevented the application from failing fast with a connection error and instead produced secondary errors such as `NoSuchBeanDefinitionException` during context shutdown (e.g. `No bean named 'org.springframework.context.annotation.ConfigurationClassPostProcessor.importRegistry' available`). ## Root Cause 1. The configuration class `PostgreSqlPersistenceConfig` uses `@PersistenceContext` to inject the JPA `EntityManager`: ```java @PersistenceContext private EntityManager entityManager; ``` 2. Creating `PostgreSqlPersistenceConfig` therefore requires the `EntityManager` (and consequently `EntityManagerFactory`) to be initialized and available. 3. In `PostgreSqlPersistenceConfig`, a customizer bean was declared as a non-static `@Bean`: ```java @Bean public FlywayConfigurationCustomizer postgreSqlFlywayLocationCustomizer() { return configuration -> configuration.locations("classpath:db/migration/postgresql"); } ``` 4. Because it is a non-static `@Bean` method, Spring requires instantiating `PostgreSqlPersistenceConfig` *before* it can call the method to register the customizer. 5. However: - `EntityManagerFactory` depends on `flywayInitializer` (to ensure migrations run first). - `flywayInitializer` depends on the `Flyway` bean. - The `Flyway` bean depends on all registered `FlywayConfigurationCustomizer` beans. - Spring tries to resolve `FlywayConfigurationCustomizer` -> instantiates `PostgreSqlPersistenceConfig` -> injects `EntityManager` -> creates `EntityManagerFactory` -> waits for `flywayInitializer` -> waits for `Flyway` -> waits for `FlywayConfigurationCustomizer`. - This forms a cycle: `EntityManagerFactory` -> `flywayInitializer` -> `Flyway` -> `PostgreSqlPersistenceConfig` -> `EntityManagerFactory`. ## Solution ### 1. Make the Flyway customizer static Change the `FlywayConfigurationCustomizer` bean declaration to a `static @Bean` method in both `PostgreSqlPersistenceConfig.java` and `SamplePostgreSqlPersistenceConfig.java`: ```java @Bean public static FlywayConfigurationCustomizer postgreSqlFlywayLocationCustomizer() { return configuration -> configuration.locations("classpath:db/migration/postgresql"); } ``` This allows Spring to invoke the customizer registration without instantiating the enclosing configuration class, breaking the immediate `EntityManagerFactory` dependency cycle. ### 2. Refactor `@PersistenceContext` Field Injection to Parameter Injection To prevent the configuration classes from triggering early instantiation of the JPA infrastructure (which causes `NoSuchBeanDefinitionException: No bean named 'org.springframework.context.annotation.ConfigurationClassPostProcessor.importRegistry' available`), eliminate the class-level `EntityManager` field injection and instead inject `EntityManager` as a parameter to the factory `@Bean` methods: **Before:** ```java @Configuration public class PostgreSqlPersistenceConfig { @PersistenceContext private EntityManager entityManager; @Bean public OutboxClaimRepository outboxClaimRepository() { return new PostgreSqlOutboxClaimRepository(entityManager); } } ``` **After:** ```java @Configuration public class PostgreSqlPersistenceConfig { @Bean public OutboxClaimRepository outboxClaimRepository(EntityManager entityManager) { return new PostgreSqlOutboxClaimRepository(entityManager); } } ``` ### Why it works - **Static customizer**: Decouples customizer registration from the instantiation of the enclosing `@Configuration` class. - **Parameter injection**: Prevents Spring's configuration class post-processor from resolving the `EntityManager` bean prematurely during class creation, postponing its resolution until the specific factory method is executed. This completely avoids the early bootstrap lifecycle cycle. ## Verification & Outcomes ### Local Verification 1. Ensured the local database container `ca-pg` is running on port `5432`. 2. Cleaned and recreated the database using `psql` to clear any checksum mismatch issues. 3. Ran `./gradlew :app-bootstrap:bootRun`. 4. The application initialized the connection pool, ran Flyway migrations, and successfully booted: ```text 2026-06-23 14:32:00.619 INFO [main] o.f.core.internal.command.DbMigrate - Successfully applied 3 migrations to schema "public", now at version v4 (execution time 00:00.085s) ... 2026-06-23 14:32:03.278 INFO [main] d.c.bootstrap.CaSkeletonApplication - Started CaSkeletonApplication in 4.87 seconds (process running for 5.034) ```