30

I would like to upgrade for my new projects to Spring Boot version 2.1.0, but I am limited with Oracle 11 database, which is supported by the Flyway 4.2.0 library. Everything runs normally on Spring Boot version 2.0.5 Release, but when moving to 2.1.0 release I get this error:

java.lang.NoClassDefFoundError: 
org/flywaydb/core/api/configuration/FluentConfiguration

The POM configuration is as follows:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.0.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
    <ojdbc6.version>11.2.0.1</ojdbc6.version>
</properties>

<dependencies>
    <dependency>
        <groupId>com.oracle.jdbc</groupId>
        <artifactId>ojdbc6</artifactId>
        <version>${ojdbc6.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>org.flywaydb</groupId>
        <artifactId>flyway-core</artifactId>
        <version>4.2.0</version>
    </dependency>
</dependencies>

UPDATE

I am able to solve the problem via @Configuration (or of course add to the main class), but the thing is its a bug or feature? Prior to version 2.1.0 everything was done via autoconfiguration and it works out-of-box.

@Bean(initMethod = "migrate")
Flyway flyway() {
    Flyway flyway = new Flyway();
    flyway.setBaselineOnMigrate(true);
    flyway.setDataSource("jdbc:oracle:thin:@localhost:1521:xe", "USER", "PASSWORD1");
    return flyway;
}
troger19
  • 1,159
  • 2
  • 12
  • 29
  • 6
    It's not a bug at all. Spring Boot has a dependency on Flyway 5.2.1, which has a different API from the old version that you're using. – JB Nizet Nov 16 '18 at 19:31
  • Yes, I have noticed, but cant it be somehow excluded in pom.xml in favour of the older one? I could find the right dependency, but it was lookoing for this FluentConfiguration class. – troger19 Nov 16 '18 at 19:34
  • 1
    You can of course. But that won't change the Spring Boot code that configure Flyway, and which uses the new classes that only exist in the latest version of Flyway. To make an analogy, if you compile classes that use java.util.stream with Java 8, and then try to run that code on Java 7, that won't work, because java.util.stream doesn't exists in Java 7. – JB Nizet Nov 16 '18 at 19:35
  • how did you manage to test your app? – John P Aug 03 '19 at 13:32

4 Answers4

10

I had the same problem with PostgreSQL 9.2, and used the following class to solve the problem.

Be aware though that all the custom properties you might set in the Spring Boot properties will be ignored, since that replaces the whole Flyway autoconfiguration by your own. So you might have to add some additional code to fit your needs.

@Configuration
class FlywayConfig {
    @Bean
    fun flyway(dataSource: DataSource): Flyway {
        val flyway = Flyway()
        flyway.dataSource = dataSource
        return flyway
    }

    @Bean
    fun flywayInitializer(flyway: Flyway): FlywayMigrationInitializer {
        return FlywayMigrationInitializer(flyway, null)
    }

    /**
     * Additional configuration to ensure that [EntityManagerFactory] beans depend on the
     * `flywayInitializer` bean.
     */
    @Configuration
    class FlywayInitializerJpaDependencyConfiguration : EntityManagerFactoryDependsOnPostProcessor("flywayInitializer")
}

PS: this is Kotlin code, but you should be able to translate it to Java fairly easily.

JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
  • Hi. Yes, thats exactly what I tried to do, please see my comment; Make configuration programatically. The thing is, is that the correct way? Why the autoconfiguration was change in this way? And how then I configure different datasources for different environments if I cant use application.properties? Thanks – troger19 Nov 16 '18 at 19:23
  • You can use application.properties. Just don't expect the flyway properties to have any effect since Spring Boot won't read and apply them anymore. The other ones, unrelated to Flyway, will still work. – JB Nizet Nov 16 '18 at 19:26
  • How to make it work in Spring Boot tests? I need to load different properties for flyway for tests, but either Flyway bean is not availabe or when Ier create Flyway bean manually, then other beans are lost. Thanks – troger19 Dec 08 '18 at 13:02
  • 1
    @JBNizet As someone who doesn't know Kotlin, all is understandable except of the last line. – Younes El Ouarti Jul 20 '20 at 16:16
  • @Younes_EO Last line : ` @Configuration public static class FlywayInitializerJpaDependencyConfiguration extends EntityManagerFactoryDependsOnPostProcessor { public FlywayInitializerJpaDependencyConfiguration() { super("flywayInitializer"); } }` – pdem Jul 28 '20 at 16:17
8

Use the following dependency, it will be resolved by this.

<dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-core</artifactId>
    <version>5.2.3</version>
</dependency>
Trenton McKinney
  • 56,955
  • 33
  • 144
  • 158
SuniiilSingh
  • 117
  • 1
  • 3
  • 3
    As I mentioned in the question, I can't use higher version of flyway than 4.2.0 because of unsuported Oracle 11 – troger19 Sep 25 '19 at 10:45
  • Same for me can't use a higher version of flyway, unless you say that flyway-core 5 is compatible with ojdbc6 11 and oracle 11 (which isn't) – pdem Jul 31 '20 at 13:58
