Home Java How to Configure a DataSource Programmatically in Spring Boot
Intermediate 3 min · July 14, 2026

How to Configure a DataSource Programmatically in Spring Boot

Learn how to configure a DataSource programmatically in Spring Boot.

N
Naren Founder & Principal Engineer

20+ years shipping production Java in banking & fintech. Lessons pulled from things that broke in production.

Follow
Production
production tested
July 15, 2026
last updated
2,398
articles · all by Naren
Before you start⏱ 15-20 min read
  • Java 17+
  • Spring Boot 3.x
  • Basic understanding of Spring Boot auto-configuration
  • Familiarity with HikariCP or connection pooling concepts
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • Use a @Configuration class with @Bean methods to define DataSource, JPA properties, and TransactionManager.
  • Override spring.datasource.* properties by providing your own DataSource bean.
  • For multiple databases, use @Primary and @Qualifier annotations.
  • Always close the DataSource in @PreDestroy to avoid connection leaks.
  • Test with Testcontainers to avoid hardcoded connection strings.
✦ Definition~90s read
What is Configuring a DataSource Programmatically in Spring Boot?

Programmatic DataSource configuration in Spring Boot is when you define a DataSource bean in a @Configuration class instead of relying on auto-configuration, giving you full control over connection pooling, multiple databases, and credential management.

Think of a DataSource as the plumbing connecting your app to the database.
Plain-English First

Think of a DataSource as the plumbing connecting your app to the database. Normally Spring Boot auto-plumbs for you, but sometimes you need custom pipes (e.g., for multiple databases or cloud secrets). Programmatic configuration is like manually installing those pipes instead of relying on the default setup.

Spring Boot's auto-configuration is a lifesaver—until it isn't. You drop a few lines in application.properties, and boom, you have a DataSource. But what happens when you need to connect to multiple databases, read credentials from a vault, or use a connection pool with specific tuning? The auto-configuration gets in the way.

I've seen countless teams struggle when they outgrow the magic. A fintech startup I consulted for had a production outage because their auto-configured HikariCP pool exhausted connections under load—they had no idea how to customize it programmatically. The default pool size of 10 was fine for dev, but in production with 200 concurrent users, it was a ticking bomb.

This article is for the developer who needs to take control. You'll learn how to configure a DataSource from scratch in Spring Boot, handle multiple databases, and avoid the pitfalls that the official docs gloss over. No fluff, just battle-tested patterns.

Why Go Programmatic?

Spring Boot's auto-configuration is great for simple apps, but it has limits. When you need to connect to multiple databases, use a custom connection pool, or load credentials from a secure vault, you have to take the wheel.

I've worked on a SaaS platform that needed separate databases for read and write operations. The auto-configuration could only handle one. We had to define two DataSources programmatically. Another time, a client required a connection pool with validation and leak detection—defaults weren't enough.

Programmatic configuration gives you full control. You can set pool sizes, timeouts, and even switch databases at runtime. The trade-off is more code and responsibility. But for production systems, it's often the right call.

DataSourceConfig.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Configuration
public class DataSourceConfig {

    @Bean
    @Primary
    public DataSource primaryDataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:postgresql://localhost:5432/mydb");
        config.setUsername("admin");
        config.setPassword("secret");
        config.setMaximumPoolSize(50);
        config.setConnectionTimeout(30000);
        config.setLeakDetectionThreshold(60000);
        return new HikariDataSource(config);
    }

    @Bean
    public DataSource secondaryDataSource() {
        // similar config for second DB
    }
}
💡Always Set Leak Detection
📊 Production Insight
In production, always set connectionTimeout and leakDetectionThreshold. Defaults can cause silent failures under load.
🎯 Key Takeaway
Programmatic configuration is essential when auto-configuration doesn't fit, especially for multiple databases or custom pool settings.

Step-by-Step: Creating a Programmatic DataSource

Let's build a production-ready DataSource configuration. Start with a @Configuration class and a @Bean method that returns a HikariDataSource.

