Spring Boot DevTools is a set of development-time tools that dramatically improves the development experience by providing automatic restart, live reload, and property defaults. This article explores how to use DevTools for efficient hot reloading in Java applications.
Setup and Configuration
1. Adding DevTools to Your Project
Maven:
<dependencies> <!-- Other dependencies --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> </dependencies>
Gradle:
dependencies {
// Other dependencies
developmentOnly 'org.springframework.boot:spring-boot-devtools'
}
2. Basic Configuration
application.properties:
# DevTools specific properties spring.devtools.restart.enabled=true spring.devtools.restart.log-condition-evaluation-delta=false spring.devtools.livereload.enabled=true spring.devtools.livereload.port=35729 # Exclude specific paths from restart spring.devtools.restart.exclude=static/**,public/**,templates/** # Additional restart trigger paths spring.devtools.restart.additional-paths=src/main/java
application.yml:
spring: devtools: restart: enabled: true log-condition-evaluation-delta: false exclude: "static/**,public/**,templates/**" additional-paths: "src/main/java" livereload: enabled: true port: 35729
Automatic Restart Functionality
3. How Automatic Restart Works
Spring Boot DevTools monitors classpath changes and automatically restarts the application. The restart uses two classloaders:
- Base ClassLoader: For third-party jars (don't change frequently)
- Restart ClassLoader: For your application code (changes frequently)
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
4. Customizing Restart Behavior
Custom Restart Configuration:
@Component
public class CustomRestartListener implements RestartListener {
@Override
public void beforeRestart() {
System.out.println("Application is about to restart...");
// Clean up resources or save state
}
@Override
public void afterRestart() {
System.out.println("Application has restarted successfully!");
// Reinitialize components if needed
}
}
Excluding Specific Files:
@Configuration
public class DevToolsConfig {
@Bean
public DevToolsRestartCustomizer devToolsRestartCustomizer() {
return new DevToolsRestartCustomizer() {
@Override
public void customize(RestartInitializer initializer) {
// Exclude configuration files from triggering restart
initializer.exclude("config/**");
initializer.exclude("logs/**");
}
};
}
}
LiveReload Integration
5. Browser Live Reload
DevTools includes a LiveReload server that automatically refreshes the browser when static resources change.
HTML with LiveReload:
<!DOCTYPE html>
<html>
<head>
<title>My Application</title>
<!-- LiveReload script for development -->
<script>
if (window.location.hostname === 'localhost') {
document.write('<script src="http://' + (location.host || 'localhost').split(':')[0] +
':35729/livereload.js?snipver=1"></' + 'script>')
}
</script>
</head>
<body>
<h1>Welcome to My Application</h1>
<div id="content">
<!-- Dynamic content -->
</div>
</body>
</html>
6. Custom LiveReload Configuration
@Configuration
public class LiveReloadConfig {
@Autowired
private LiveReloadServer liveReloadServer;
@Bean
public LiveReloadServer liveReloadServer() {
return new LiveReloadServer(35729);
}
@EventListener
public void onClassPathChanged(ClassPathChangedEvent event) {
// Trigger LiveReload when classpath changes
liveReloadServer.triggerReload();
}
}
Global Settings
7. Global DevTools Configuration
Create .spring-boot-devtools.properties in your $HOME directory:
~/.spring-boot-devtools.properties:
# Global restart excludes spring.devtools.restart.exclude=log4j2.xml,logback-spring.xml # Global trigger file spring.devtools.restart.trigger-file=.reloadtrigger # Remote debug configuration spring.devtools.remote.debug.enabled=true spring.devtools.remote.debug.local-port=8000
Property Defaults
8. Development-Specific Properties
DevTools automatically sets sensible development-time defaults:
@Configuration
public class DevelopmentConfig {
@Value("${spring.h2.console.enabled:false}")
private boolean h2ConsoleEnabled;
@Value("${spring.thymeleaf.cache:false}")
private boolean thymeleafCache;
@Value("${spring.web.resources.cache.period:0}")
private int resourcesCachePeriod;
@PostConstruct
public void init() {
System.out.println("Development configuration loaded:");
System.out.println("H2 Console: " + h2ConsoleEnabled);
System.out.println("Thymeleaf Cache: " + thymeleafCache);
System.out.println("Resources Cache: " + resourcesCachePeriod);
}
}
Remote Development
9. Remote Applications with DevTools
Client Application (Development Machine):
@SpringBootApplication
public class RemoteClientApplication {
public static void main(String[] args) {
SpringApplication.run(RemoteClientApplication.class, args);
}
}
Remote Application Configuration:
# Enable remote support spring.devtools.remote.secret=mysecret # Remote URL spring.devtools.remote.url=http://my-remote-server:8080
10. Remote Restart Endpoint
@RestController
public class DevelopmentController {
private final DevToolsRestartHandler restartHandler;
public DevelopmentController(DevToolsRestartHandler restartHandler) {
this.restartHandler = restartHandler;
}
@PostMapping("/dev/restart")
public ResponseEntity<String> triggerRestart() {
restartHandler.restart();
return ResponseEntity.ok("Application restart triggered");
}
@GetMapping("/dev/info")
public Map<String, Object> getDevInfo() {
Map<String, Object> info = new HashMap<>();
info.put("uptime", ManagementFactory.getRuntimeMXBean().getUptime());
info.put("restarts", getRestartCount());
return info;
}
private long getRestartCount() {
// Custom logic to track restarts
return 0L;
}
}
Integration with IDEs
11. IntelliJ IDEA Configuration
Enable automatic build:
- Go to Settings → Build, Execution, Deployment → Compiler
- Check Build project automatically
Registry setting:
- Press
Ctrl+Shift+A, type "Registry" - Find and enable
compiler.automake.allow.when.app.running
12. Eclipse Configuration
Enable automatic build:
- Project → Build Automatically
.spring-boot-devtools.properties in project:
spring.devtools.restart.poll-interval=1000 spring.devtools.restart.quiet-period=400
Custom Development Tools
13. Creating Custom DevTools
@Component
public class DevelopmentWatcher {
private static final Logger logger = LoggerFactory.getLogger(DevelopmentWatcher.class);
private final ApplicationEventPublisher eventPublisher;
private final FileWatcher fileWatcher;
public DevelopmentWatcher(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
this.fileWatcher = new FileWatcher();
}
@PostConstruct
public void startWatching() {
fileWatcher.watchDirectory("src/main/resources/templates", this::onTemplateChange);
fileWatcher.watchDirectory("src/main/resources/static", this::onStaticResourceChange);
}
private void onTemplateChange(Path changedFile) {
logger.info("Template changed: {}", changedFile);
eventPublisher.publishEvent(new TemplateChangeEvent(this, changedFile));
}
private void onStaticResourceChange(Path changedFile) {
logger.info("Static resource changed: {}", changedFile);
eventPublisher.publishEvent(new StaticResourceChangeEvent(this, changedFile));
}
}
@Component
public class DevelopmentEventListener {
@EventListener
public void handleTemplateChange(TemplateChangeEvent event) {
// Clear template cache
// Recompile templates if needed
System.out.println("Template changed: " + event.getChangedFile());
}
@EventListener
public void handleStaticResourceChange(StaticResourceChangeEvent event) {
// Trigger browser refresh
System.out.println("Static resource changed: " + event.getChangedFile());
}
}
14. Database Development Tools
@Component
public class DatabaseDevelopmentTools {
@Autowired
private DataSource dataSource;
@Value("${spring.datasource.url:}")
private String datasourceUrl;
@EventListener
public void onApplicationReady(ApplicationReadyEvent event) {
if (isDevelopmentProfile() && isH2Database()) {
initializeDevelopmentData();
}
}
private boolean isDevelopmentProfile() {
return Arrays.stream(environment.getActiveProfiles())
.anyMatch(profile -> profile.equals("dev") || profile.equals("development"));
}
private boolean isH2Database() {
return datasourceUrl.contains("h2");
}
private void initializeDevelopmentData() {
try (Connection conn = dataSource.getConnection();
Statement stmt = conn.createStatement()) {
// Insert development test data
stmt.execute("INSERT INTO users (username, email) VALUES ('devuser', '[email protected]')");
logger.info("Development data initialized");
} catch (SQLException e) {
logger.warn("Failed to initialize development data", e);
}
}
}
Performance Optimization
15. Optimizing Restart Performance
@Configuration
public class PerformanceConfig {
@Bean
@Profile("dev")
public RestartClassLoader restartClassLoader() {
return new RestartClassLoader() {
@Override
protected boolean isExcludedFromRestart(String className) {
// Exclude large libraries to improve restart performance
return className.startsWith("org.hibernate.") ||
className.startsWith("com.fasterxml.jackson.") ||
className.startsWith("org.springframework.security.");
}
};
}
}
application-dev.properties:
# Optimize for faster restarts spring.devtools.restart.poll-interval=1000 spring.devtools.restart.quiet-period=500 # Exclude large libraries spring.devtools.restart.exclude= org.hibernate./*, com.fasterxml.jackson./*, org.springframework.security./*
Testing with DevTools
16. Test Configuration
@SpringBootTest
@TestPropertySource(properties = {
"spring.devtools.restart.enabled=false",
"spring.devtools.livereload.enabled=false"
})
public class ApplicationTests {
@Test
public void contextLoads() {
// Test that application starts correctly
}
@Test
@DirtiesContext
public void testWithRestart() {
// Test that survives application restart
}
}
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public class RepositoryTests {
@Autowired
private TestEntityManager entityManager;
@Autowired
private UserRepository userRepository;
@Test
public void testRepositoryOperations() {
// Repository tests work with DevTools
User user = new User("testuser", "[email protected]");
entityManager.persist(user);
User found = userRepository.findByUsername("testuser");
assertThat(found).isNotNull();
}
}
Troubleshooting Common Issues
17. Common Problems and Solutions
@Component
public class DevToolsDiagnostics {
private static final Logger logger = LoggerFactory.getLogger(DevToolsDiagnostics.class);
@EventListener
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof RestartEvent) {
logger.info("Restart event detected");
logClasspathInfo();
}
}
private void logClasspathInfo() {
try {
ClassLoader classLoader = getClass().getClassLoader();
if (classLoader instanceof URLClassLoader) {
URL[] urls = ((URLClassLoader) classLoader).getURLs();
logger.info("Classpath URLs: {}", Arrays.toString(urls));
}
} catch (Exception e) {
logger.warn("Failed to log classpath info", e);
}
}
}
Common Configuration Fixes:
# If restarts are too slow spring.devtools.restart.quiet-period=500ms spring.devtools.restart.poll-interval=1s # If LiveReload isn't working spring.devtools.livereload.port=35729 spring.devtools.livereload.enabled=true # If specific files aren't triggering restart spring.devtools.restart.additional-paths=src/main/java,src/main/resources spring.devtools.restart.trigger-file=.reloadtrigger
Best Practices
18. Development Workflow Optimization
@Profile("dev")
@Component
public class DevelopmentAssistant {
@Autowired
private Environment environment;
@PostConstruct
public void printDevelopmentInfo() {
System.out.println("=== Development Mode Active ===");
System.out.println("Active Profiles: " + Arrays.toString(environment.getActiveProfiles()));
System.out.println("DevTools: Enabled");
System.out.println("LiveReload: Port 35729");
System.out.println("Automatic Restart: Active");
System.out.println("===============================");
}
@EventListener
public void onRestart(RestartEvent event) {
System.out.println("Application restarted at: " + new Date());
System.out.println("Restart cause: " + event.getSource());
}
}
Conclusion
Spring Boot DevTools significantly enhances developer productivity by providing:
- Fast application restarts without full JVM shutdown
- Live browser reloading for static content changes
- Sensible development defaults for caching and debugging
- Remote development support for cloud environments
- Customizable restart triggers and exclusions
By properly configuring and utilizing DevTools, you can achieve sub-second restart times and a seamless development experience that closely mirrors production behavior while maintaining development convenience.