This guide provides step-by-step instructions for configuring an Ebean Database bean
using Avaje Inject (@Factory / @Bean), backed by a PostgreSQL datasource built
with Ebean's DataSourceBuilder. Follow every step in order. This is Step 2 of 2.
- Step 1 complete:
pom.xmlalready includesebean-postgres,ebean-maven-plugin, andquerybean-generator(seeadd-ebean-postgres-maven-pom.md) - Avaje Inject is on the classpath (e.g.
io.avaje:avaje-inject) - A configuration source is available at runtime (e.g.
avaje-configreadingapplication.ymlor environment variables) - The following configuration keys are resolvable at runtime (adapt names to your project):
Key Description db_urlJDBC URL for the master/write connection db_userDatabase username db_passDatabase password db_master_min_connectionsMinimum pool size (default: 1) db_master_initial_connectionsInitial pool size at startup — set high to pre-warm on pod start (see K8s note below) db_master_max_connectionsMaximum pool size (default: 200)
Look for an existing Avaje Inject @Factory-annotated class in the project
(often named AppConfig, DatabaseConfig, or similar). If one exists, add the new
@Bean method to it. If none exists, create one:
package com.example.configuration;
import io.avaje.inject.Bean;
import io.avaje.inject.Factory;
@Factory
class DatabaseConfig {
// beans will be added in the steps below
}Add the following @Bean method to the @Factory class. This creates an Ebean
Database backed by a single master (read-write) PostgreSQL datasource.
import io.ebean.Database;
import io.ebean.datasource.DataSourceBuilder;
@Bean
Database database() {
var dataSource = DataSourceBuilder.create()
.url(/* resolve from config, e.g.: */ Config.get("db_url"))
.username(Config.get("db_user"))
.password(Config.get("db_pass"))
.driver("org.postgresql.Driver")
.schema("myschema") // set to your target schema
.applicationName("my-app") // visible in pg_stat_activity
.minConnections(Config.getInt("db_master_min_connections", 1))
.initialConnections(Config.getInt("db_master_initial_connections", 10))
.maxConnections(Config.getInt("db_master_max_connections", 200));
return Database.builder()
.name("db") // logical name for this Database instance
.dataSourceBuilder(dataSource)
.build();
}| Field | Notes |
|---|---|
url |
Full JDBC URL, e.g. jdbc:postgresql://host:5432/dbname |
schema |
The Postgres schema Ebean should use (omit if using public) |
applicationName |
Shown in pg_stat_activity.application_name; helps with DB-side diagnostics |
name("db") |
Logical Ebean database name; relevant if multiple Database instances exist |
minConnections |
Connections kept open at all times; pool will not shrink below this |
initialConnections |
Connections opened at startup; see K8s warm-up note below |
maxConnections |
Hard upper limit on concurrent connections |
When a pod starts in Kubernetes it will receive live traffic as soon as it passes readiness checks — often before the connection pool has had a chance to grow to handle the load. This can cause latency spikes on the first wave of requests while the pool expands one connection at a time.
Use initialConnections to pre-warm the pool at startup so it is already sized
for peak load when the pod goes live:
minConnections: 2 ← floor; pool will shrink back here when idle
initialConnections: 20 ← opened at pod start, before first request arrives
maxConnections: 50 ← hard ceiling
The lifecycle is:
- Pod starts — pool opens
initialConnectionsconnections immediately. - Pod receives traffic — pool is already at capacity; no growth latency.
- Traffic drops — idle connections are closed; pool trims back toward
minConnections. - Next traffic spike — pool grows again up to
maxConnectionson demand.
Set initialConnections to a value high enough that the pool does not need to grow
during the first minute of live traffic. A common starting point is 50–75% of
maxConnections.
Rather than calling Config.get(...) inline, inject a typed config helper or the
Avaje Configuration bean if one is available. This makes the factory testable and
keeps the wiring explicit. For example:
@Bean
Database database(Configuration config) {
String url = config.get("db_url");
String user = config.get("db_user");
String pass = config.get("db_pass");
int min = config.getInt("db_master_min_connections", 1);
int init = config.getInt("db_master_initial_connections", 10);
int max = config.getInt("db_master_max_connections", 200);
var dataSource = DataSourceBuilder.create()
.url(url)
.username(user)
.password(pass)
.driver("org.postgresql.Driver")
.schema("myschema")
.applicationName("my-app")
.minConnections(min)
.initialConnections(init)
.maxConnections(max);
return Database.builder()
.name("db")
.dataSourceBuilder(dataSource)
.skipDataSourceCheck(true)
.build();
}If the project has a dedicated config-wrapper class (a @Component that reads config
keys), accept it as a parameter instead of Configuration.
For production services that have a separate read-replica, add a second
DataSourceBuilder for read-only queries and wire it via
readOnlyDataSourceBuilder(...). The read-only datasource:
- Uses
readOnly(true)andautoCommit(true)(Ebean routes read queries there automatically) - Typically has a higher max connection count than the master
- Benefits from a prepared-statement cache (
pstmtCacheSize)
@Bean
Database database(Configuration config) {
String masterUrl = config.get("db_url");
String readOnlyUrl = config.get("db_url_readonly");
String user = config.get("db_user");
String pass = config.get("db_pass");
var masterDataSource = buildDataSource(user, pass)
.url(masterUrl)
.minConnections(config.getInt("db_master_min_connections", 1))
.initialConnections(config.getInt("db_master_initial_connections", 10))
.maxConnections(config.getInt("db_master_max_connections", 50));
var readOnlyDataSource = buildDataSource(user, pass)
.url(readOnlyUrl)
.readOnly(true)
.autoCommit(true)
.pstmtCacheSize(250) // cache up to 250 prepared statements per connection
.maxInactiveTimeSecs(600) // close idle connections after 10 minutes
.minConnections(config.getInt("db_readonly_min_connections", 2))
.initialConnections(config.getInt("db_readonly_initial_connections", 10))
.maxConnections(config.getInt("db_readonly_max_connections", 200));
return Database.builder()
.name("db")
.dataSourceBuilder(masterDataSource)
.readOnlyDataSourceBuilder(readOnlyDataSource)
.build();
}
private static DataSourceBuilder buildDataSource(String user, String pass) {
return DataSourceBuilder.create()
.username(user)
.password(pass)
.driver("org.postgresql.Driver")
.schema("myschema")
.applicationName("my-app")
.addProperty("prepareThreshold", "2"); // PostgreSQL: server-side prepared statements
}| Key | Description | Default |
|---|---|---|
db_url_readonly |
JDBC URL for the read replica | — |
db_master_initial_connections |
Initial master pool size at startup | 10 |
db_readonly_min_connections |
Minimum pool size | 2 |
db_readonly_initial_connections |
Initial pool size at startup | same as min |
db_readonly_max_connections |
Maximum pool size | 20 |
If the project uses Ebean's built-in DB migration runner to apply SQL migrations on
startup, enable it on the DatabaseBuilder:
return Database.builder()
.name("db")
.dataSourceBuilder(dataSource)
.runMigration(true) // run pending migrations on startup
.build();This is equivalent to setting ebean.migration.run=true in application.properties
but is preferred because it keeps all database configuration in one place. To make it
conditional (e.g. only in non-production environments):
.runMigration(config.getBoolean("db.runMigrations", false))See the DB migration generation guide (add-ebean-db-migration-generation.md) for
full details on generating and managing migration files.
For advanced connection pool configuration, production deployment patterns, and connection validation best practices, see the ebean-datasource guides:
- Creating DataSource Pools — Covers read-only pools (
readOnly(true)+autoCommit(true)), Kubernetes deployment strategies usinginitialConnections, and AWS Lambda optimization - AWS Aurora Read-Write Split — Setting up dual DataSources with Aurora reader and writer endpoints, including Ebean secondary datasource routing
- Connection Validation Best Practices — Why
Connection.isValid()is the recommended default and when (rarely) explicitheartbeatSqlis needed
-
Start the application (or run
mvn test -pl <your-module>). -
Look for log output similar to:
INFO o.a.datasource.pool.ConnectionPool - DataSourcePool [db] autoCommit[false] min[1] max[5] INFO io.ebean.internal.DefaultContainer - DatabasePlatform name:db platform:postgres -
If you see
DataSourcePoolandDatabasePlatformlog lines, Ebean is connected and the database bean is wired correctly.
| Symptom | Likely cause | Fix |
|---|---|---|
ClassNotFoundException: org.postgresql.Driver |
PostgreSQL JDBC driver missing | Add org.postgresql:postgresql dependency (see Step 1 guide) |
Cannot connect to database at startup |
DB unreachable but skipDataSourceCheck is false |
Set .skipDataSourceCheck(true) |
| Ebean enhancement warnings in logs | ebean-maven-plugin not configured |
Complete Step 1 guide |
NullPointerException reading config key |
Config key not defined | Add the key to application.yml or environment |