First, define the HikariConfig. Set the JDBC URL, username, password, and pool size. Then create the DataSource.

But wait—where do you put credentials? Hardcoding is a security risk. Use environment variables or a vault. Spring Boot's Environment can inject values via @Value or @ConfigurationProperties.

DatabaseConfig.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Configuration
public class DatabaseConfig {

    @Value("${db.url}")
    private String url;

    @Value("${db.username}")
    private String username;

    @Value("${db.password}")
    private String password;

    @Bean
    @Primary
    public DataSource dataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl(url);
        config.setUsername(username);
        config.setPassword(password);
        config.setMaximumPoolSize(50);
        config.setConnectionTimeout(30000);
        config.setLeakDetectionThreshold(60000);
        config.setPoolName("MyPool");
        return new HikariDataSource(config);
    }

    @Bean
    public JpaProperties jpaProperties() {
        JpaProperties props = new JpaProperties();
        props.setShowSql(true);
        props.setDatabasePlatform("org.hibernate.dialect.PostgreSQLDialect");
        return props;
    }
}
⚠ Don't Forget JPA Properties
🎯 Key Takeaway
Use @Value to inject externalized credentials. Always define JPA properties alongside the DataSource to avoid conflicts.

Multiple DataSources: The Right Way

Handling multiple databases is where programmatic configuration shines. You define two DataSource beans, but you must mark one as @Primary and use @Qualifier for injection.

Here's the pattern: Create two @Bean methods, each returning a DataSource. Annotate the primary one with @Primary. Then, when injecting, use @Qualifier("nameOfBean").

But there's a gotcha: Spring Boot's auto-configuration for JPA will try to use the primary DataSource. For the second one, you need to create a separate EntityManagerFactory and TransactionManager.

MultiDataSourceConfig.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
@Configuration
public class MultiDataSourceConfig {

    @Primary
    @Bean(name = "primaryDataSource")
    @ConfigurationProperties(prefix = "app.datasource.primary")
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "secondaryDataSource")
    @ConfigurationProperties(prefix = "app.datasource.secondary")
    public DataSource secondaryDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Primary
    @Bean(name = "primaryEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory(
            EntityManagerFactoryBuilder builder,
            @Qualifier("primaryDataSource") DataSource dataSource) {
        return builder
                .dataSource(dataSource)
                .packages("com.example.primary.entity")
                .persistenceUnit("primary")
                .build();
    }

    @Bean(name = "secondaryEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean secondaryEntityManagerFactory(
            EntityManagerFactoryBuilder builder,
            @Qualifier("secondaryDataSource") DataSource dataSource) {
        return builder
                .dataSource(dataSource)
                .packages("com.example.secondary.entity")
                .persistenceUnit("secondary")
                .build();
    }

    @Primary
    @Bean(name = "primaryTransactionManager")
    public PlatformTransactionManager primaryTransactionManager(
            @Qualifier("primaryEntityManagerFactory") EntityManagerFactory emf) {
        return new JpaTransactionManager(emf);
    }

    @Bean(name = "secondaryTransactionManager")
    public PlatformTransactionManager secondaryTransactionManager(
            @Qualifier("secondaryEntityManagerFactory") EntityManagerFactory emf) {
        return new JpaTransactionManager(emf);
    }
}
🔥Use @ConfigurationProperties for Clean Config
📊 Production Insight
A common mistake is forgetting to set packagesToScan for each EntityManagerFactory. If you don't, Hibernate will scan all entities and fail.
🎯 Key Takeaway
For multiple DataSources, you need separate EntityManagerFactories and TransactionManagers. Use @Primary and @Qualifier to avoid ambiguity.

What the Official Docs Won't Tell You

