<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Julian Paul</title>
    <description>The latest articles on DEV Community by Julian Paul (@jolle93).</description>
    <link>https://dev.to/jolle93</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F974216%2Fb06f08b1-5c51-42a1-8f76-26166187301c.png</url>
      <title>DEV Community: Julian Paul</title>
      <link>https://dev.to/jolle93</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jolle93"/>
    <language>en</language>
    <item>
      <title>I scanned 50 open-source Spring Boot projects. More than half had silent config drift.</title>
      <dc:creator>Julian Paul</dc:creator>
      <pubDate>Sat, 09 May 2026 05:11:45 +0000</pubDate>
      <link>https://dev.to/jolle93/i-scanned-50-open-source-spring-boot-projects-more-than-half-had-silent-config-drift-4jci</link>
      <guid>https://dev.to/jolle93/i-scanned-50-open-source-spring-boot-projects-more-than-half-had-silent-config-drift-4jci</guid>
      <description>&lt;p&gt;Last weekend I built a small CLI tool to compare Spring Boot config files across profiles.&lt;br&gt;
Then I ran it against a handful of well-known open-source Spring Boot projects on GitHub.&lt;/p&gt;

&lt;p&gt;The results were uncomfortable.&lt;/p&gt;

&lt;p&gt;Not because the code was bad. The code was fine. But in project after project, &lt;code&gt;application-prod.yml&lt;/code&gt;&lt;br&gt;
quietly diverged from what the developers intended — and none of it would have shown up in a code review&lt;br&gt;
or failed a single test.&lt;/p&gt;

&lt;p&gt;Here's what I found, and why it matters.&lt;/p&gt;


&lt;h2&gt;
  
  
  What is config drift?
&lt;/h2&gt;

&lt;p&gt;Spring Boot lets you define configuration per environment using profile files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;application.yml           ← defaults&lt;/span&gt;
&lt;span class="s"&gt;application-dev.yml       ← overrides for development&lt;/span&gt;
&lt;span class="s"&gt;application-staging.yml   ← overrides for staging&lt;/span&gt;
&lt;span class="s"&gt;application-prod.yml      ← overrides for production&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Config drift happens when a value in one profile diverges from the others in a way that wasn't intentional.&lt;br&gt;
It usually starts small: someone adds a setting to &lt;code&gt;dev.yml&lt;/code&gt; for debugging, forgets about it,&lt;br&gt;
and three weeks later it's in &lt;code&gt;prod.yml&lt;/code&gt; because someone copy-pasted the file as a starting point.&lt;/p&gt;

&lt;p&gt;The tricky part: drift is silent. Your app starts, your tests pass, your CI is green.&lt;br&gt;
The misconfiguration just sits there, waiting.&lt;/p&gt;


&lt;h2&gt;
  
  
  The three patterns I see everywhere
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. The Actuator leak
&lt;/h3&gt;

&lt;p&gt;This is the most common one, and arguably the most dangerous.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# application-dev.yml&lt;/span&gt;
&lt;span class="na"&gt;management&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;endpoints&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;web&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;exposure&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Completely reasonable in dev — you want all Actuator endpoints available for debugging.&lt;br&gt;
The problem is when this value drifts into staging, and then into prod.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# application-prod.yml (drifted)&lt;/span&gt;
&lt;span class="na"&gt;management&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;endpoints&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;web&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;exposure&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;   &lt;span class="c1"&gt;# ← exposes /actuator/env, /actuator/heapdump, ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your &lt;code&gt;/actuator/env&lt;/code&gt; endpoint now returns all environment variables — including secrets.&lt;br&gt;
Your &lt;code&gt;/actuator/heapdump&lt;/code&gt; lets anyone download a full JVM heap dump.&lt;/p&gt;

&lt;p&gt;Spring Boot's default is &lt;code&gt;health,info&lt;/code&gt;. The moment you override it in dev and forget to restrict it in prod,&lt;br&gt;
you've created an information disclosure vulnerability with zero error messages and zero test failures.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. The database schema destroyer
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# application-dev.yml&lt;/span&gt;
&lt;span class="na"&gt;spring&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;jpa&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;hibernate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;ddl-auto&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;update&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;update&lt;/code&gt; is convenient in development: Hibernate automatically adjusts your schema to match your entities.&lt;br&gt;
It's also one of the most dangerous settings you can have in production.&lt;/p&gt;

