From 955a6bd29aaedde0359cf0a59e0acffef630a634 Mon Sep 17 00:00:00 2001 From: Nikhil Bharadwaj Ramashasthri Date: Tue, 14 Jul 2026 19:15:30 -0700 Subject: [PATCH 1/2] Add DependsOnDatabaseInitializationDetector so engines wait for database initializers Flowable engines create/validate their own schema on engine build, but Spring Boot gave no ordering guarantee between engine beans and managed database initializers (Flyway, Liquibase, spring.sql.init). Register a FlowableDependsOnDatabaseInitializationDetector for all Engine beans via the DependsOnDatabaseInitializationDetector SPI, with an opt-out property (flowable.depends-on-database-initialization-detection), following the approach outlined by @filiphr in #4213. --- ...pendsOnDatabaseInitializationDetector.java | 46 +++++++++++ ...itional-spring-configuration-metadata.json | 9 ++- .../main/resources/META-INF/spring.factories | 2 + ...sOnDatabaseInitializationDetectorTest.java | 76 +++++++++++++++++++ 4 files changed, 131 insertions(+), 2 deletions(-) create mode 100644 modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/boot/FlowableDependsOnDatabaseInitializationDetector.java create mode 100644 modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/test/java/org/flowable/test/spring/boot/FlowableDependsOnDatabaseInitializationDetectorTest.java diff --git a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/boot/FlowableDependsOnDatabaseInitializationDetector.java b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/boot/FlowableDependsOnDatabaseInitializationDetector.java new file mode 100644 index 00000000000..83939e18fb4 --- /dev/null +++ b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/boot/FlowableDependsOnDatabaseInitializationDetector.java @@ -0,0 +1,46 @@ +/* Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.flowable.spring.boot; + +import java.util.Collections; +import java.util.Set; + +import org.flowable.common.engine.api.Engine; +import org.springframework.boot.sql.init.dependency.AbstractBeansOfTypeDependsOnDatabaseInitializationDetector; +import org.springframework.core.env.Environment; + +/** + * {@link org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitializationDetector} + * marking all Flowable {@link Engine} beans as depending on database initialization, so Spring + * Boot orders them after Flyway, Liquibase or spring.sql.init have run. + * + *