The Spring Boot reference guide is comprehensive, but it leaves out some hard-earned lessons. Here are the gotchas I've encountered:

  1. Auto-configuration back off: If you define a single DataSource bean, Spring Boot's auto-configuration backs off. But if you define multiple, it still tries to configure a default EntityManagerFactory. You must explicitly disable JPA auto-configuration or provide your own.
  2. HikariCP defaults: The default maximumPoolSize is 10. In production, that's often too low. Also, connectionTimeout defaults to 30 seconds, which can cause long waits. Set both explicitly.
  3. DataSourceBuilder vs HikariConfig: DataSourceBuilder.create().build() returns a HikariDataSource if HikariCP is on the classpath. But you lose fine-grained control. Use HikariConfig directly for production tuning.
  4. @Primary is not optional: If you have multiple DataSources, you must mark one as @Primary. Otherwise, injection fails with 'NoUniqueBeanDefinitionException'.
  5. Closing the DataSource: Spring Boot does not automatically close programmatic DataSources. Use @PreDestroy on a @Bean method to call close(). Otherwise, you leak connections on shutdown.
  6. Transaction management: With multiple DataSources, you need separate transaction managers. Spring's @Transactional uses the primary one by default. Use @Transactional("transactionManagerName") to specify.
CleanupConfig.javaJAVA
1
2
3
4
5
6
7
8
9
10
@Configuration
public class CleanupConfig {

    @Bean(destroyMethod = "close")
    public DataSource dataSource() {
        HikariConfig config = new HikariConfig();
        // ... config
        return new HikariDataSource(config);
    }
}
⚠ Always define destroyMethod
📊 Production Insight
I once saw a production outage because a developer defined a DataSource bean without destroyMethod. The app shutdown left connections open, causing the database to hit max connections.
🎯 Key Takeaway
The official docs don't emphasize the need to handle shutdown, multiple DataSource pitfalls, or the importance of tuning HikariCP defaults.

Testing Your DataSource Configuration

Never deploy a DataSource configuration without testing it. Use Testcontainers to spin up a real database in your tests. This catches issues like wrong URLs, missing drivers, or insufficient pool sizes.

DataSourceTest.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@SpringBootTest
@Testcontainers
class DataSourceTest {

    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15");

    @DynamicPropertySource
    static void configureProperties(DynamicPropertyRegistry registry) {
        registry.add("db.url", postgres::getJdbcUrl);
        registry.add("db.username", postgres::getUsername);
        registry.add("db.password", postgres::getPassword);
    }

    @Autowired
    private DataSource dataSource;

    @Test
    void shouldConnect() throws SQLException {
        try (Connection conn = dataSource.getConnection()) {
            assertNotNull(conn);
        }
    }

    @Test
    void shouldHandleLoad() throws Exception {
        ExecutorService executor = Executors.newFixedThreadPool(20);
        for (int i = 0; i < 100; i++) {
            executor.submit(() -> {
                try (Connection conn = dataSource.getConnection()) {
                    Thread.sleep(100);
                } catch (Exception e) {
                    fail("Connection failed under load");
                }
            });
        }
        executor.shutdown();
        executor.awaitTermination(1, TimeUnit.MINUTES);
    }
}
💡Use Testcontainers, Not H2
📊 Production Insight
I've seen tests pass with H2 but fail in production because of PostgreSQL-specific SQL or driver differences. Testcontainers eliminates that risk.
🎯 Key Takeaway
Test your DataSource with Testcontainers to verify connectivity and load handling.
● Production incidentPOST-MORTEMseverity: high

The Case of the Vanishing Connections

Symptom
Users saw '500 Internal Server Error' and logs showed 'HikariPool-1 - Connection is not available, request timed out after 30000ms'.
Assumption
The developer assumed the default HikariCP settings were sufficient for production traffic.
Root cause
Auto-configured DataSource used a maximum pool size of 10, but the service had 50 concurrent requests, each holding a connection for 2 seconds. Connections queued up and timed out.
Fix
Replaced auto-configuration with a programmatic DataSource bean setting max pool size to 50 and adding connection timeout and leak detection thresholds.
Key lesson
  • Never trust default pool sizes in production—profile your connection usage.
  • Always set a connection timeout and leak detection threshold.
  • Use @PreDestroy to close the DataSource gracefully.
  • Monitor connection pool metrics via Actuator or JMX.
  • Test under realistic load before going live.