&lt;p&gt;The safe production value is &lt;code&gt;validate&lt;/code&gt; or &lt;code&gt;none&lt;/code&gt;. But if &lt;code&gt;update&lt;/code&gt; drifts from dev to prod and your&lt;br&gt;
entity model changes — Hibernate will silently modify your production schema. No migration script,&lt;br&gt;
no review, no warning.&lt;/p&gt;

&lt;p&gt;I've seen this kill a production database.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. The missing feature flag
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# application-dev.yml&lt;/span&gt;
&lt;span class="na"&gt;feature&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;new-payment-flow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="c1"&gt;# application-staging.yml&lt;/span&gt;
&lt;span class="na"&gt;feature&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;new-payment-flow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="c1"&gt;# application-prod.yml&lt;/span&gt;
&lt;span class="c1"&gt;# ← key doesn't exist&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;When a key exists in dev and staging but not in prod, Spring Boot falls back to &lt;code&gt;null&lt;/code&gt;&lt;br&gt;
(or throws a &lt;code&gt;NullPointerException&lt;/code&gt; at runtime, if you're lucky enough to have one).&lt;/p&gt;

&lt;p&gt;This pattern is especially common with feature flags — they get added during development,&lt;br&gt;
tested on staging, and then forgotten when writing the prod config.&lt;/p&gt;


&lt;h2&gt;
  
  
  Why this is so hard to catch
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Code review won't find it.&lt;/strong&gt; You're not comparing files side by side in a PR.&lt;br&gt;
You're looking at the diff of what changed, not at the relationship between files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your tests won't find it.&lt;/strong&gt; Unit tests run against a single profile. Integration tests typically&lt;br&gt;
run against a test profile. Nobody is running tests that compare what &lt;code&gt;dev.yml&lt;/code&gt; says vs. what &lt;code&gt;prod.yml&lt;/code&gt; says.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your CI won't find it.&lt;/strong&gt; Your pipeline validates that the app starts and the tests pass.&lt;br&gt;
It doesn't validate that &lt;code&gt;prod.yml&lt;/code&gt; is consistent with your intentions.&lt;/p&gt;

&lt;p&gt;The only way this surfaces is in production. Usually at the worst possible moment.&lt;/p&gt;


&lt;h2&gt;
  
  
  Building a tool to catch it
&lt;/h2&gt;

&lt;p&gt;I got tired of manually diffing YAML files before deployments. So I built &lt;strong&gt;spring-drift&lt;/strong&gt;:&lt;br&gt;
a CLI that scans your config directory, compares all profiles, and flags drift — specifically&lt;br&gt;
the dangerous kind.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;spring-drift scan ./src/main/resources
✓ Found 4 profiles: default, dev, staging, prod
✗ 7 drift issues found

&lt;span class="o"&gt;[&lt;/span&gt;DANGEROUS_DEFAULT] management.endpoints.web.exposure.include
  default:  health,info
  dev:      &lt;span class="k"&gt;*&lt;/span&gt;
  staging:  &lt;span class="k"&gt;*&lt;/span&gt;
  prod:     &lt;span class="k"&gt;*&lt;/span&gt;           ← matches dev — likely accidental

&lt;span class="o"&gt;[&lt;/span&gt;DANGEROUS_DEFAULT] spring.jpa.hibernate.ddl-auto
  default:  validate
  dev:      update
  prod:     update      ← destructive &lt;span class="k"&gt;in &lt;/span&gt;production

&lt;span class="o"&gt;[&lt;/span&gt;MISSING_KEY] feature.new-payment-flow.enabled
  dev:      &lt;span class="nb"&gt;true
  &lt;/span&gt;staging:  &lt;span class="nb"&gt;true
  &lt;/span&gt;prod:     &amp;lt;missing&amp;gt;   ← will fall back to null

Report written to drift-report.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It runs in 26ms on Linux, outputs a Markdown report you can paste directly into a PR,&lt;br&gt;
and knows about the most common dangerous Spring Boot defaults out of the box.&lt;/p&gt;

&lt;p&gt;It's built with Java 21 + Picocli + GraalVM Native Image — single binary, no JRE required.&lt;/p&gt;


&lt;h2&gt;
  
  
  How to add it to your workflow
&lt;/h2&gt;

&lt;p&gt;The simplest integration is a pre-deployment check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Before deploying to staging or prod:&lt;/span&gt;
spring-drift scan ./src/main/resources
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to fail the build on drift, use the exit code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;spring-drift scan ./src/main/resources &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or add it to your GitHub Actions pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Check config drift&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;curl -L https://github.com/jolle93/spring-boot-config-drift-detector/releases/latest/download/spring-drift-linux-x64 -o spring-drift&lt;/span&gt;
    &lt;span class="s"&gt;chmod +x spring-drift&lt;/span&gt;
    &lt;span class="s"&gt;./spring-drift scan ./src/main/resources&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  What I learned from scanning 50 open-source projects
&lt;/h2&gt;

&lt;p&gt;I ran spring-drift against 50 well-known Spring Boot projects on GitHub —&lt;br&gt;
from the official Spring PetClinic to large-scale projects like mall, jhipster-generated apps,&lt;br&gt;
and Apache projects like ShenYu and DolphinScheduler.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;26 out of 50 projects had at least one drift issue.&lt;/strong&gt; That's 53%.&lt;/p&gt;

&lt;p&gt;Across 497 individual modules scanned, the tool found:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;2,470 total drift issues&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;167 dangerous defaults&lt;/strong&gt; — values that are actively unsafe in production&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2,172 missing keys&lt;/strong&gt; — configuration that exists in one profile but not another&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;131 value drifts&lt;/strong&gt; — same key, different values across profiles&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The most common dangerous default by far was &lt;code&gt;management.endpoints.web.exposure.include&lt;/code&gt;,&lt;br&gt;
flagged in &lt;strong&gt;78 modules across multiple projects&lt;/strong&gt;. Including, notably, the official&lt;br&gt;
&lt;strong&gt;Spring PetClinic&lt;/strong&gt; — Spring's own reference application ships with &lt;code&gt;include: *&lt;/code&gt; as a default,&lt;br&gt;
meaning Actuator is fully exposed unless you explicitly override it in your production profile.&lt;/p&gt;

&lt;p&gt;The second most common dangerous default: &lt;code&gt;spring.datasource.password&lt;/code&gt; — found either&lt;br&gt;
empty or using a dev-only placeholder in production-facing profiles. 83 occurrences.&lt;/p&gt;

&lt;p&gt;The pattern that surprised me most was how consistent this was across project sizes.&lt;br&gt;
Small tutorial repos, large enterprise projects, Apache top-level projects — the same&lt;br&gt;
issues appeared everywhere. Config files accumulate decisions. Unlike code, they rarely&lt;br&gt;
get refactored.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Config drift is one of those problems that feels minor until it isn't.&lt;br&gt;
The three patterns above — Actuator exposure, &lt;code&gt;ddl-auto&lt;/code&gt;, missing feature flags —&lt;br&gt;
have caused real outages in real systems. None of them are exotic edge cases.&lt;/p&gt;

&lt;p&gt;If you want to try spring-drift on your own project:&lt;br&gt;
👉 &lt;a href="https://julianpaul.dev/spring-drift" rel="noopener noreferrer"&gt;julianpaul.dev/spring-drift&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The tool is source-available on &lt;a href="https://github.com/jolle93/spring-boot-config-drift-detector" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;br&gt;
and takes about 30 seconds to run against an existing Spring Boot project.&lt;/p&gt;

&lt;p&gt;I'd love to know what you find — especially if you hit a dangerous default I haven't added yet.&lt;br&gt;
Drop it in the issues as a &lt;a href="https://github.com/jolle93/spring-boot-config-drift-detector/issues/new/choose" rel="noopener noreferrer"&gt;Severity Rule Request&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with Java 21, Picocli, SnakeYAML, and GraalVM Native Image.&lt;/em&gt;&lt;br&gt;
&lt;em&gt;Available for Linux (x64) and macOS (arm64).&lt;/em&gt;&lt;/p&gt;




</description>
      <category>springboot</category>
      <category>java</category>
      <category>productivity</category>
      <category>tooling</category>
    </item>
  </channel>
</rss>
