Spring Boot DevTools: Hot Reload, LiveReload & Developer Productivity in 2024
Master Spring Boot DevTools for hot reload, LiveReload, and faster development cycles.
20+ years shipping production Java in banking & fintech. Notes here come from systems that actually shipped.
- ✓Java 17 or later (Java 21 recommended for Spring Boot 3.2+)
- ✓Maven 3.9+ or Gradle 8.5+
- ✓Spring Boot 3.2+ project with web dependency
- ✓IDE: IntelliJ IDEA 2024+ or VS Code with Spring Boot extension
- ✓Basic understanding of Spring Boot application structure
• Spring Boot DevTools enables automatic restart on classpath changes, reducing build-restart cycles from 30s to under 3s in most projects • LiveReload automatically refreshes browser when static resources change, no manual F5 needed • Disable DevTools in production via spring.devtools.restart.enabled=false or Maven exclusion • Use remote debugging with caution: it opens RMI ports and can be a security risk if exposed • Trigger restart manually via Maven: mvn spring-boot:run -Dspring.devtools.restart.trigger-file=trigger
Think of DevTools like a Formula 1 pit crew: when you change a tire (code), the crew instantly swaps it without stopping the car. Without DevTools, you'd have to pull into the garage, shut off the engine, change the tire, and restart — taking 10x longer. The "restart" is actually a smart classloader swap that keeps your application context alive, using two classloaders: base (unchanged dependencies) and restart (your code). Only the restart classloader gets thrown away and recreated.
| Chrome | Firefox | Safari | Edge |
|---|---|---|---|
| ✓ | ✓ | ✓ | ✓ |
Every Spring Boot developer has been there: you make a one-line change, hit Ctrl+C, wait for the application to shut down, run Maven again, watch the banner print, wait for Hibernate to validate schemas, and finally test your change. That cycle takes 20-40 seconds per iteration. Multiply by 50 changes a day and you've wasted 15-30 minutes just waiting for restarts. Spring Boot DevTools solves this with hot reload, LiveReload, and a suite of developer-friendly features that cut iteration time by 90%.
DevTools is not a build tool — it's a development-time agent that uses a specialized classloader strategy. When you change a file, it triggers a "restart" that only reloads your application classes, not the third-party JARs. This is fundamentally different from JRebel's bytecode swapping; DevTools does a fast restart using two classloaders. The base classloader holds all dependency JARs (which rarely change), and the restart classloader holds your application classes. On change, the restart classloader is discarded and recreated, which takes 2-5 seconds versus a full JVM restart of 15-30 seconds.
But DevTools is more than just restart. It includes LiveReload integration that auto-refreshes your browser when static resources change. It also disables caching of Thymeleaf templates, Hibernate second-level cache, and Spring Security's filter chain cache. This means you see changes immediately without clearing caches manually. However, many developers misuse DevTools in production or misunderstand its limitations. In this article, I'll cover everything from setup to production incident prevention, based on real-world experience with Spring Boot 3.2.x and Java 21.
Setting Up Spring Boot DevTools
Adding DevTools to your project is trivial, but the devil is in the details. For Maven, add the dependency with runtime scope — this ensures it's not bundled in your production JAR by default. Here's the correct setup for Spring Boot 3.2.x with Maven:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency>
The <optional>true</optional> flag is critical: it prevents DevTools from being pulled transitively by other modules. The runtime scope means it's not included in the executable JAR when you run mvn package. For Gradle, use developmentOnly configuration:
developmentOnly("org.springframework.boot:spring-boot-devtools")
After adding the dependency, you'll notice the console prints "LiveReload server is running on port 35729" when you start your application. That's the LiveReload server that communicates with browser extensions. To verify restart is working, change a controller method and save. You should see a log line: "Restarted main application in 2.345 seconds" — that's the restart classloader being swapped.
One common mistake: using compile scope instead of runtime. This bundles DevTools into your production artifact, which can cause performance degradation and security risks. Always verify your final JAR with jar -tf target/app.jar | grep devtools — if you see any class, you've done it wrong.
What the Official Docs Won't Tell You
The official Spring Boot documentation covers the basics, but it glosses over several critical nuances that will bite you in production or complex development setups. First, the restart mechanism uses a custom ClassLoader that extends URLClassLoader. When a restart is triggered, the old ClassLoader is not garbage collected immediately — it holds references to all loaded classes. If you have static collections or thread-local variables in your application, they can prevent the old ClassLoader from being GC'd, causing metaspace leaks. I've seen this in applications with heavy use of ThreadLocal for request context — after 50 restarts, metaspace was exhausted.
Second, DevTools does NOT reload resources that are in the classpath but outside your project's target/classes directory. For example, if you have a shared library JAR that you're also developing locally, changes to that JAR won't trigger a restart unless you rebuild it and copy it to the local Maven repository. This is a common pain point in microservice architectures where you develop multiple services simultaneously.
Third, the LiveReload server uses WebSocket to communicate with browser extensions. If you're behind a corporate proxy or have network restrictions, the WebSocket connection may fail silently. The browser won't refresh, and you'll wonder why DevTools isn't working. Check the browser's developer console for WebSocket errors.
Finally, DevTools has a sneaky behavior: it sets spring.freemarker.cache=false, spring.thymeleaf.cache=false, and spring.h2.console.enabled=true by default. These are great for development but can cause confusion when you deploy to production and templates are cached, or H2 console is unexpectedly enabled. Always explicitly set these properties in your production configuration.
Configuring Restart Exclusions and Triggers
By default, DevTools watches your classpath for changes. But not all changes should trigger a restart. For example, changes to static resources (CSS, JS, HTML) should only trigger a browser refresh via LiveReload, not a full restart. DevTools has sensible defaults: /META-INF/maven, /META-INF/resources, /resources, /static, /public, /templates are excluded from restart but included in LiveReload. However, you can customize this with spring.devtools.restart.exclude property.
A more powerful feature is the trigger file. Instead of watching the entire classpath, you can configure DevTools to watch a single file. When you touch (modify) that file, it triggers a restart. This is useful when your IDE compiles files asynchronously and you want to batch changes. For example, with Gradle's continuous build, you can have the trigger file touched after the build completes.
Here's a typical configuration for a large project with multiple modules:
spring.devtools.restart.exclude=static/,public/,templates/** spring.devtools.restart.additional-paths=../shared-library/build/classes spring.devtools.restart.trigger-file=trigger.txt
To use the trigger file, create an empty file named trigger.txt in your project root. When you want to restart, touch it (on Linux: touch trigger.txt, on Windows: type nul >> trigger.txt). This gives you explicit control over when restarts happen.
Another critical configuration is spring.devtools.restart.enabled. Set this to false in production profiles to disable restart entirely. Even if DevTools is on the classpath, this property prevents the restart mechanism from initializing.
LiveReload Integration and Browser Setup
LiveReload is DevTools' secret weapon for frontend developers. When you change a static resource (CSS, JavaScript, Thymeleaf template), DevTools sends a WebSocket message to the LiveReload server, which then pushes a notification to browser extensions to refresh the page. This eliminates the manual F5 cycle.
To use LiveReload, you need a browser extension. For Chrome, use "LiveReload" by livereload.com. For Firefox, use "LiveReload" by the same author. After installing the extension, click its icon to connect to the DevTools LiveReload server running on port 35729. The icon should change from a hollow circle to a filled circle when connected.
One critical detail: LiveReload only works for resources that change on the file system. If you're using an IDE that compiles to a different output directory, you need to configure the additional paths. For example, in IntelliJ IDEA, if you use "Build project automatically" (Registry key: compiler.automake.allow.when.app.running), the compiled classes go to target/classes, which DevTools watches by default. But if you have a Gradle build that outputs to build/classes, you need to add that path.
LiveReload also works with remote applications, but this is dangerous. The remote LiveReload server opens a port on the remote machine. If you're using a cloud IDE or remote development environment, ensure the port is not publicly accessible. Use SSH tunneling instead of exposing the port directly.
If LiveReload isn't working, check these common issues: (1) The browser extension is not connected — click the icon. (2) The resource is excluded from LiveReload — check spring.devtools.restart.exclude. (3) WebSocket connection is blocked by a firewall or proxy. (4) You're using HTTPS locally but the LiveReload server uses HTTP — the extension may block mixed content.
Remote Debugging with DevTools (and Why You Should Be Careful)
DevTools supports remote debugging via a special mode that allows you to trigger restarts on a remote application. This sounds great for debugging on staging servers, but it's a double-edged sword. The remote mode uses RMI (Remote Method Invocation) to communicate, which opens two ports: one for the RMI registry (default 9099) and one for the RMI server (random). This is a security nightmare if not properly secured.
To enable remote debugging, start your remote application with:
java -jar myapp.jar --spring.devtools.remote.secret=my-secret-key
Then on your local machine, run a remote client:
mvn spring-boot:run -Dspring-boot.run.profiles=remote
This connects to the remote application and allows you to push local changes to the remote server. The remote application will restart with your changes. However, the secret key is sent in plaintext over the RMI connection. If someone intercepts the RMI traffic, they can extract the secret and push arbitrary code to your server.
In practice, I've seen remote debugging used in only one production scenario: debugging a hard-to-reproduce issue on a staging server that mirrors production. Even then, the team used a VPN and IP whitelisting to restrict access. For most developers, local debugging with DevTools is sufficient. Remote debugging adds complexity and risk without proportional benefit.
If you must use remote debugging, follow these security practices: (1) Use a strong, randomly generated secret (at least 32 characters). (2) Bind the RMI server to localhost only using -Djava.rmi.server.hostname=127.0.0.1. (3) Use SSH tunneling to expose the RMI ports only to your local machine. (4) Never use remote debugging on production — only on isolated staging environments.
Troubleshooting Common DevTools Issues
DevTools is generally reliable, but it has its quirks. Here are the most common issues I've encountered and their solutions.
Issue 1: Restart not triggering. If you save a file and nothing happens, first check if DevTools is actually active. Look for "LiveReload server is running on port 35729" in the logs. If you don't see it, DevTools isn't on the classpath or is disabled. Check your dependency scope and the spring.devtools.restart.enabled property. Also, some IDEs (like Eclipse) compile on save but output to a different directory. Ensure the output directory is in the classpath.
Issue 2: Restart looping. If your application restarts repeatedly without you changing anything, something is modifying the classpath. Common culprits: log rotation (log files written to the classpath), IDE plugin that generates files, or a file watcher that touches files. Use spring.devtools.restart.exclude to exclude directories that change frequently. If the issue persists, use a trigger file to take control.
Issue 3: LiveReload not refreshing the browser. First, verify the browser extension is connected (icon should be solid). Then check the browser console for WebSocket errors. If you see "WebSocket connection to 'ws://localhost:35729/livereload' failed", the LiveReload server isn't running or is on a different port. Check if another application is using port 35729. You can change the port with spring.devtools.livereload.port=35730.
Issue 4: Slow restarts. If restarts take more than 10 seconds, you probably have too many classes in the restart classloader. Consider moving some of your code to a separate Maven module that becomes a dependency (and thus goes to the base classloader). Alternatively, use spring.devtools.restart.poll-interval to increase the polling frequency (default 1 second).
Issue 5: Hibernate validation on every restart. DevTools disables Hibernate's second-level cache by default, but schema validation still runs. Set spring.jpa.hibernate.ddl-auto=update in development to avoid full validation. But be careful: this can modify your database schema.
DevTools vs JRebel vs DCEVM: Which Should You Use?
Developers often ask whether DevTools is enough or if they need a commercial tool like JRebel or a free alternative like DCEVM (Dynamic Code Evolution VM). The answer depends on your project size and budget.
DevTools (free, built into Spring Boot) uses a fast restart approach. It discards and recreates the application classloader, which takes 2-5 seconds for most projects. It works out of the box with no JVM arguments. However, it loses application state: all beans are recreated, sessions are lost, and caches are cleared. This means you may need to re-login or re-populate data after each restart.
JRebel (commercial, ~$550/year) uses bytecode instrumentation to swap individual classes at runtime without restart. It preserves application state: your HTTP session, Hibernate caches, and bean instances remain intact. Changes to method bodies, annotations, and even new classes are applied instantly. The downside is cost and complexity — you need a JVM agent and IDE plugin.
DCEVM (free, open-source) is a modified JVM that supports hot-swapping of classes at the JVM level. It's more powerful than standard Java hot-swap (which only allows method body changes) but less comprehensive than JRebel. DCEVM can add new methods and fields, but it has limitations with enum changes and lambda expressions. It requires installing a custom JVM, which can be a barrier in enterprise environments.
My recommendation: For small to medium projects (< 5 seconds restart time), DevTools is sufficient. For large enterprise applications with 20+ second restart times and complex state, invest in JRebel — the productivity gain justifies the cost. DCEVM is a good middle ground if your team can manage the custom JVM installation. In my experience, teams that use JRebel report a 30-40% reduction in development iteration time.
One more thing: never mix DevTools with JRebel. They interfere with each other because both try to manipulate class loading. If you use JRebel, remove DevTools from your development dependencies.
Best Practices for DevTools in Team Environments
Using DevTools effectively in a team requires discipline. Here are practices I've refined over years of working with Spring Boot teams.
First, use Maven profiles or Gradle build variants to control DevTools inclusion. Never rely on developers remembering to exclude DevTools from production builds. In your pom.xml, use a profile that activates DevTools only for local development:
<profiles> <profile> <id>dev</id> <activation><activeByDefault>true</activeByDefault></activation> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> </dependencies> </profile> </profiles>
Second, standardize DevTools configuration across the team. Create a application-dev.properties file that everyone uses. Include restart exclusions, LiveReload settings, and any team-specific paths. Commit this file to the repository so everyone has the same setup.
Third, educate new team members about DevTools' behavior. I've seen junior developers spend hours debugging why their application restarts every time they save a log file. Create a short onboarding document that covers: how to verify DevTools is active, how to trigger a manual restart, and what to do when LiveReload isn't working.
Fourth, integrate DevTools with your CI/CD pipeline. Add a build step that checks for DevTools in the production artifact. This can be a simple grep command or a more sophisticated ArchUnit test. For example:
@ArchTest static final ArchRule no_devtools_in_production = classes() .that().resideInAPackage("org.springframework.boot.devtools..") .should().onlyBeAccessed().byClassesThat().resideInAPackage("..dev..");
Finally, consider using DevTools' remote secret management with a vault. If you use remote debugging, store the secret in a secure vault (e.g., HashiCorp Vault, AWS Secrets Manager) and inject it at runtime. Never hardcode secrets in configuration files.
When DevTools Caused a Production Outage
- Never assume a dependency is harmless in production — verify with build profiles
- Always add a production safety net property spring.devtools.restart.enabled=false
- Use CI/CD to scan for development-only dependencies in deployment artifacts
- DevTools remote features should only be used on isolated developer machines, never on shared or production environments
mvn spring-boot:run -Dspring-boot.run.jvmArguments="-Dspring.devtools.restart.enabled=true"ls -la target/classes/com/example/ (verify class files exist)| File | Command / Code | Purpose |
|---|---|---|
| pom.xml | Setting Up Spring Boot DevTools | |
| DevToolsClassLoaderIssue.java | public class RequestContextHolder { | What the Official Docs Won't Tell You |
| application-dev.properties | spring.devtools.restart.exclude=static/**,public/**,templates/** | Configuring Restart Exclusions and Triggers |
| livereload.html | LiveReload Integration and Browser Setup | |
| remote-debug.sh | java -jar myapp.jar \ | Remote Debugging with DevTools (and Why You Should Be Carefu |
| SlowRestartFix.java | Troubleshooting Common DevTools Issues | |
| JRebelConfig.java | DevTools vs JRebel vs DCEVM | |
| ArchUnitTest.java | public class DevToolsArchitectureTest { | Best Practices for DevTools in Team Environments |
Key takeaways
Interview Questions on This Topic
Explain how Spring Boot DevTools achieves fast restart without restarting the JVM.
Frequently Asked Questions
20+ years shipping production Java in banking & fintech. Notes here come from systems that actually shipped.
That's Spring Boot. Mark it forged?
9 min read · try the examples if you haven't