Production debug guideSymptom to Action4 entries
Symptom · 01
Application fails to start with 'Failed to configure a DataSource'
Fix
Check if spring.datasource.url is set or if a DataSource bean is defined. If using programmatic config, ensure the @Bean method is in a @Configuration class.
Symptom · 02
Connection timeout errors under load
Fix
Increase maximumPoolSize in HikariConfig. Also check for connection leaks by setting leakDetectionThreshold.
Symptom · 03
Multiple DataSources but wrong one is used
Fix
Mark one DataSource as @Primary. For others, use @Qualifier on injection points.
Symptom · 04
Database credentials change but app still uses old ones
Fix
If using programmatic config, ensure you're not caching the credentials. Use a secrets manager or refreshable properties.
★ Quick Debug Cheat SheetCommon DataSource issues and immediate actions.
Startup fails: 'Failed to configure a DataSource'
Immediate action
Add a DataSource @Bean
Commands
Check application.properties for spring.datasource.url
Verify @Configuration class is scanned
Fix now
Define a DataSource bean with url, username, password
Connection timeout+
Immediate action
Increase pool size
Commands
Check HikariConfig maximumPoolSize
Set connectionTimeout to 30000
Fix now
Set maximumPoolSize=50 and connectionTimeout=30000
Wrong database used+
Immediate action
Use @Primary
Commands
Check for multiple DataSource beans
Add @Primary to the main one
Fix now
Annotate primary DataSource with @Primary
FeatureAuto-ConfigurationProgrammatic Configuration
Ease of setupVery easy (just properties)Requires code
Multiple databasesNot supportedSupported
Custom pool tuningLimitedFull control
Security (external secrets)Possible with placeholdersFull control
TestingHarder to overrideEasy with Testcontainers
⚙ Quick Reference
5 commands from this guide
FileCommand / CodePurpose
DataSourceConfig.java@ConfigurationWhy Go Programmatic?
DatabaseConfig.java@ConfigurationStep-by-Step
MultiDataSourceConfig.java@ConfigurationMultiple DataSources
CleanupConfig.java@ConfigurationWhat the Official Docs Won't Tell You
DataSourceTest.java@SpringBootTestTesting Your DataSource Configuration

Key takeaways

1
Programmatic DataSource configuration gives you full control over connection pooling, multiple databases, and security.
2
Always set HikariCP's maximumPoolSize, connectionTimeout, and leakDetectionThreshold for production.
3
Use @Primary and @Qualifier for multiple DataSources, and provide separate EntityManagerFactory and TransactionManager beans.
4
Test with Testcontainers to catch environment-specific issues before deployment.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
How do you configure a DataSource programmatically in Spring Boot?
Q02SENIOR
What is the difference between DataSourceBuilder and HikariConfig?
Q03SENIOR
How do you configure two DataSources with Spring Boot?
Q01 of 03SENIOR

How do you configure a DataSource programmatically in Spring Boot?

ANSWER
Create a @Configuration class with a @Bean method that returns a DataSource. Use HikariConfig to set properties like JDBC URL, username, password, and pool size. Then return new HikariDataSource(config).
FAQ · 3 QUESTIONS

Frequently Asked Questions

01
How do I configure a DataSource without application.properties?
02
What is the best connection pool for Spring Boot?
03
How do I handle multiple databases in Spring Boot?
N
Naren Founder & Principal Engineer

20+ years shipping production Java in banking & fintech. Lessons pulled from things that broke in production.

Follow
Verified
production tested
July 15, 2026
last updated
2,398
articles · all by Naren
🔥

That's Hibernate & JPA. Mark it forged?

3 min read · try the examples if you haven't

Previous
Introduction to Spring Data Redis: CRUD Operations and Caching
23 / 28 · Hibernate & JPA
Next
Hibernate Relationship Mappings Deep Dive: @OneToOne, @OneToMany, @ManyToMany Best Practices