DevTools and Fast Restarts
Use spring-boot-devtools to restart the application in a second or two after each change, and keep it out of production.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>Stopping and starting a Boot application after every edit adds up. spring-boot-devtools watches the classpath and restarts the application automatically when compiled classes change. The restart is much faster than a cold start because the JVM keeps running and most classes stay loaded.
How the restart works
DevTools uses two class loaders. Third-party jars, which rarely change, go into a base class loader that is kept. Your own classes go into a restart class loader. On a change, DevTools closes the application context, throws the restart class loader away and starts the context again with a new one. A restart costs roughly the time your own beans take to initialise.
The trigger is a change to compiled classes, not to source files. VS Code and Eclipse compile on save. In IntelliJ IDEA, either build the project (Ctrl+F9 or Cmd+F9) or enable automatic building, so the output folder actually changes.
What else it changes
DevTools applies development defaults collected from the defaults. entries in each Boot module's META-INF/spring-devtools.properties: template caching and static resource caching are switched off, error responses include the message, binding errors and stack trace, and Spring MVC logs resolved exceptions. Set spring.devtools.add-properties=false to keep your own values instead.
Changes under static/, public/, templates/, resources/ and META-INF/resources/ do not trigger a restart, because those files can be served fresh without one. Test classes and git.properties are excluded too.
Keeping it out of production
DevTools disables itself when the application is launched from a packaged jar with java -jar. On top of that, the Maven plugin leaves DevTools out of the repackaged jar by default, and in Gradle the developmentOnly configuration keeps it out of bootJar. In Maven also mark it optional so it does not leak into other modules that depend on yours. Do not rely on the runtime check alone: a jar that contains DevTools is one misconfigured start command away from verbose error pages and other development behaviour in production.
Problems you will meet
ClassCastExceptionbetween two identical class names: an object created by the old restart class loader, kept in a static field or a cache from a library in the base loader, is not an instance of the class from the new loader. Avoid static caches of your own types, or load the library in the restart class loader with an entry such asrestart.include.mylib=/mylib-[\\w.-]+\\.jarin your ownMETA-INF/spring-devtools.properties.- Restarts on every keystroke: with automatic building, each save triggers a restart. Set
spring.devtools.restart.trigger-fileto a file name such as.reloadtrigger, put that file on the classpath (for example insrc/main/resources, which the build copies to the output folder), and only changing it restarts. - Connections and containers: a restart closes and recreates the connection pool, which is quick against a local database but noticeable against a remote one.
DevTools does not hot-swap code inside a running context. For changes to a method body only, your IDE's debugger hot swap is faster still; DevTools covers the cases it cannot, such as new beans and new endpoints.
Example
<dependencies>
<!-- Development only: the Boot Maven plugin leaves DevTools out of the executable jar; optional stops it reaching other modules. -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
</dependencies>
When to use it
- Add an endpoint and call it a second later without restarting the application by hand.
- Edit server-rendered templates and see changes on refresh because template caching is off.
- Guarantee developer tooling never reaches the production jar by marking it optional.
More examples
The Gradle equivalent
The developmentOnly configuration is on the classpath for bootRun but is not packaged by bootJar. Declaring DevTools as implementation instead would put it inside the production jar, where only the runtime check stands between you and development defaults.
dependencies {
// developmentOnly is a configuration the Spring Boot Gradle plugin creates.
developmentOnly("org.springframework.boot:spring-boot-devtools")
implementation("org.springframework.boot:spring-boot-starter-webmvc")
}Tuning restarts
A trigger file suits IDEs that build continuously: you keep coding, then change the file when you want to see the result. Use additional-exclude rather than exclude: setting exclude replaces the default list, so static and templates would start triggering restarts again. Setting enabled=false here stops file watching, but the restart class loader is still created; to disable restarts completely, set it as a system property before SpringApplication.run. These keys only take effect while DevTools is on the classpath.
# application.properties (development profile only)
# Restart only when the .reloadtrigger file on the classpath changes, not on every save.
spring.devtools.restart.trigger-file=.reloadtrigger
# Added to the default exclusions (static/**, public/**, templates/** and others).
spring.devtools.restart.additional-exclude=openapi/**
# Keep your own cache and error settings instead of the DevTools defaults.
# spring.devtools.add-properties=false
# Stop watching for changes while debugging a start-up problem.
# spring.devtools.restart.enabled=falseSeeing which class loader you are in
This is a quick way to confirm DevTools is active, and the first thing to check when you see a ClassCastException naming the same class twice: two different loaders are involved. Remove the bean once you have your answer; it is a diagnostic, not something to ship.
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
public class ClassLoaderReport {
private static final Logger log = LoggerFactory.getLogger(ClassLoaderReport.class);
@EventListener(ApplicationReadyEvent.class)
public void report() {
// With DevTools active this prints its RestartClassLoader; from java -jar it does not.
log.info("Application classes loaded by {}", getClass().getClassLoader().getClass().getName());
}
}
Discussion