API Hooks

API: hooks

There are three ways you can hook into the Flyway API.

Java-based Migrations

The first one is the most common one: Java-based Migrations when you need more power than SQL can offer you. This is great to for dealing with LOBs or performing advanced data transformations.

In order to be picked up by Flyway, Java-based Migrations must implement the JavaMigration interface. Most users however should inherit from the convenience class BaseJavaMigration instead as it encourages Flyway's default naming convention, enabling Flyway to automatically extract the version and the description from the class name.

Java-based migrations as Spring Beans

By default Java-based migrations discovered through classpath scanning and instantiated by Flyway. In a dependency injection container it is sometimes useful to let the container instantiate the class and wire up its dependencies for you.

The Flyway API lets you pass pre-instantiated Java-based migrations using the javaMigrations property.

Spring users can use this to automatically use all JavaMigration Spring beans with Flyway:

import org.flywaydb.core.Flyway;
import org.flywaydb.core.api.migration.JavaMigration;
import org.springframework.context.ApplicationContext;

...
ApplicationContext applicationContext = ...; // obtain a reference to Spring's ApplicationContext.

Flyway flyway = Flyway.configure()
    .dataSource(url, user, password)
    // Add all Spring-instantiated JavaMigration beans
    .javaMigrations(applicationContext.getBeansOfType(JavaMigration.class).values().toArray(new JavaMigration[0]))
    .load();
flyway.migrate();

Java-based Callbacks

Building upon that are the Java-based Callbacks when you need more power or flexibility in a Callback than SQL can offer you.

They can be created by implementing the Callback interface:

package org.flywaydb.core.api.callback;

/**
 * This is the main callback interface that should be implemented to handle Flyway lifecycle events.
 */
public interface Callback {
    /**
     * Whether this callback supports this event or not. This is primarily meant as a way to optimize event handling
     * by avoiding unnecessary connection state setups for events that will not be handled anyway.
     *
     * @param event   The event to check.
     * @param context The context for this event.
     * @return {@code true} if it can be handled, {@code false} if not.
     */
    boolean supports(Event event, Context context);

    /**
     * Whether this event can be handled in a transaction or whether it must be handled outside a transaction instead.
     * In the vast majority of the cases the answer will be
     * {@code true}. Only in the rare cases where non-transactional statements are executed should this return {@code false}.
     * This method is called before {@link #handle(Event, Context)} in order to determine in advance whether a transaction
     * can be used or not.
     *
     * @param event   The event to check.
     * @param context The context for this event.
     * @return {@code true} if it can be handled within a transaction (almost all cases). {@code false} if it must be
     * handled outside a transaction instead (very rare).
     */
    boolean canHandleInTransaction(Event event, Context context);

    /**
     * Handles this Flyway lifecycle event.
     *
     * @param event   The event to handle.
     * @param context The context for this event.
     */
    void handle(Event event, Context context);

    /**
     * The callback name, Flyway will use this to sort the callbacks alphabetically before executing them
     * @return The callback name
     */
    String getCallbackName();
}

The event argument tells you which Event (beforeClean, afterMigrate, ...) is being handled and the context argument gives you access to things like the database connection and the Flyway configuration.

It is possible for a Java callback to handle multiple events; for example, if you wanted to write a callback to fire off a notification to a third party service at the end of a migration, whether successful or not, and didn't want to duplicate the code, then you could achieve this by handling both afterMigrate and afterMigrateError:

public class MyNotifierCallback implements Callback {
    
    // Ensures that this callback handles both events
    @Override
    public boolean supports(Event event, Context context) {
        return event.equals(Event.AFTER_MIGRATE) || event.equals(Event.AFTER_MIGRATE_ERROR);
    }
    
    // Not relevant if we don't interact with the database
    @Override
    public boolean canHandleInTransaction(Event event, Context context) {
        return true;
    }
    
    // Send a notification when either event happens.
    @Override
    public void handle(Event event, Context context) {
        String notification = event.equals(Event.AFTER_MIGRATE) ? "Success" : "Failed";
        // ... Notification logic ...
        notificationService.send(notification);
    }

    String getCallbackName() {
        return "MyNotifier";
    }
}

In order to be picked up by Flyway, Java-based Callbacks must implement the Callback interface. Flyway will automatically scan for and load all callbacks found in the db/callback package. Additional callback classes or scan locations can be specified by the flyway.callbacks configuration property.

Furthermore, a new Exception type, FlywayBlockStatementExecutionException, has been introduced to facilitate the uninterrupted progression of the entire migration process. To ensure that an error in your Java callback does not halt the migration, you can raise a FlywayBlockStatementExecutionException within your java callback when dealing with BEFORE_EACH_MIGRATE_STATEMENT. This approach allows the migration process to persist rather than terminating upon encountering an error.

Custom Migration resolvers & executors

For those that need more than what the SQL and Java-based migrations offer, you also have the possibility to implement your own MigrationResolver coupled with a custom MigrationExecutor.

These can then be used for loading things like CSV-based migrations or other custom formats.

By using the skipDefaultResolvers property, these custom resolvers can also be used to completely replace the built-in ones (by default, custom resolvers will run in addition to built-in ones).


Didn't find what you were looking for?