Yes, we're now running our Black Friday Sale. All Access and Pro are 33% off until 2nd December, 2025:
Multiple Databases With Flyway in Spring Boot
Last updated: October 2, 2025
1. Overview
When working with microservices or complex monoliths, it’s common to use multiple databases to handle distinct domains like users, products, etc. Spring Boot provides powerful support for managing such multi-database setups. However, managing schema migrations for multiple databases can be tricky.
In this tutorial, we explore how to integrate Flyway with Spring Boot to support multiple databases in a single application. We’ll use two separate H2 in-memory databases — one for users and one for products — and apply migrations using Flyway independently for each database.
2. Maven Configuration
Before diving in, we’ll set up a simple Spring Boot application with Maven through Spring Initializr.
First, we need to configure the required dependencies. We include Spring Boot starters for data JPA, Flyway, H2, and testing. These dependencies will allow us to use Flyway for managing schema versions and JPA for interacting with the database layer:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>3.2.3</version>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
<version>9.22.3</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.2.224</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>3.2.3</version>
<scope>test</scope>
</dependency>
These dependencies are essential for enabling schema migrations, setting up in-memory databases, and writing unit tests for validation.
3. Configuration for Multiple Databases
In this section, we’ll define data sources, Flyway beans, JPA configurations, and all essential components to support multiple database connections and schema migrations. Spring Boot supports auto-configuration for a single data source, so for multiple databases, we need to provide custom configurations manually. This includes creating separate data source beans, entity manager factories, and transaction managers for each database.
We also need to configure Flyway separately so that each database can run its migration scripts. These configurations ensure clean separation of responsibilities and prevent conflicts.
3.1. Application Properties
Our application.yml is minimal because we configure both datasources programmatically in the UserDbConfig and ProductDbConfig classes. The only property we keep here is to allow circular references in Spring, which avoids potential bean dependency issues in more complex setups:
spring:
main:
allow-circular-references: true
We deliberately omitted the datasource configurations from application.yml to prevent confusion since all connection details are hardcoded in the respective configuration classes. This approach keeps this example focused on demonstrating multiple databases with Flyway rather than external property binding.
3.2. User Entity
The User entity is a simple JPA model that represents user-related data in the userdb database. It contains two essential fields: id and name, both stored as columns:
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
3.3. Product Entity
The Product entity models product-related data stored in the productdb database. It consists of two primary fields: id and title. Similar to the User entity, it uses standard JPA annotations to define persistence behavior:
@Entity
@Table(name = "products")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "product_seq")
private Long id;
private String name;
}
Both entities support all CRUD operations and integrate seamlessly with the configured respective datasource to perform ORM mapping.
3.4. Repositories
The repository interfaces are responsible for encapsulating the logic required to access data sources. For each entity, User and Product, we define a separate repository interface that extends the JpaRepository interface. This extension enables Spring Data JPA to auto-generate standard data access methods such as findById, save, deleteById, and findAll without requiring boilerplate code:
public interface UserRepository extends JpaRepository<User, Long> {}
public interface ProductRepository extends JpaRepository<Product, Long> {}
The UserRepository interacts with userdb, while the ProductRepository is wired to productdb, based on their respective configurations. These interfaces play a crucial role in keeping the code clean, readable, and decoupled from the actual persistence logic.
3.5. Configuration Classes
In a multi-database setup, Spring Boot does not automatically manage multiple data sources or migration scripts. Therefore, we need to explicitly configure each database’s connection, entity scanning, transaction management, and Flyway migration settings.
We define separate configuration classes for each database userdb and productdb to keep the setup modular and maintainable. This explicit configuration ensures that each database runs its migration and manages its transaction boundaries, avoiding conflicts or runtime ambiguity.
Here’s the configuration for userdb:
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
basePackages = "com.baeldung.repository.user",
entityManagerFactoryRef = "userEntityManagerFactory",
transactionManagerRef = "userTransactionManager"
)
public class UserDbConfig {
@Bean
@Primary
public DataSource userDataSource() {
return DataSourceBuilder.create()
.url("jdbc:h2:mem:userdb")
.username("sa")
.password("")
.driverClassName("org.h2.Driver")
.build();
}
@Bean
@Primary
public LocalContainerEntityManagerFactoryBean userEntityManagerFactory(
EntityManagerFactoryBuilder builder) {
return builder
.dataSource(userDataSource())
.packages("com.baeldung.entity")
.persistenceUnit("userPU")
.properties(Map.of("hibernate.hbm2ddl.auto", "none"))
.build();
}
@Bean
@Primary
public PlatformTransactionManager userTransactionManager(
EntityManagerFactory userEntityManagerFactory) {
return new JpaTransactionManager(userEntityManagerFactory);
}
@PostConstruct
public void migrateUserDb() {
Flyway.configure()
.dataSource(userDataSource())
.locations("classpath:db/migration/userdb")
.load()
.migrate();
}
}
The exact configuration is required for productdb, but with a different package and migration path:
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
basePackages = "com.baeldung.repository.product",
entityManagerFactoryRef = "productEntityManagerFactory",
transactionManagerRef = "productTransactionManager"
)
public class ProductDbConfig {
@Bean
public DataSource productDataSource() {
return DataSourceBuilder.create()
.url("jdbc:h2:mem:productdb")
.username("sa")
.password("")
.driverClassName("org.h2.Driver")
.build();
}
@Bean
public LocalContainerEntityManagerFactoryBean productEntityManagerFactory(
EntityManagerFactoryBuilder builder) {
return builder
.dataSource(productDataSource())
.packages("com.baeldung.entity")
.persistenceUnit("productPU")
.properties(Map.of("hibernate.hbm2ddl.auto", "none"))
.build();
}
@Bean
public PlatformTransactionManager productTransactionManager(
EntityManagerFactory productEntityManagerFactory) {
return new JpaTransactionManager(productEntityManagerFactory);
}
@PostConstruct
public void migrateProductDb() {
Flyway.configure()
.dataSource(productDataSource())
.locations("classpath:db/migration/productdb")
.load()
.migrate();
}
}
3.6. SQL Migrations
Flyway uses versioned SQL scripts to manage database schema changes in a predictable and repeatable way. In a multi-database setup, we organize these migration scripts in separate directories, such as db/migration/userdb and db/migration/productdb, to prevent version conflicts and ensure isolation between schemas:
The following configuration is for the userdb database:
-- V1__create_users_table.sql
CREATE TABLE users (
id BIGINT PRIMARY KEY,
name VARCHAR(255)
);
The following configuration is for the productdb database:
-- V1__create_products_table.sql
CREATE TABLE products (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255)
);
This structure is essential for avoiding errors and helps in cleanly managing schema changes for each data domain.
3.7. Unit Tests
Here, we’ll verify the correctness of our service layer logic and database interactions in a multi-database setup. The test ensures that entities are correctly saved and retrieved from their respective databases. It also validates the configuration wiring, bean initialization, and Flyway migrations.
@Test
void givenUsersAndProducts_whenSaved_thenFoundById() {
User user = new User();
user.setName("John");
userRepository.save(user);
Product product = new Product();
product.setName("Laptop");
productRepository.save(product);
assertTrue(userRepository.findById(user.getId()).isPresent());
assertTrue(productRepository.findById(product.getId()).isPresent());
}
This test ensures that both databases work independently and persist data correctly.
4. Conclusion
In this tutorial, we explored how to configure Spring Boot with two databases, manage Flyway migrations independently for each, and validate the setup using unit tests. This pattern can be easily extended to support additional databases or other database engines, such as PostgreSQL or MySQL, by modifying the datasource properties accordingly.
Managing multiple databases in a Spring Boot application can be challenging, especially when schema versioning is involved. By explicitly defining configurations, repositories, and Flyway setups for each data source, we can cleanly separate concerns and enable smooth migrations. This approach not only improves maintainability but also aligns well with the modular architecture often required in enterprise applications.
The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.















