Spring Boot DevTools for Hot Reloading in Java

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.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper