As a principal engineer, what risks and edge cases would you flag about DevTools, especially remote DevTools and restart-classloader pitfalls?
answer
- remote DevTools = RCE — never prod, strong secret, HTTPS
- cross-restart ClassCastException from two classloaders
- state wiped every restart (not JRebel)
- META-INF/spring-devtools.properties to move classes to base CL
- exploded run may skip self-disable — enforce scope
basics
~20 sNever 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 sDevTools 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# 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 onlygo deeper
Just know DevTools is local-only and not for production.
Know remote DevTools exists and is risky, and that restarts lose state.
Explain ClassCastException across restarts and how to move classes to the base classloader.
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.