How to Configure Two DataSources in Spring Boot
Introduction
An application keeps its own data in one database and has to read from, or write to, a second one - a reporting database, a legacy system, an audit store. Spring Boot configures oneDataSource from spring.datasource.*, and adding a second DataSource bean the obvious way makes the first one disappear or causes "expected single matching bean" errors. This quick tip shows the setup Spring Boot 4 recommends, which keeps the auto-configured DataSource and adds a second one next to it.Tested on Spring Boot 4.1.2, Spring Framework 7.0.10, HikariCP 7.0.2, H2 2.4.240 and JDK 25.
Adding a second DataSource next to the auto-configured one
Declare the secondDataSource with @Bean(defaultCandidate = false) and a @Qualifier: it is then injected only where it is asked for by name, and Spring Boot keeps configuring the first one as before. Its settings are bound from a prefix of your own with @ConfigurationProperties -
The short answer
@Bean(defaultCandidate = false)
@Qualifier("audit")
@ConfigurationProperties("app.audit.datasource")
HikariDataSource auditDataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
application.properties
spring.datasource.url=jdbc:h2:mem:shop
app.audit.datasource.jdbc-url=jdbc:h2:mem:audit
Two DataSources in a running application
The example has a shop database, configured by Spring Boot, with abook table from schema.sql, and an audit database added as above. Each database gets its own JdbcClient.
src/main/resources/application.properties
spring.main.banner-mode=off
logging.level.root=WARN
# the first DataSource - auto-configured by Spring Boot
spring.datasource.url=jdbc:h2:mem:shop
# the second DataSource - bound by @ConfigurationProperties("app.audit.datasource")
app.audit.datasource.jdbc-url=jdbc:h2:mem:audit
app.audit.datasource.maximum-pool-size=2
src/main/resources/schema.sql
create table book (isbn varchar(20) primary key, title varchar(100));
insert into book values ('978-1', 'Effective Java');
DataSourceConfig.java
//DataSourceConfig.java
package com.javabrahman.tips.datasources;
import com.zaxxer.hikari.HikariDataSource;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.simple.JdbcClient;
@Configuration
public class DataSourceConfig {
@Bean(defaultCandidate = false)
@Qualifier("audit")
@ConfigurationProperties("app.audit.datasource")
HikariDataSource auditDataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
@Bean(defaultCandidate = false)
@Qualifier("audit")
JdbcClient auditJdbcClient(@Qualifier("audit") DataSource auditDataSource) {
return JdbcClient.create(auditDataSource);
}
}
TwoDataSourcesApp.java
//TwoDataSourcesApp.java
package com.javabrahman.tips.datasources;
import java.util.List;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.core.simple.JdbcClient;
@SpringBootApplication
public class TwoDataSourcesApp {
public static void main(String[] args) {
SpringApplication.run(TwoDataSourcesApp.class, args);
}
@Bean
CommandLineRunner demo(JdbcClient shop, @Qualifier("audit") JdbcClient audit) {
return args -> {
audit.sql("create table audit_log (id int auto_increment primary key, message varchar(200))").update();
String title = shop.sql("select title from book where isbn = '978-1'").query(String.class).single();
audit.sql("insert into audit_log (message) values (?)").param("read book: " + title).update();
System.out.println("shop JdbcClient -> database " + database(shop) + ", tables " + tables(shop));
System.out.println("audit JdbcClient -> database " + database(audit) + ", tables " + tables(audit));
System.out.println("audit log: " + audit.sql("select message from audit_log").query(String.class).list());
};
}
private static String database(JdbcClient jdbc) {
return jdbc.sql("select database()").query(String.class).single();
}
private static List<String> tables(JdbcClient jdbc) {
return jdbc.sql("select table_name from information_schema.tables where table_schema = 'PUBLIC'").query(String.class).list();
}
}
shop JdbcClient -> database SHOP, tables [BOOK] audit JdbcClient -> database AUDIT, tables [AUDIT_LOG] audit log: [read book: Effective Java]
- Each
JdbcClientreaches its own database:select database()answersSHOPfor the auto-configured one andAUDITfor the one with theauditqualifier. - The injection point
JdbcClient shophas no qualifier and still received the shop database, because the audit beans are not default candidates. Spring Boot's auto-configuredDataSourceandJdbcClientstayed in place. - The shop database has the
BOOKtable fromschema.sql, the audit database only theAUDIT_LOGtable the runner created.schema.sqlwas applied to the auto-configuredDataSourceonly.
url, the name used under spring.datasource. Run with that file, the application fails on the first use of the audit database -
src/main/resources/wrong.properties - the usual first attempt
spring.main.banner-mode=off
logging.level.root=WARN
spring.datasource.url=jdbc:h2:mem:shop
app.audit.datasource.url=jdbc:h2:mem:audit
2026-11-13T23:01:01.170+05:30 ERROR 56316 --- [ main] com.zaxxer.hikari.HikariConfig : HikariPool-2 - dataSource or dataSourceClassName or jdbcUrl is required. java.lang.IllegalArgumentException: dataSource or dataSourceClassName or jdbcUrl is required.
Pitfalls
- Under your own prefix the properties are those of the pool class, not Spring Boot's: HikariCP's address property is
jdbc-url, sourlis ignored and the pool fails with "jdbcUrl is required", as above. Other Hikari settings, such asmaximum-pool-size, are set under the same prefix. - Leaving out
defaultCandidate = falsemakes the second bean an ordinaryDataSource. Spring Boot's ownDataSourceis only created when no such bean exists, so the auto-configured one is no longer created. schema.sql,data.sql, Flyway and Liquibase run against the auto-configuredDataSourceonly. The second database needs its own initialization.@Transactionaluses the transaction manager of the auto-configuredDataSource. Work on the second database that must be transactional needs aDataSourceTransactionManagerfor it, named in@Transactional("auditTransactionManager").- With Spring Data JPA, each database also needs its own
EntityManagerFactoryand an@EnableJpaRepositoriesfor the repositories that use it; the Spring Boot reference shows this in Using Multiple EntityManagerFactoriesClick to read: Using Multiple EntityManagerFactories.
Related Spring Boot tutorials
How Spring Boot configures aDataSource and JdbcClient is in JdbcClient and Spring Data JDBCClick to read tutorial: JdbcClient and Spring Data JDBC: When Not to Use JPA; binding settings with @ConfigurationProperties is in type-safe configuration with @ConfigurationPropertiesClick to read tutorial: Type-Safe Configuration with @ConfigurationProperties, and choosing between beans of the same type in @Qualifier, @Primary and conditional beansClick to read tutorial: Spring @Qualifier, @Primary and Conditional Beans.Related Spring Boot tutorials
JdbcClient and Spring Data JDBC: When Not to Use JPAClick to read tutorial: JdbcClient and Spring Data JDBC: When Not to Use JPA Spring Data JPA Introduction: Entities and RepositoriesClick to read tutorial: Spring Data JPA Introduction: Entities and Repositories Type-Safe Configuration with @ConfigurationPropertiesClick to read tutorial: Type-Safe Configuration with @ConfigurationProperties Spring @Qualifier, @Primary and Conditional BeansClick to read tutorial: Spring @Qualifier, @Primary and Conditional Beans
JdbcClient and Spring Data JDBC: When Not to Use JPAClick to read tutorial: JdbcClient and Spring Data JDBC: When Not to Use JPA Spring Data JPA Introduction: Entities and RepositoriesClick to read tutorial: Spring Data JPA Introduction: Entities and Repositories Type-Safe Configuration with @ConfigurationPropertiesClick to read tutorial: Type-Safe Configuration with @ConfigurationProperties Spring @Qualifier, @Primary and Conditional BeansClick to read tutorial: Spring @Qualifier, @Primary and Conditional Beans