Home / Spring Boot / Data and Transactions / Connection Pool (HikariCP) and Flyway Migrations

Connection Pool (HikariCP) and Flyway Migrations

Two things every real project needs: a tuned connection pool, and versioned database migrations instead of letting Hibernate change your schema.

HikariCP: the default connection pool

Opening a database connection is slow. A pool keeps a fixed number of open connections and lends them to your code. Spring Boot uses HikariCP by default.

spring.datasource.url=jdbc:postgresql://localhost:5432/shop
spring.datasource.username=app
spring.datasource.password=${DB_PASSWORD}

spring.datasource.hikari.maximum-pool-size=10
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.idle-timeout=600000
spring.datasource.hikari.max-lifetime=1800000
PropertyMeaningDefault
maximum-pool-sizeMaximum connections in the pool10
minimum-idleIdle connections kept readysame as maximum
connection-timeoutMax wait (ms) for a free connection before an exception30000
idle-timeoutHow long an idle connection may stay (ms)600000
max-lifetimeMaximum life of a connection (ms). Keep it below the database limit1800000

Flyway: version control for your database

<dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-core</artifactId>
</dependency>
<!-- Flyway 10+ needs a database module, for example for PostgreSQL: -->
<dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-database-postgresql</artifactId>
</dependency>
src/main/resources/db/migration/
    V1__create_products.sql
    V2__add_category_column.sql
    V3__insert_default_data.sql
Naming: V<version>__<description>.sql   (two underscores)
-- V1__create_products.sql
CREATE TABLE products (
    id       BIGSERIAL PRIMARY KEY,
    name     VARCHAR(100)  NOT NULL,
    price    NUMERIC(10,2) NOT NULL
);

-- V2__add_category_column.sql
ALTER TABLE products ADD COLUMN category VARCHAR(50);
spring.flyway.enabled=true
spring.flyway.baseline-on-migrate=true      # for an existing database
spring.jpa.hibernate.ddl-auto=validate      # Hibernate only checks, Flyway changes

At startup Flyway checks the table flyway_schema_history, runs every new migration in order, and stores a checksum for each. Rules:

H2 console for local development

spring.h2.console.enabled=true
spring.datasource.url=jdbc:h2:mem:testdb
# open http://localhost:8080/h2-console

How HikariCP works inside

HikariPool
  ├─ ConcurrentBag of PoolEntry (connection + state): NOT_IN_USE / IN_USE / REMOVED / RESERVED   (lock-free, thread-local fast path)
  ├─ housekeeper thread every 30 s:  retire connections older than maxLifetime (with random variance), keep minimumIdle, detect clock jumps
  └─ addConnectionExecutor thread: creates new connections in the background

getConnection():  1. take an idle connection from the bag  2. none? wait up to connectionTimeout (30 s), while creating one if below maximumPoolSize
                  3. timeout ─► SQLTransientConnectionException: Connection is not available, request timed out after 30000ms
close() on the returned proxy does NOT close the socket. It returns the connection to the pool.

Pool size: the formula and the trap

spring.datasource.hikari.maximum-pool-size=10
spring.datasource.hikari.minimum-idle=10                       # fixed size pool (Hikari recommends min = max for steady load)
spring.datasource.hikari.leak-detection-threshold=20000        # log a stack trace if a connection is held longer than 20 s
spring.datasource.hikari.max-lifetime=1740000                  # 29 min: shorter than the database or proxy idle timeout (often 30 min)
spring.datasource.hikari.pool-name=shop-pool
spring.datasource.hikari.auto-commit=true
management.metrics.enable.hikaricp=true                        # hikaricp.connections.active / pending / timeout metrics
MetricAlarm when
hikaricp.connections.activeClose to the maximum for a long time
hikaricp.connections.pendingAbove 0 for more than a few seconds (threads are waiting)
hikaricp.connections.timeoutIncreases (requests failed to get a connection)
hikaricp.connections.usage (timer)p99 grows: transactions are getting long

Two databases in one application

@Configuration
public class DataSourceConfig {