4

I made the configuration for Spring Boot 2.1.1 and had to redefine the bean FlywayDefaultDdlModeProvider as well.

@Configuration
@ConditionalOnProperty(prefix = "spring.flyway", name = "enabled", matchIfMissing = true)
public class LegacyFlywayAutoConfiguration {

    @Bean
    @Primary
    public SchemaManagementProvider flywayDefaultDdlModeProvider(ObjectProvider<Flyway> flyways) {
        return new SchemaManagementProvider() {
            @Override
            public SchemaManagement getSchemaManagement(DataSource dataSource) {
                return SchemaManagement.MANAGED;
            }
        };
    }

    @Bean(initMethod = "migrate")
    public Flyway flyway(DataSource dataSource) {
        Flyway flyway = new Flyway();
        flyway.setBaselineOnMigrate(true);
        flyway.setDataSource(dataSource);
        return flyway;
    }

    @Bean
    public FlywayMigrationInitializer flywayInitializer(Flyway flyway) {
        return new FlywayMigrationInitializer(flyway, null);
    }

    /**
     * Additional configuration to ensure that {@link JdbcOperations} beans depend
     * on the {@code flywayInitializer} bean.
     */
    @Configuration
    @ConditionalOnClass(JdbcOperations.class)
    @ConditionalOnBean(JdbcOperations.class)
    protected static class FlywayInitializerJdbcOperationsDependencyConfiguration
            extends JdbcOperationsDependsOnPostProcessor {

        public FlywayInitializerJdbcOperationsDependencyConfiguration() {
            super("flywayInitializer");
        }

    }
}
vargapeti
  • 301
  • 2
  • 7
  • can you use it in @SpringBootTest? – troger19 Dec 08 '18 at 13:02
  • Yes, the overwritten configuration itself works with @SpringBootTest. But usually you don`t want to use Oracle database for integration test, so I don`t see the point using it only for the test. I am using H2 database for @SpringBootTest, and it does not any have problem with the latest Flyway version. Although, for sake of the compatibility I did not create an own Flyway configuration for integration test. – vargapeti Dec 10 '18 at 07:17
  • Yes, thats what I want to do, but somehow I cant figure the correct way. If I include the application.yml in test/resource, then the Flyway Bean is instantiated, and it instantiated for Version 5+. If I put @ Configuration or @ TestCOnfiguration somewhere, then I can create it programatically, but then other Bean (Daos, Services) are null, because I need to create them manually as well. I just want to create Flyway Bean manually and everything rest leave on Spring. – troger19 Dec 10 '18 at 14:09
  • Had to add `spring.main.allow-bean-definition-overriding=true` to application.properties but it works! – Ondřej Stašek Apr 18 '19 at 11:56
2

Using Javassist library you can instrument the FlywayDB library to log that the Oracle version is no longer supported instead of throwing a fatal exception (by wrapping the ensureDatabaseIsCompatibleWithFlywayEdition method call in a try-catch clause). In my case FlywayDB community edition (5.2.4) seems to work fine with Oracle 11.2 once I did it. This solution has its drawbacks but it was the best option in my case (database should be upgraded soon) so maybe someone can find it useful too. The below code should be run in your application before anything else ideally. Please use at your own risk.

public static void suppressIncompatibleDatabaseVersionCheck() {
    try {
        CtClass ctClass = ClassPool.getDefault().get("org.flywaydb.core.internal.database.base.Database");
        ctClass.defrost();
        CtMethod method = ctClass.getDeclaredMethod("ensureDatabaseIsCompatibleWithFlywayEdition");
        CtClass etype = ClassPool.getDefault().get("java.lang.Exception");
        method.addCatch("{ LOG.warn(\"Exception suppressed: \" + $e); return ;}", etype);
        ctClass.toClass();
    } catch (NotFoundException | CannotCompileException e) {
        log.error("Could not instrument FlywayDB code.", e);
    }
}
kieraf
  • 96
  • 3