Skip to main content

PostgreSQL AWS starter

jeap-spring-boot-postgresql-aws-starter simplifies connecting a jEAP Spring Boot service to an AWS RDS / Aurora PostgreSQL database. RDS provides fully managed relational databases on AWS; Aurora follows a single-primary replication model where one writer instance handles writes and up to 15 read-only replicas can offload read traffic. The starter wires up IAM authentication, SSL, sensible HikariCP defaults and — for multi-instance clusters — read-replica transaction routing, so services do not have to assemble this configuration themselves.

When this starter is active, spring.datasource.* is ignored; the datasource is configured under the jeap.datasource.* prefix instead.

Enabling the starter

The starter is disabled by default and is switched on with a property (the Maven dependency version is managed by the jEAP Spring Boot parent):

jeap:
postgresql:
aws:
enabled: true

How it works

JeapPostgreSQLAWSDataSourceAutoConfig runs before Spring's DataSourceAutoConfiguration and builds a HikariCP datasource. Two ingredients are central:

  • AWS Advanced JDBC WrapperHikariDataSourceFactory wraps the JDBC URL as jdbc:aws-wrapper:postgresql://… and uses AwsWrapperDataSource over PGSimpleDataSource. The wrapper understands the RDS cluster topology and provides IAM authentication and faster failover, so an RDS Proxy is no longer required. The default wrapper plugins are auroraConnectionTracker, failover, efm2, iam. (The wrapper is the default and the only supported mode; the pre-wrapper logic has been removed.)
  • IAM authentication — instead of a password, the application authenticates with a short-lived IAM token generated automatically by the wrapper's iam plugin. Because the default RDS IAM token lifetime is 15 minutes, the starter sets HikariCP max-lifetime=840000 (14 min) so connections rotate before their token expires. The AWS credentials used to mint the token are read from the standard environment (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_DEFAULT_REGION, optionally AWS_PROFILE).

For local development with H2, the factory detects the test database (jdbc:h2: URL or org.h2.Driver) and builds a plain HikariCP datasource without the wrapper, since the wrapper is not H2-compatible. A password is then required (there is no IAM token locally), and the Hikari schema must be set to the H2 default (e.g. PUBLIC) because the starter otherwise defaults the schema to data.

Inferred URL, username and database name

If jeap.datasource.url is unset, the JDBC URL is built from jeap.datasource.aws.hostname / .port / .database-name, with the SSL query string ?ssl=true,sslMode=verify-full,sslfactory=org.postgresql.ssl.DefaultJavaSSLFactory appended. Unset names are derived from spring.application.name, snake-cased: the username defaults to {app}_db_rwa (primary), {app}_db_ro (replica), and the database name to {app}_db.

Configuration

jeap:
postgresql:
aws:
enabled: true
datasource:
url: "jdbc:postgresql://my-cluster.rds.amazonaws.com:5432/my_db?ssl=true,sslMode=verify-full,sslfactory=org.postgresql.ssl.DefaultJavaSSLFactory"
username: my_db_rwa # optional; inferred from app name if omitted
aws:
region: eu-central-2
PropertyDefaultDescription
jeap.postgresql.aws.enabledfalseEnable the starter
jeap.datasource.urlinferredFull JDBC URL of the read/write endpoint
jeap.datasource.username{app}_db_rwaDB username (inferred from spring.application.name)
jeap.datasource.passwordOnly used for local H2 testing; ignored when the AWS wrapper is active (IAM token auth is always used for RDS)
jeap.datasource.aws.regioneu-central-2AWS region of the database
jeap.datasource.aws.hostnameRDS endpoint host (used to build the URL when url unset)
jeap.datasource.aws.port5432RDS port
jeap.datasource.aws.database-name{app}_dbDatabase name (inferred from app name if omitted)
jeap.datasource.aws.wrapper.target-data-source-properties.*see belowProperties passed to the wrapper's target datasource (camelCase)
jeap.datasource.hikari.*see belowHikariCP pool tuning for the primary (writer)

Starter-applied Hikari defaults for the primary pool: schema=data, maximum-pool-size=4, minimum-idle=0, keepalive-time=120000, pool-name=hikari-cp-rw, max-lifetime=840000. JPA's hibernate.default_schema defaults to data. The wrapper target property ...wrapper.target-data-source-properties.wrapperPlugins defaults to auroraConnectionTracker,failover,efm2,iam.

Read replicas

In a multi-instance Aurora cluster, read-only replicas can serve read traffic and relieve the writer. Replicas are opt-in:

jeap:
datasource:
replica:
enabled: true
url: "jdbc:postgresql://my-cluster-ro.rds.amazonaws.com:5432/my_db?ssl=true,sslMode=verify-full,sslfactory=org.postgresql.ssl.DefaultJavaSSLFactory"
username: my_db_ro # optional; inferred as {app}_db_ro
aws:
region: eu-central-2

When jeap.datasource.replica.enabled=true, RDSReadOnlyReplicaAutoConfiguration builds a second HikariCP datasource (pool-name=hikari-cp-ro, username defaulting to {app}_db_ro) under jeap.datasource.replica.* (with .aws.* and .hikari.* subtrees mirroring the primary). A @Primary ReadReplicaAwareTransactionRoutingDataSource then exposes both as writer and reader targets and routes read-only top-level transactions to the replica. The routing primitive and the @TransactionalReadReplica annotation that triggers it are provided by jeap-spring-boot-tx — see that page for the routing semantics.

Eventual consistency: the replica page cache is updated asynchronously, so a replica may return slightly stale data (on the PROD application platform the AuroraReplicaLag is roughly 15 ms). Route to a replica only for use cases that tolerate this — dashboards, UIs, non-time-critical read endpoints — and never for read-after-write patterns where a consumer must immediately see just-written data. This starter wires a single reader DataSource per application; if the reader endpoint fronts multiple physical Aurora replicas, consecutive reads can still land on different replicas, and since replicas are not updated in lockstep they can return mutually inconsistent (diverging) data.

PropertyDefaultDescription
jeap.datasource.replica.enabledfalseEnable the read-replica datasource and routing
jeap.datasource.replica.urlinferredJDBC URL of the read-only endpoint
jeap.datasource.replica.username{app}_db_roReplica DB username
jeap.datasource.replica.aws.hostnameRead-only endpoint host (used when url unset)
jeap.datasource.replica.aws.port5432Read-only endpoint port
jeap.datasource.replica.aws.database-namefrom primaryReplica database name; falls back to the primary's
jeap.datasource.replica.hikari.*see aboveHikariCP tuning for the replica pool

Common patterns and pitfalls

  • spring.datasource.* is ignored — always configure under jeap.datasource.*.
  • Token expiry — keep Hikari max-lifetime below the 15-minute IAM token lifetime (the default 840000 ms already does this); do not raise it carelessly.
  • Local H2 — set a password and hikari.schema: PUBLIC (plus the matching hibernate.default_schema), since the wrapper and IAM auth do not apply locally.
  • open-in-view — Spring Boot enables spring.jpa.open-in-view by default, which keeps the first transaction open across a request; this can trip the read-only/read-write consistency check in jeap-spring-boot-tx. Annotate the entry method with @Transactional to mark the top-level transaction read-write when needed.