    @Bean @Primary
    @ConfigurationProperties("app.datasource.orders")
    public DataSourceProperties ordersProps() { return new DataSourceProperties(); }

    @Bean @Primary
    public DataSource ordersDataSource(@Qualifier("ordersProps") DataSourceProperties props) {
        return props.initializeDataSourceBuilder().type(HikariDataSource.class).build();
    }

    @Bean
    @ConfigurationProperties("app.datasource.reports")
    public DataSourceProperties reportsProps() { return new DataSourceProperties(); }

    @Bean
    public DataSource reportsDataSource(@Qualifier("reportsProps") DataSourceProperties props) {
        return props.initializeDataSourceBuilder().type(HikariDataSource.class).build();
    }

    @Bean
    public JdbcTemplate reportsJdbc(@Qualifier("reportsDataSource") DataSource ds) { return new JdbcTemplate(ds); }
}
// For JPA on both, define an EntityManagerFactory and a TransactionManager per database with @EnableJpaRepositories(basePackages, entityManagerFactoryRef, transactionManagerRef).

Flyway workflow at startup

FlywayAutoConfiguration runs BEFORE the JPA EntityManagerFactory is created
  1. connect, acquire a lock (so two instances do not migrate at the same time)
  2. create table flyway_schema_history if missing
        columns: installed_rank, version, description, type, script, checksum, installed_by, installed_on, execution_time, success
  3. scan locations (classpath:db/migration) ─► list of migrations V1, V2, V3 ... and R__ repeatables
  4. validate:  checksum of every APPLIED migration must equal the file. Changed file ─► FlywayValidateException (startup fails)
  5. run the pending versions in order, each in its own transaction (PostgreSQL supports transactional DDL, MySQL does not)
  6. record each result in flyway_schema_history
  7. Hibernate starts and (with ddl-auto=validate) checks that entities match the new schema
-- V3__add_orders_customer_index.sql
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_orders_customer_id ON orders (customer_id);
-- CONCURRENTLY cannot run in a transaction: add   -- flyway:executeInTransaction=false   style config (executeInTransaction=false in a .conf file) for this file
spring.flyway.locations=classpath:db/migration,classpath:db/migration/{vendor}   # {vendor} = postgresql, mysql ... for special SQL
spring.flyway.validate-on-migrate=true
spring.flyway.clean-disabled=true            # NEVER allow clean (drops everything) outside local development
spring.flyway.out-of-order=false
spring.flyway.baseline-on-migrate=true
spring.flyway.baseline-version=1

Zero-downtime migrations: expand and contract

Goal: rename column  customer_name -> full_name  while old and new application versions run together (rolling update)
 Release 1 (expand):   ADD COLUMN full_name;  app writes BOTH columns, reads customer_name
 Migration:            backfill:  UPDATE ... SET full_name = customer_name WHERE full_name IS NULL   (in batches)
 Release 2:            app reads full_name (still writes both)
 Release 3 (contract): stop writing customer_name;   Migration: DROP COLUMN customer_name
Rules: only backward compatible steps per release. Never rename or drop in the same release that stops using the column.

Common mistake: Editing an old Flyway migration file after it ran on a database. The checksum no longer matches and startup fails. Always add a new versioned migration.

Interview questions

What is the default pool in Spring Boot?

HikariCP, chosen because it is small and very fast.

Flyway or Liquibase?

Both work well. Flyway uses plain SQL files and is simpler. Liquibase uses XML, YAML or SQL changelogs with more features like rollback.

Why not use ddl-auto=update?

It cannot handle renames, data changes or rollbacks safely and gives no history. Migrations are repeatable and reviewable.

What does 'Connection is not available, request timed out' mean?

All connections are in use and a thread waited connectionTimeout without getting one. Look for slow queries, long transactions, connection leaks (use leak-detection-threshold) or a pool that is too small for the real concurrency.

Why does Flyway fail with 'Validate failed: Migration checksum mismatch'?

An already applied migration file was changed. Restore the original file and add a new migration, or use flyway repair only when the change was intentional and safe.