Spring Boot Devtools
Why DevTools Matters
Spring Boot DevTools enables sub-second application restarts during development by intelligently reloading only changed classes, eliminating the 10-30 second full restart cycle. In production development workflows where engineers make dozens of code changes per hour, manual restart overhead costs 5-15 minutes per hour—DevTools with automatic restart and LiveReload browser refresh delivers instant feedback loops enabling rapid iteration on REST APIs, templates, and configuration.
Core Benefits:
- Fast restart: 1-2 second restarts vs 10-30 seconds for full JVM restart
- Automatic restart: File changes trigger restart without manual intervention
- LiveReload: Browser auto-refreshes on restart (no F5 spam)
- Remote debugging: Connect to running applications in containers or cloud
- Development-only: Automatically disabled in production deployments
Problem: Manual restart cycles waste developer time and break flow state.
Solution: Spring Boot DevTools monitors classpath changes and triggers intelligent restarts with LiveReload integration.
Manual Restart Cycle
Traditional development requires manual restart after every code change:
// => Traditional development workflow (no DevTools)
// 1. Write code
@RestController
public class ZakatController {
@GetMapping("/api/zakat/calculate")
public BigDecimal calculate(@RequestParam BigDecimal gold) {
return gold.multiply(new BigDecimal("0.025"));
}
}
// 2. Save file (Ctrl+S or Cmd+S)
// 3. Stop application (Ctrl+C in terminal)
// 4. Wait for JVM shutdown: 2-3 seconds
// 5. Run: mvn spring-boot:run or gradle bootRun
// 6. Wait for startup: 10-30 seconds (Spring context initialization)
// 7. Switch to browser
// 8. Refresh page (F5)
// 9. Test changes
// => Time per iteration: 15-35 seconds
// => 20 code changes per hour: 5-12 minutes wasted waiting
// => Flow state disrupted by context switchingLimitations:
- Slow feedback: 15-35 seconds per code change
- Manual process: Must remember to restart after every change
- Context switching: IDE → terminal → browser → IDE
- Flow disruption: Long waits break concentration
Spring Boot DevTools Automatic Restart
DevTools monitors classpath for changes and triggers fast restarts:
<!-- Add DevTools dependency -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
<!-- => Optional: not included in production JARs
- DevTools automatically disabled when running java -jar app.jar
- Only active during development (mvn spring-boot:run, IDE run) -->
</dependency>Development workflow with DevTools:
// => DevTools-enabled development workflow
// 1. Write code
@RestController
public class ZakatController {
@GetMapping("/api/zakat/calculate")
public BigDecimal calculate(@RequestParam BigDecimal gold) {
return gold.multiply(new BigDecimal("0.025"));
// => Modify rate to 0.03 for testing
}
}
// 2. Save file (Ctrl+S)
// => DevTools detects classpath change automatically
// => Triggers restart in background
// 3. Wait for restart: 1-2 seconds
// => DevTools output:
// 2025-12-30 10:15:23.456 INFO 12345 --- [ restartedMain]
// .ConditionEvaluationDeltaLoggingListener : Condition evaluation unchanged
//
// => "restartedMain" thread indicates DevTools restart, not full JVM restart
// => Application ready in 1.2 seconds (vs 15 seconds for cold start)
// 4. Browser auto-refreshes (LiveReload)
// => No manual F5, no context switching
// => Time per iteration: 1-3 seconds (vs 15-35 seconds)
// => 20 code changes per hour: 30-60 seconds total wait (vs 5-12 minutes)
// => Flow state preserved: minimal context switchingHow DevTools fast restart works:
graph TD
A["File Saved: ZakatController.java"] -->|"File Watcher"| B["DevTools Detects Change"]
B -->|"Classpath Modified"| C["Determine Restart Strategy"]
C -->|"User Code Changed"| D["Restart Only 'restart' Classloader"]
C -->|"Dependency Changed"| E["Full JVM Restart Required"]
D -->|"Unload"| F["Unload User Classes"]
F -->|"Reload"| G["Load New User Classes"]
G -->|"Keep"| H["Base Classloader Unchanged<br/>(Spring, Hibernate, Jackson)"]
H -->|"Restart"| I["Re-initialize Spring Context"]
I -->|"Ready in 1-2 seconds"| J["Application Running"]
J -->|"Trigger"| K["LiveReload Browser Refresh"]
style B fill:#0173B2,color:#fff
style D fill:#029E73,color:#fff
style E fill:#CC78BC,color:#fff
style J fill:#029E73,color:#fff
Trade-offs:
- Development-only: DevTools disabled in production (optional dependency)
- Resource usage: File watching and classloader management add overhead
- Limited scope: Only restarts for classpath changes, not configuration server changes
- Justification: Worth it for >5 code changes per hour (saves 2-5 minutes hourly)
Two-Classloader Architecture
DevTools uses two classloaders for fast restarts:
// => DevTools classloader architecture
// Base Classloader (never reloaded)
// - Third-party JARs: Spring Framework, Hibernate, Jackson, SLF4J
// - JDK classes: java.*, javax.*, jakarta.*
// - Loaded once at JVM startup, cached for entire dev session
// - Loading time: 8-12 seconds
// Restart Classloader (reloaded on file change)
// - User code: @RestController, @Service, @Repository classes
// - application.properties, application.yml
// - Templates (Thymeleaf, Freemarker)
// - Static resources (CSS, JS) if not excluded
// - Loading time: 1-2 seconds
// => Example: Change ZakatController.java
// 1. DevTools unloads restart classloader (100ms)
// 2. Reloads ZakatController.class from disk (50ms)
// 3. Re-initializes Spring ApplicationContext with new controller (1-2 seconds)
// 4. Base classloader unchanged: Spring Framework JARs not reloaded
// => Result: 1-2 second restart instead of 10-15 second full JVM restartConfigure what triggers restart:
# => application.yml: DevTools configuration
spring:
devtools:
restart:
enabled: true # => Enable automatic restart (default: true)
# => Exclude paths from restart (static resources)
exclude: static/**, public/**, resources/**
# => Changes to CSS/JS don't trigger restart
# => Only changes to Java classes, templates, application.yml trigger restart
# => Additional paths to watch (outside classpath)
additional-paths: /path/to/external/config
# => Watch external configuration files
# => Paths to exclude from watch
additional-exclude: "**/node_modules/**"
# => Ignore frontend dependencies
# => Trigger file: restart when this file modified
trigger-file: .restart
# => Manual restart control: touch .restart to trigger restart
# => Useful for batch changes: modify 10 files, trigger once
# => Restart delay: debounce rapid file changes
poll-interval: 1s # => Check for changes every 1 second
quiet-period: 400ms # => Wait 400ms after last change before restarting
# => Prevents restart storm when saving multiple files rapidlyLiveReload Browser Integration
DevTools includes embedded LiveReload server for automatic browser refresh:
# => Enable LiveReload (default: true)
spring:
devtools:
livereload:
enabled: true
# => Starts LiveReload server on port 35729
# => Browser extension connects via WebSocket
port: 35729 # => LiveReload server port (default)Install browser extension:
# => LiveReload browser extensions
# Chrome/Edge: LiveReload extension
# https://chrome.google.com/webstore/detail/livereload/jnihajbhpnppcggbcgedagnkighmdlei
# Firefox: LiveReload add-on
# https://addons.mozilla.org/en-US/firefox/addon/livereload/
# Safari: LiveReload app (paid)
# http://livereload.com/
# => After installing:
# 1. Click extension icon to enable for localhost
# 2. DevTools sends reload signal after restart
# 3. Browser refreshes automatically (no F5)LiveReload workflow:
// => LiveReload development flow
// 1. Application running with DevTools
// 2. Browser extension connected to localhost:35729
// 3. Modify code:
@RestController
public class ZakatController {
@GetMapping("/api/zakat/calculate")
public BigDecimal calculate(@RequestParam BigDecimal gold) {
return gold.multiply(new BigDecimal("0.03")); // Changed rate
}
}
// 4. Save file (Ctrl+S)
// => DevTools detects change
// => Restarts application (1-2 seconds)
// => Sends LiveReload signal to browser
// 5. Browser receives signal
// => Automatically refreshes page
// => Shows updated response with 0.03 rate
// => No manual F5, no context switching to browserManual LiveReload trigger (without browser extension):
# => Embed LiveReload script in HTML template
<script src="http://localhost:35729/livereload.js"></script>
<!-- DevTools serves this script automatically -->
<!-- Browser connects to LiveReload server via WebSocket -->
# => Or use curl to trigger refresh
curl http://localhost:35729/changed?files=application.css
# => Tells LiveReload to refresh browsers (testing)Remote DevTools for Containerized Development
DevTools supports remote applications running in Docker or cloud:
<!-- Enable remote DevTools (production: set to runtime scope) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<!-- => Include in production JAR for remote debugging -->
</dependency>Configure remote secret:
# => application.yml: Remote DevTools security
spring:
devtools:
remote:
secret: ${DEVTOOLS_SECRET}
# => Shared secret between client and server
# => Prevents unauthorized remote connections
# => Remote restart endpoint
restart:
enabled: true # => Allow remote restart triggers
# => Set environment variable
# export DEVTOOLS_SECRET=my-secure-secret-keyConnect to remote application:
# => Run remote DevTools client (from IDE or command-line)
java -jar spring-boot-devtools.jar http://remote-host:8080 \
--spring.devtools.remote.secret=my-secure-secret-key
# => Remote client:
# 1. Connects to remote application via HTTP tunnel
# 2. Monitors local classpath for changes
# 3. Sends changed classes to remote application
# 4. Triggers remote restart with updated classes
# => Use case: Kubernetes development with remote debug
# 1. Deploy app to Kubernetes with DevTools
# 2. Port-forward: kubectl port-forward pod/zakat-app 8080:8080
# 3. Connect remote DevTools client to localhost:8080
# 4. Edit code locally, changes applied to Kubernetes podSecurity warning: Remote DevTools exposes restart endpoint—only enable in development clusters, never production.
DevTools Property Overrides
DevTools applies development-friendly defaults:
// => DevTools automatically sets these properties in development
// Caching disabled
spring.thymeleaf.cache=false
// => Template changes reflect immediately, no restart
spring.freemarker.cache=false
// => Freemarker template hot reload
spring.groovy.template.cache=false
// => Groovy template hot reload
spring.mustache.cache=false
// => Mustache template hot reload
// H2 console enabled
spring.h2.console.enabled=true
// => H2 web console available at /h2-console
// Web resources cache disabled
spring.web.resources.cache.period=0
// => Static resources (CSS, JS, images) not cached
spring.web.resources.chain.cache=false
// => Resource chain caching disabled
// Jackson pretty-print enabled
spring.jackson.serialization.indent-output=true
// => JSON responses formatted (development readability)
// => Override DevTools defaults in application-dev.yml
spring:
devtools:
add-properties: false # => Disable DevTools property overrides
# => Use explicit configuration instead of DevTools defaultsExcluding Resources from Restart
Prevent restart for static resources:
spring:
devtools:
restart:
# => Default exclusions (no restart for these paths)
exclude: >
META-INF/maven/**,
META-INF/resources/**,
resources/**,
static/**,
public/**,
templates/**
# => CSS, JS, HTML template changes don't trigger restart
# => LiveReload refreshes browser without restart
# => Add custom exclusions
additional-exclude: >
**/node_modules/**,
**/frontend/**,
**/docs/**Trigger file for manual restart control:
# => Create trigger file
touch src/main/resources/.restart
# => DevTools watches for trigger file modification
# => Allows batching changes:
# 1. Modify 5 Java files (no restart yet)
# 2. Modify 3 templates (no restart yet)
# 3. Touch trigger file: touch src/main/resources/.restart
# 4. DevTools restarts once with all changesProduction Deployment (DevTools Disabled)
DevTools automatically disables itself in production:
# => Production JAR deployment
java -jar zakat-service.jar
# => DevTools detects production deployment:
# - No IDE classpath
# - Running from java -jar (not mvn spring-boot:run)
# - DevTools dependency marked optional
# => DevTools features disabled:
# - No automatic restart
# - No LiveReload server
# - No remote DevTools endpoint
# => Explicitly disable DevTools (if needed)
java -jar zakat-service.jar --spring.devtools.restart.enabled=falseExclude DevTools from production JARs:
<!-- Maven: exclude DevTools from production -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludeDevtools>true</excludeDevtools>
<!-- => DevTools not included in bootJar -->
</configuration>
</plugin>// Gradle: exclude DevTools from production
bootJar {
excludeDevtools = true
// => DevTools not included in JAR
}DevTools Logging and Debugging
Monitor DevTools activity:
# => Enable DevTools debug logging
logging:
level:
org.springframework.boot.devtools: DEBUG
# => Logs:
# - File changes detected
# - Restart triggered
# - LiveReload signals sent
# => DevTools restart log output example:
# 2025-12-30 10:15:23.123 INFO 12345 --- [ restartedMain]
# o.s.b.d.a.OptionalLiveReloadServer : LiveReload server is running on port 35729
#
# 2025-12-30 10:15:45.678 INFO 12345 --- [ Thread-5]
# o.s.b.d.a.LocalDevToolsAutoConfiguration$RestartingClassPathChangeChangedEventListener
# : Restart triggered due to change: ZakatController.class
#
# 2025-12-30 10:15:46.789 INFO 12345 --- [ restartedMain]
# c.e.z.ZakatApplication : Started ZakatApplication in 1.234 secondsWhen to Use DevTools
Use DevTools:
- Active development with frequent code changes (>5 per hour)
- Template-based applications (Thymeleaf, Freemarker, JSP)
- REST API development with manual browser testing
- Docker/Kubernetes local development (remote DevTools)
Skip DevTools:
- Production deployments (always disabled)
- Unit test development (restart not needed)
- Configuration-only changes (restart needed anyway)
- Minimal code changes (<5 per hour)
Production recommendation: Always include DevTools in development, exclude from production JARs via optional dependency or bootJar configuration.
Best Practices
// => Good: DevTools with trigger file for batch changes
// Modify 10 files without triggering restart
// Touch .restart when ready for single restart
// => Good: Exclude static resources from restart
spring.devtools.restart.exclude=static/**,public/**
// => CSS/JS changes reload via LiveReload without restart
// => Bad: DevTools in production JAR
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>compile</scope> <!-- BAD: should be optional -->
</dependency>
// => DevTools included in production, security risk
// => Good: Remote DevTools with secret
spring.devtools.remote.secret=${DEVTOOLS_SECRET}
// => Prevents unauthorized remote connections
// => Bad: Remote DevTools without authentication
spring.devtools.remote.secret=password123
// => Hardcoded secret, security vulnerabilityCommon Issues and Solutions
# => Issue: DevTools not restarting on file change
# Solution: Check IDE auto-save settings
# IntelliJ IDEA: File > Settings > Build > Compiler > Build project automatically
# Eclipse: Project > Build Automatically (enable)
# => Issue: Restart too slow (5-10 seconds)
# Solution: Exclude unnecessary paths
spring.devtools.restart.exclude=node_modules/**,frontend/**
# => Issue: LiveReload not working
# Solution: Check browser extension enabled and port not blocked
# Verify: curl http://localhost:35729/livereload.js
# Should return JavaScript content
# => Issue: Application restarts in infinite loop
# Solution: Exclude generated files from restart
spring.devtools.restart.additional-exclude=**/target/**,**/build/**
# => Issue: Remote DevTools connection refused
# Solution: Verify secret matches and port forwarding correct
# Check logs: Caused by: java.net.ConnectException: Connection refusedRelated Patterns
- Application Properties - Property overrides applied by DevTools
- Auto-Configuration - DevTools auto-configuration for development