This file documents key information about the project architecture, build commands, code style, and security practices.
Project overview
RotatingSecrets is a Java library (rotating-secrets
module) and demo app (demo module) for zero-downtime
database credential rotation in Kubernetes. When a secrets manager
(Vault, OpenBao, ESO) rotates database credentials by updating mounted
secret files, the library detects the change and updates connection
pools without dropping connections.
Commands
Build and test:
./gradlew build # compile, test, spotless check
./gradlew test # run all tests (both modules)
./gradlew :rotating-secrets:test # test only rotating-secrets module
./gradlew :demo:test # test only demo module
./gradlew test --tests "*ProviderTest" # run tests by class name
./gradlew test --tests "*ProviderTest.test*" # run tests by method patternCode quality:
./gradlew spotlessApply # auto-format (required before commit)
./gradlew dependencyCheckAnalyze # OWASP vulnerability scan (slow; fails at CVSS ≥ 7)External dependencies: Requires Kubernetes, secrets manager (Vault/OpenBao/ESO). Demo requires database.
Build uses Java 25 toolchain, compiles to Java 17 bytecode
(release = "17"). CI tests on Java 17, 21, and 25.
Key Entry Points
CredentialsProviderService— watches secret files and triggers credential updates (:rotating-secretsmodule)HikariCredentialsUpdater— implements HikariCP credential rotationUcpCredentialsUpdater— implements Oracle UCP credential rotationRotatingSecretsApplication— Spring Boot demo app wiring both pool implementations (:demomodule)
Architecture
Module structure
rotating-secrets/— reusable library; no Spring Boot plugin, produces a plain JARdemo/— Spring Boot application that depends onrotating-secretsand exercises both connection pools
Credential rotation flow
CredentialsProviderService is a @Scheduled
Spring service that polls two files (username,
password) from a Kubernetes-mounted directory (default
/var/run/secrets/database). On first read or any change it
calls setCredential(username, password) on every registered
UpdatableCredential<String> bean.
Two implementations are wired as named beans
(hikariUpdater, ucpUpdater) and
auto-registered via @Autowired @Qualifier setters on
CredentialsProviderService:
HikariCredentialsUpdater— implements bothUpdatableCredential<String>and HikariCP’sHikariCredentialsProvider. On update it swaps the immutableCredentialsobject and callssoftEvictConnections(). There is a deliberate circular dependency:HikariDataSourceConfigmust set theHikariCredentialsProvideron the config before creating theHikariDataSource, then inject theHikariDataSourceback into the updater for eviction support.UcpCredentialsUpdater— callssetUser()/setPassword()andrefreshConnectionPool()on the Oracle UCPPoolDataSource.
Key configuration properties
| Property | Default | Purpose |
|---|---|---|
k8s.secrets.path |
/var/run/secrets/database |
Directory with username and password
files |
k8s.secrets.refreshInterval |
30000 |
Poll interval (ms) |
spring.datasource.* |
— | HikariCP datasource (primary) |
spring.datasource.ucp.* |
— | Oracle UCP datasource |
Code style
Spotless enforces Google Java Format. Run
./gradlew spotlessApply before committing.
module-info.java is excluded from formatting.
Security patches
For CVE patch management, see the gradle-security-patch
skill. Use /gradle-security-patch to pin a CVE fix in the
version catalog.
Local Development
Running the demo:
The demo requires a running database and a secrets manager. To run locally with filesystem-based secrets:
mkdir -p /tmp/secrets/database
echo "dbuser" > /tmp/secrets/database/username
echo "dbpassword" > /tmp/secrets/database/password
./gradlew :demo:bootRun --args='--k8s.secrets.path=/tmp/secrets/database'Update the secret files to trigger rotation (the service polls every 30 seconds by default).
Dependency constraints
Spring Boot and Spring Cloud versions are coupled.
The demo module uses a Spring Cloud starter, and Spring Cloud’s
compatibility verifier aborts startup with
CompatibilityNotMetException if the Boot version is outside
its supported range. Always bump spring-boot and
spring-cloud together in
gradle/libs.versions.toml, not independently. Currently:
Spring Cloud 2025.1.2+ supports Spring Boot 4.1.x. (Dependabot PRs that
bump only one side of this pair will fail CI.)