Why reading files does not answer it
The instinct is to open application.yml, then application-prod.yml, then the Helm chart, and reason about which wins. That fails for a structural reason. Spring Boot configuration is assembled at runtime from sources that mostly do not live in the repository: environment variables injected by the platform, JVM flags in a base image, files mounted from a secret store, arguments added by a wrapper script. The precedence order is documented and worth knowing, and a summary of it sits in the auto-configuration page. But knowing the order tells you which source would win if present, not which sources are present on this pod.
So the strong answer starts with evidence from the process. The Environment already holds an ordered list of property sources and knows which one supplied each value. The job is to read that list.
Asking the process with the env endpoint
Actuator's env endpoint has a form that takes a property name. It returns the resolved value and then every property source, from highest precedence to lowest, with the value for that name attached to each source that has one. That second part is the whole answer: you can see that application.yml inside the jar says one thing, application-prod.yml says nothing, and the source called systemEnvironment says something else and sits above both.
kubectl port-forward deploy/orders 8081:8081
curl -s localhost:8081/actuator/env/spring.datasource.url
kubectl exec deploy/orders -- printenv | grep -i datasource
Two details make this work in practice. First, env is not exposed over the web by default, so production usually needs it enabled deliberately, ideally on a management port that only the cluster can reach. Second, since Spring Boot 3.0 the env and configprops endpoints mask every value by default, governed by show-values settings that accept never, always and when-authorized. Masking does not hide the source names, which are what you need, so even a fully masked response tells you where the value comes from. If you must see the value, when-authorized with an operator role is the defensible setting, not always.
The configprops endpoint complements this when the property is bound to a @ConfigurationProperties class. It shows the bean's bound fields and, for each input, the origin it was read from, which matters when the code reads a bound object rather than the raw key.
Relaxed binding is usually the answer
The most common culprit is an environment variable nobody recognised as the same property. Spring Boot's relaxed binding maps SPRING_DATASOURCE_URL to spring.datasource.url by replacing dots with underscores, removing dashes and upper-casing. A platform team that sets SPRING_DATASOURCE_URL in a shared deployment template has overridden your YAML, and a search of the repository for spring.datasource.url will never find it. OS environment variables rank above every application*.yml file, profile-specific ones included, so that override wins quietly.
Sources outside the manifest
When the winning property source is a system property or the command line and the Deployment manifest shows neither, look one layer down.
JAVA_TOOL_OPTIONS is read by the JVM itself, so a -Dspring.datasource.url=... inside it becomes a system property without appearing in the container's command. Base images and platform sidecars set this variable more often than people expect. SPRING_APPLICATION_JSON carries a block of inline JSON properties as a single variable, and it ranks above ordinary environment variables. An entrypoint script can append command-line arguments, which rank above all of these. spring.config.import and spring.config.additional-location, themselves settable by environment variable, can pull in a mounted file or a config tree from a secret volume.
Profiles add one more layer. SPRING_PROFILES_ACTIVE in the environment activates a profile file that may ship inside the jar, so a value can come from the repository through a file nobody thought was in use. When several profiles are active, the last one listed wins for keys they both define.
Starting from the files is the weak answer
The adequate answer recites the precedence list correctly. The strong answer treats the list as a hypothesis and the running process as the evidence. Reciting precedence answers which source would win; only the Environment answers which sources exist on that pod, and the gap between those two is exactly where an unexplained production value lives. A candidate who goes to actuator/env/<name> first, expects masked values and reads the source names anyway, and then checks printenv and JAVA_TOOL_OPTIONS, is describing a debugging session rather than a textbook.
The follow-through is part of the answer too. Once you find the property source, decide who owns it. Either move the value into a file the service repository controls, or document that the platform owns it. Otherwise the next incident repeats the same hunt.
Precedence tells you which source would win; the running Environment tells you which sources exist. Query the env endpoint for the property, read the source names even when the values are masked, and only then go hunting in templates.
© 2026 Preptima. Originally published at preptima.com (opens in a new tab).