A library and demo for zero-downtime database credential rotation in
Kubernetes environments. The rotating-secrets module is a
plain-Java core that reads fresh credentials from Kubernetes-mounted
secret files and notifies registered pool updaters when passwords are
rotated. The rotating-secrets-hikari and
rotating-secrets-ucp modules adapt HikariCP and Oracle
Universal Connection Pool (UCP) to it, and
rotating-secrets-spring creates and registers the provider
as a Spring bean. Each Spring-facing module depends only on its own
library plus spring-context. The demo module
is a Spring Boot application that exercises the library.
How It Works
When running in Kubernetes with a secrets manager (HashiCorp Vault, OpenBao, External Secrets Operator), database credentials can be automatically rotated. The library uses a publisher-subscriber pattern to notify connection pools when credentials change:
Secret Mounting: A secrets manager mounts credentials as files in a configurable directory (default:
/var/run/secrets/database/)Credential Monitoring:
CredentialsProviderServicewatches the secrets directory for changes usingWatchServiceand re-reads credentials when a change event firesPool Notification: When credentials change, all registered
UpdatableCredentialimplementations are notified:- HikariCP: Updates credentials and soft-evicts existing connections
- Oracle UCP: Updates credentials and refreshes the connection pool
Seamless Rotation: New connections automatically use updated credentials while existing connections continue unaffected until returned to the pool
Library: rotating-secrets
API
Connection Pool Support
| Feature | HikariCP | Oracle UCP |
|---|---|---|
| Default for Spring Boot | Yes | No |
| Oracle-specific features | No | Yes |
| Credential update | Via CredentialsProvider interface | Direct pool refresh |
| Connection eviction | Soft evict (graceful) | Pool refresh |
| FAN support | No | Yes |
| Application Continuity | No | Yes |
- HikariCP is the Spring Boot default and works well with any database. It’s lightweight and high-performance.
- Oracle UCP provides Oracle-specific features
essential for enterprise deployments:
- Fast Application Notification (FAN) for RAC/Data Guard failover
- Transparent Application Continuity for request replay
- Oracle Wallet integration
- Service-aware connections
Production Considerations
- Pool Tuning: Adjust pool sizes based on your workload and database capacity
- Monitoring: Both HikariCP and UCP expose metrics via JMX/MXBeans
- Fail-Fast: The application throws RuntimeException if secrets cannot be read
- RBAC: Ensure the pod has read permissions on mounted secret volumes
- FAN Events: For Oracle RAC, enable FAN in UCP configuration
- Fallback Interval:
k8s.secrets.refresh-interval(default30s) is a safety-net timeout — credentials are re-checked after this interval even if no watch event fires
Architecture
Component Overview
Deployment Architecture
Demo Application
Project Structure
rotating-secrets/ # Core library (depends only on SLF4J)
└── src/main/java/
├── module-info.java
└── com/maybeitssquid/rotatingsecrets/
├── UpdatableCredential.java # Interface for credential update notification
├── CredentialsProviderService.java # Reads secrets, notifies registered updaters
└── CredentialRotationException.java # Exception for rotation failures
rotating-secrets-hikari/ # HikariCP adapter (HikariCP, spring-context)
└── src/main/java/
├── module-info.java
└── com/maybeitssquid/rotatingsecrets/hikari/
├── HikariCredentialsUpdater.java # HikariCP credential rotation handler
└── HikariDataSourceConfig.java # HikariCP configuration (primary)
rotating-secrets-ucp/ # Oracle UCP adapter (UCP, spring-context)
└── src/main/java/
├── module-info.java
└── com/maybeitssquid/rotatingsecrets/ucp/
├── UcpCredentialsUpdater.java # Oracle UCP credential rotation handler
└── UcpDataSourceConfig.java # Oracle UCP configuration
rotating-secrets-spring/ # Spring configuration (spring-context)
└── src/main/java/
├── module-info.java
└── com/maybeitssquid/rotatingsecrets/spring/
└── RotatingSecretsConfig.java # Creates and starts the provider; registers every updater bean
demo/ # Spring Boot demo application
└── src/main/
├── java/com/maybeitssquid/rotatingsecrets/
│ ├── DemoRotatingSecretsApplication.java # Entry point with scheduling enabled
│ ├── DemoDatabasePollingService.java # Exercises the connection pool
│ └── DemoQueryResult.java # Query result model
└── resources/
└── application.properties # Datasource and pool configuration
Prerequisites
- Kubernetes cluster with secrets management
Configuration
application.properties
spring.application.name=RotatingSecrets
# Kubernetes secrets path (mounted by Vault Agent or CSI driver)
k8s.secrets.path=/var/run/secrets/database
k8s.secrets.refresh-interval=30s # fallback timeout if no watch event fires
# Common datasource settings
spring.datasource.url=jdbc:oracle:thin:@//host:1521/service
spring.datasource.driver-class-name=oracle.jdbc.OracleDriver
spring.datasource.username=myuser
spring.datasource.password=mypassword
# HikariCP settings (primary datasource)
spring.datasource.hikari.pool-name=HikariRotatingSecrets
spring.datasource.hikari.maximum-pool-size=10
spring.datasource.hikari.minimum-idle=2
spring.datasource.hikari.idle-timeout=30000
spring.datasource.hikari.connection-timeout=20000
spring.datasource.hikari.max-lifetime=1800000
# Oracle UCP settings
spring.datasource.ucp.pool-name=UCPRotatingSecrets
spring.datasource.ucp.url=${spring.datasource.url}
spring.datasource.ucp.connection-factory-class-name=${spring.datasource.driver-class-name}
spring.datasource.ucp.user=${spring.datasource.username}
spring.datasource.ucp.password=${spring.datasource.password}
spring.datasource.ucp.initial-pool-size=2
spring.datasource.ucp.min-pool-size=2
spring.datasource.ucp.max-pool-size=10
spring.datasource.ucp.connection-wait-timeout=20
spring.datasource.ucp.inactive-connection-timeout=30
spring.datasource.ucp.max-connection-reuse-time=1800
Secret Files
The demo expects these files in the secrets directory:
| File | Description |
|---|---|
username |
Database username |
password |
Database password |
Building
./gradlew buildRunning
Local Development
Create the secret files in a local directory:
mkdir -p /tmp/secrets/database
echo "myuser" > /tmp/secrets/database/username
echo "mypassword" > /tmp/secrets/database/passwordRun with the custom secrets path:
./gradlew :demo:bootRun --args='--k8s.secrets.path=/tmp/secrets/database'Kubernetes Deployment
Mount your secrets as a volume at the configured path. Example with Vault Agent:
apiVersion: v1
kind: Pod
metadata:
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/agent-inject-secret-username: "database/creds/myapp"
vault.hashicorp.com/agent-inject-template-username: |
{{- with secret "database/creds/myapp" -}}
{{ .Data.username }}
{{- end }}
vault.hashicorp.com/agent-inject-secret-password: "database/creds/myapp"
vault.hashicorp.com/agent-inject-template-password: |
{{- with secret "database/creds/myapp" -}}
{{ .Data.password }}
{{- end }}
spec:
containers:
- name: app
image: rotating-secrets:latestTesting
./gradlew testTechnologies
| Component | Version |
|---|---|
| Java | 25 (toolchain; runs on 17+) |
| Gradle | 9.6.1 |
| Spring Boot | 4.0.6 |
| Spring Cloud Vault | 2025.1.1 |
| HikariCP | (via Spring Boot) |
| Resilience4j | (via Spring Cloud) |
| Oracle UCP | (via oracle-jdbc) |
| Oracle JDBC | ojdbc11 |
| JaCoCo | 0.8.14 |
Links
- GitHub repository
- Javadoc: rotating-secrets
- Javadoc: rotating-secrets-hikari
- Javadoc: rotating-secrets-ucp
- Javadoc: rotating-secrets-spring
- Javadoc: demo
- Test Results: rotating-secrets
- Test Results: rotating-secrets-hikari
- Test Results: rotating-secrets-ucp
- Test Results: rotating-secrets-spring
- Test Results: demo
- Coverage Report: rotating-secrets
- Coverage Report: rotating-secrets-hikari
- Coverage Report: rotating-secrets-ucp
- Coverage Report: rotating-secrets-spring
- Coverage Report: demo
- Code of Conduct
- Agents guidance