A Java application should never hold secrets in source code, in the jar or in a committed properties file. Load them at start from a secrets manager or a mounted file, keep them out of logs and error messages, limit their lifetime with rotation, and scan the repository for any that slipped through.
Why is this still a common failure?
OWASP's secrets management guidance notes that many organizations hardcode secrets in source code in plaintext and scatter them through configuration files and configuration management tools. It calls for centralizing storage, provisioning, auditing and rotation, and observes that services often share the same secrets, which makes it hard to tell which one leaked.
Java adds its own habits. Properties files bundled in the jar, application.yml committed with a database password, a static final String API key, and Maven or Gradle settings with registry tokens all end up in repositories and build artifacts that many people can read.
What do the Spring docs suggest?
The Spring Boot reference on externalized configuration discusses environment variables and says storing config values in them has drawbacks, especially for values that must stay secret. As an alternative it points to mapping configuration into mounted data volumes, for example Kubernetes ConfigMaps and Secrets. It describes a config tree pattern in which multiple files are written to a directory tree, and each file name becomes a property. You import it with the spring.config.import property pointing at a configtree: location. A file named password inside a folder named myapp then provides myapp.password to the application.
That keeps the secret out of the jar and the repository, and the file permissions can be restricted to the service account.
A constructed example: a payments service ships with db.password=changeme in a properties file for local development. Production overrides it with an environment variable. A CI step later dumps the environment into a build log for debugging, and the password is now in a log system with wide read access. Moving to a mounted secret file, removing the default from the repository and failing startup if the file is missing removes both failure modes.
What about lifetime and rotation?
OWASP describes rotating secrets and, as an alternative, dynamic secrets created per consumer and tied to a lease. Invalidation follows the provider's lease expiry or explicit revocation, since stopping the consumer alone does not revoke the secret. It also notes that using a secret from anywhere other than the consumer's address is easy to detect when secrets are dynamic. For Java, that means database connection credentials and API keys fetched from a manager at start, with a restart or reload path for rotation, instead of long-lived static values.
Where else do secrets leak in Java?
- Logs and exceptions: connection strings and request headers appear in stack traces. Mask them in logging configuration.
- Object printing: a
toStringthat includes a token field will print it. Exclude secret fields. - Heap dumps and actuator endpoints: restrict and sanitize them.
- Build files and CI: registry tokens in build settings, and environment dumps.
- Client side: never embed service credentials in an Android app or a desktop client.
What to do
- Scan the repository and its history for credentials and rotate every one you find. Safeguard's secrets scanning can run on pull requests to catch new ones.
- Remove default secrets from committed files. Fail startup when a required secret is absent.
- Move secrets to a manager or mounted files, and use Spring's config tree import or your framework's equivalent.
- Set read permissions on mounted files to the service account only.
- Mask sensitive properties in logs, actuator output and error pages.
- Give each service its own credentials with the minimum database and API privileges.
- Define a rotation schedule and test that a rotation does not require a code change.
Quick check
Are environment variables a safe place for secrets?
They are better than source code, but the Spring docs say they have drawbacks for secret values. Mounted files or a secrets manager give you tighter control and auditing.
Is deleting a committed secret enough?
No. It stays in Git history and in any clone. Rotate it, then remove it.
Sources
Last reviewed: 18 October 2026.