Can be opted out of by setting {@code flowable.depends-on-database-initialization-detection} + * to {@code false}, mirroring the JPA detector's opt-out behavior. + * + * @author Nikhil Bharadwaj Ramashasthri + */ +class FlowableDependsOnDatabaseInitializationDetector extends AbstractBeansOfTypeDependsOnDatabaseInitializationDetector { + + private final Environment environment; + + FlowableDependsOnDatabaseInitializationDetector(Environment environment) { + this.environment = environment; + } + + @Override + protected Set> getDependsOnDatabaseInitializationBeanTypes() { + boolean detectionEnabled = environment + .getProperty("flowable.depends-on-database-initialization-detection", Boolean.class, true); + return detectionEnabled ? Collections.singleton(Engine.class) : Collections.emptySet(); + } +} diff --git a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 050d700a1e2..6dbe5a34dd8 100644 --- a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -714,6 +714,12 @@ "name": "flowable.process.async-history.executor.timer-runnable-needed", "type": "java.lang.Boolean", "defaultValue": false + }, + { + "name": "flowable.depends-on-database-initialization-detection", + "type": "java.lang.Boolean", + "description": "Whether Flowable engine beans should be marked as depending on Spring Boot managed database initializers (Flyway, Liquibase, spring.sql.init), ensuring they start only after schema initialization has run.", + "defaultValue": true } ], "hints": [ @@ -761,7 +767,6 @@ "value": "spring_delegating_noop", "description": "Use the Spring Security 5 PasswordEncoderFactories.createDelegatingPasswordEncoder(). With NoopPasswordEncoder as the default password encoder for matches. This should be used for backwards compatibility with older versions." } - ] }, { @@ -797,4 +802,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories index 6fa3a2dcdb0..df3bcafce56 100644 --- a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories @@ -1,3 +1,5 @@ org.springframework.boot.EnvironmentPostProcessor=\ org.flowable.spring.boot.environment.FlowableDefaultPropertiesEnvironmentPostProcessor +org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitializationDetector=\ + org.flowable.spring.boot.FlowableDependsOnDatabaseInitializationDetector diff --git a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/test/java/org/flowable/test/spring/boot/FlowableDependsOnDatabaseInitializationDetectorTest.java b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/test/java/org/flowable/test/spring/boot/FlowableDependsOnDatabaseInitializationDetectorTest.java new file mode 100644 index 00000000000..4aa94c50dc0 --- /dev/null +++ b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/test/java/org/flowable/test/spring/boot/FlowableDependsOnDatabaseInitializationDetectorTest.java @@ -0,0 +1,76 @@ +/* Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.flowable.test.spring.boot; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.flowable.spring.boot.ProcessEngineAutoConfiguration; +import org.flowable.spring.boot.ProcessEngineServicesAutoConfiguration; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration; +import org.springframework.boot.jdbc.autoconfigure.DataSourceInitializationAutoConfiguration; +import org.springframework.boot.jdbc.autoconfigure.DataSourceTransactionManagerAutoConfiguration; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +/** + * The Flowable engines create/validate their own schema on engine build, so their beans must be + * ordered after Spring Boot managed database initializers (Flyway, Liquibase, spring.sql.init). + * This is done through the {@code DependsOnDatabaseInitializationDetector} SPI (issue #4213). + * + * @author Nikhil Bharadwaj Ramashasthri + */ +class FlowableDependsOnDatabaseInitializationDetectorTest { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of( + DataSourceAutoConfiguration.class, + DataSourceTransactionManagerAutoConfiguration.class, + DataSourceInitializationAutoConfiguration.class, + ProcessEngineAutoConfiguration.class, + ProcessEngineServicesAutoConfiguration.class)); + + @Test + void processEngineDependsOnDatabaseInitialization() { + contextRunner + .withPropertyValues("spring.sql.init.mode=always") + .run(context -> { + assertThat(context).hasNotFailed(); + String[] dependsOn = context.getBeanFactory() + .getBeanDefinition("processEngine") + .getDependsOn(); + assertThat(dependsOn) + .as("processEngine must wait for the SQL init database initializer") + .isNotNull() + .contains("dataSourceScriptDatabaseInitializer"); + }); + } + + @Test + void detectionCanBeOptedOut() { + contextRunner + .withPropertyValues( + "spring.sql.init.mode=always", + "flowable.depends-on-database-initialization-detection=false") + .run(context -> { + assertThat(context).hasNotFailed(); + String[] dependsOn = context.getBeanFactory() + .getBeanDefinition("processEngine") + .getDependsOn(); + assertThat(dependsOn == null || !java.util.Arrays.asList(dependsOn) + .contains("dataSourceScriptDatabaseInitializer")) + .as("opt-out must remove the database initializer dependency") + .isTrue(); + }); + } +} From a797f2fe6a9786c3445861ca26b2129d5ecc7b31 Mon Sep 17 00:00:00 2001 From: Nikhil Bharadwaj Ramashasthri Date: Wed, 15 Jul 2026 01:09:28 -0700 Subject: [PATCH 2/2] Address review: skip auto deployment in tests, tighten assertions --- ...leDependsOnDatabaseInitializationDetectorTest.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/test/java/org/flowable/test/spring/boot/FlowableDependsOnDatabaseInitializationDetectorTest.java b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/test/java/org/flowable/test/spring/boot/FlowableDependsOnDatabaseInitializationDetectorTest.java index 4aa94c50dc0..3da7ff2e362 100644 --- a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/test/java/org/flowable/test/spring/boot/FlowableDependsOnDatabaseInitializationDetectorTest.java +++ b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/test/java/org/flowable/test/spring/boot/FlowableDependsOnDatabaseInitializationDetectorTest.java @@ -43,7 +43,9 @@ class FlowableDependsOnDatabaseInitializationDetectorTest { @Test void processEngineDependsOnDatabaseInitialization() { contextRunner - .withPropertyValues("spring.sql.init.mode=always") + .withPropertyValues( + "spring.sql.init.mode=always", + "flowable.check-process-definitions=false") .run(context -> { assertThat(context).hasNotFailed(); String[] dependsOn = context.getBeanFactory() @@ -51,7 +53,6 @@ void processEngineDependsOnDatabaseInitialization() { .getDependsOn(); assertThat(dependsOn) .as("processEngine must wait for the SQL init database initializer") - .isNotNull() .contains("dataSourceScriptDatabaseInitializer"); }); } @@ -61,16 +62,16 @@ void detectionCanBeOptedOut() { contextRunner .withPropertyValues( "spring.sql.init.mode=always", + "flowable.check-process-definitions=false", "flowable.depends-on-database-initialization-detection=false") .run(context -> { assertThat(context).hasNotFailed(); String[] dependsOn = context.getBeanFactory() .getBeanDefinition("processEngine") .getDependsOn(); - assertThat(dependsOn == null || !java.util.Arrays.asList(dependsOn) - .contains("dataSourceScriptDatabaseInitializer")) + assertThat(dependsOn) .as("opt-out must remove the database initializer dependency") - .isTrue(); + .isNullOrEmpty(); }); } }