skip to content

As a principal engineer, what risks and edge cases would you flag about DevTools, especially remote DevTools and restart-classloader pitfalls?

level: principalimportance: nice to knowfreq 18%

answer

  1. remote DevTools = RCE — never prod, strong secret, HTTPS
  2. cross-restart ClassCastException from two classloaders
  3. state wiped every restart (not JRebel)
  4. META-INF/spring-devtools.properties to move classes to base CL
  5. exploded run may skip self-disable — enforce scope

basics

~20 s

Never enable remote DevTools against production — it can execute code on the server. Also watch for cross-restart ClassCastExceptions from the two-classloader model, lost in-memory state on each restart, and exploded-run deployments where DevTools may not self-disable. Keep it strictly developmentOnly.

solid answer

~50 s

DevTools is a productivity tool with sharp edges I'd govern at team level. The biggest is **remote DevTools** (`spring.devtools.remote.secret`), which lets a local IDE push restarts/updates to a running remote app — effectively remote code execution; it must never target production or any shared environment, and the secret must be strong. On the restart mechanism: the **two-classloader** design means a class loaded by the old restart classloader is a different type after a restart, so caching instances in base-classloader statics causes **ClassCastException**, and serialized/session objects can break across restarts. **In-memory state is wiped** every restart, so don't build workflows that assume persistence. Libraries that cache class references may misbehave and sometimes need moving to the base classloader via `META-INF/spring-devtools.properties`. Finally, self-disable relies on a packaged-jar launch — **exploded deployments** could leave DevTools active, so I enforce `developmentOnly`/`optional` scope in build policy rather than trusting runtime detection.

code

properties · 7 lines
properties
# META-INF/spring-devtools.properties
# Force a problematic library into the base classloader so it survives restarts
restart.exclude.companycommonlibs=/mycorp-[\\w-]+\\.jar
# (regex matches classpath entries to KEEP in the base classloader)

# DANGER: remote DevTools activates remote code push — never in prod/staging
# spring.devtools.remote.secret=<strong-secret>   # local/trusted only

go deeper

for a junior

Just know DevTools is local-only and not for production.

for a middle

Know remote DevTools exists and is risky, and that restarts lose state.

for a senior

Explain ClassCastException across restarts and how to move classes to the base classloader.

for a principal

Set governance: ban remote in shared profiles, enforce scope, understand exploded-run self-disable gaps and the RCE risk.

## Remote DevTools — the security headline **Remote DevTools** lets your local IDE connect to a *remotely running* app and push restarts and updated classes to it. It is enabled by setting a shared secret: ```properties spring.devtools.remote.secret=some-strong-secret ``` Mechanically this is close to **remote code execution**: you're shipping new bytecode to a running server and restarting its context. Principal-level guidance: - **Never** enable it against production or any shared/staging environment reachable by others. - If used at all, only over **HTTPS** with a strong, secret value, and ideally not exposed publicly. - Because merely having the property present activates a powerful capability, treat it as a red-flag in config review. It should never be in a committed production profile. - The whole DevTools module should be **absent from production artifacts** (developmentOnly/optional), which also removes the remote endpoint entirely — the best mitigation is simply not shipping it. ## Restart-classloader pitfalls The two-classloader model (base = jars, restart = your directory classes) creates subtle traps: - **Cross-restart ClassCastException**: `com.example.Foo` loaded by restart-CL v1 and by restart-CL v2 are *distinct* `Class` objects. If a base-classloader-held static (or a library cache) retains a v1 instance and later code casts it to the v2 `Foo`, you get `ClassCastException`. Fix: don't cache your classes' instances in base-classloader-owned statics; or push those classes into the base classloader. - **`META-INF/spring-devtools.properties`**: lets you override which classes go to base vs restart classloader (`restart.include.<name>` / `restart.exclude.<name>` regex on classpath entries). Needed for some frameworks that assume a single classloader. - **Lost state**: every restart is a fresh context — caches, counters, in-memory sessions vanish. Don't design dev workflows that rely on state surviving edits. - **Deserialization**: objects serialized before a restart may fail to deserialize after, due to classloader identity. ## Deployment edge cases - Self-disable keys off being launched from a **fully packaged jar**. If you run the app **exploded** (directory classpath — some container/layered-jar or `java -cp classes` setups, or misconfigured CI), DevTools may consider it a dev run and **stay active**. Therefore treat correct dependency scoping (`developmentOnly`/`optional`) as the real control; self-disable is only a backstop. - **Performance**: active DevTools disables template/resource caching and runs a file-watcher thread and a LiveReload server — all wasteful and inappropriate in any perf-sensitive environment. ## Governance recommendations - Enforce `developmentOnly` (Gradle) / `optional` (Maven) via build lint or PR review; ban `implementation`/`compile` scope for DevTools. - Forbid `spring.devtools.remote.*` in any committed non-local profile. - Consider a global `~/.config/spring-boot/spring-boot-devtools.properties` for team-wide defaults. - Document that DevTools ≠ JRebel: it restarts the context (state lost); if the team needs stateful reload, that's a different (commercial) tool. ## When to use DevTools at all Great for local, single-developer inner-loop work on web/templating apps. Less useful for stateless microservices where a fast `bootRun` is already quick, and undesirable anywhere the state-loss or caching-off behavior interferes. Always local-only.

  • Why is remote DevTools considered a security risk?
    It lets a client push new bytecode to a running server and restart its context — effectively remote code execution. If enabled on a reachable environment with a weak/known secret, an attacker could run arbitrary code. It must never target production.
  • A teammate reports intermittent ClassCastException only in dev after saving. Diagnosis?
    Classic two-classloader symptom: an instance created by an old restart classloader is cached (often in a base-classloader static or a library cache) and later cast to the same class freshly loaded by the new restart classloader. Remove the cross-restart cache or move the class to the base classloader.

saying these in an interview costs you the question

  • Treating remote DevTools as a safe way to hot-update production.
  • Assuming DevTools preserves in-memory state like JRebel.
  • Trusting runtime self-disable instead of enforcing developmentOnly/optional scope.
  • Committing spring.devtools.remote.secret into a shared/production profile.

context