I just disvovered spring-devtools to restart the application when a class file changes.
To trigger the restart i setup IntelliJ like this:
Whenever I change a .java file and hit Strg-S for save, the file gets compiled by IntelliJ and Spring Boot restarts within a few seconds. The .class file is compiled to the build/ directory and not the default out/ directory of IntelliJ. This is correct because of gradle. That's fine and it works as expected.
But it does not work with properties files. I have a message.properties file and a ReloadableResourceBundleMessageSource in my Spring Boot Application. I want this file to be reloaded, too.
Whenever I change the messages.properties file and save it, the file gets copied to out/ directory instead of the build/ directory. In the build/ directory it can't get picked up.
It is not picked up by devtools restart mechanism (which could/might be disabled for these files with spring.devtools.restart.exclude)
But it is not picked up by ReloadableResourceBundleMessageSource either as the classpath is -classpath [...]/build/classes/java/main:[...]/build/resources/main
How do I tell IntelliJ to respect the gradle build Directory for resource files?
We are using AspectJ compile-time-weaving
(ctw). But the IntelliJ auto-compile mechanism does not take into account our gradle aspectj configuration. So sometimes the compilation is just wrong. This happens if you have a @Transactional or @Async annotation. This is quite annoying.
If the compilation takes some more time, the application restarts already and restarts again as it detects new changes afterwards. this could be solved with a trigger-file with devtools. But you can't configure IntelliJ to touch a trigger file after compiling.
How to solve these problems?
I found a good solution for this problem I would like to share:
We want to achieve proper compilation and fast reload times with devtools (without jrebel). Proper compilation includes aspectj compile time weaving (ctw) which is something that the internal IntelliJ compile does not handle.
So automatic compilation was not an option for us. What we are using instead is gradle continuous build
import java.time.Instant
tasks.register('compileTrigger') {
dependsOn tasks.compileJava
onlyIf {
!compileJava.state.upToDate
}
doLast {
def file = new File(projectDir, "build/classes/java/main/.reloadTrigger");
file.createNewFile();
file.setLastModified(Instant.now().toEpochMilli())
}
}
tasks.register('continuous-build') {
getProject()
.getRootProject()
.getSubprojects()
.findAll { (it.name != project.name) }
.forEach {
it.tasks.configureEach {
enabled = false
}
}
dependsOn tasks.processResources, tasks.compileTrigger, tasks.processTestResources, tasks.compileTestJava
}
We decided to use a trigger file because otherwise the application restarts to early if more work is done.
We decided to exclude all other modules as these have their own reload mechanisms (npm, vue, vite etc). your mileage may vary.
We only add a trigger file if compileJava
was running. All files from process-resources gets picked up by Spring boot automatically if configured properly.
Add to build.gradle
dependencies {
developmentOnly("org.springframework.boot:spring-boot-devtools")
}
Adding to application-development.properties:
spring.devtools.restart.trigger-file=.reloadTrigger
spring.devtools.restart.poll-interval=400ms
spring.devtools.restart.quiet-period=1ms
poll-interval
and quiet-period
are not needed but they save some time (1sec). It would be nice if devtools uses a file watcher and not a polling algorithm.
So we disable all automatic build (which is the default in IntelliJ anyway). But check this options as otherwise gradle and IntelliJ are building at the same time.
Edit your Spring Boot Run Configuration and go to "Modify options" and activate "Do not build before run". As building is done by continuous gradle, it is important to disable it here.
We are adding a Gradle "Run Configuration" in IntelliJ to easily start our continuous build.
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="continous-build" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="--continuous" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="continuous-build" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>
Now we are able to start a continuous build in IntelliJ.
Add a Compound Run Configuration adding gradle and spring-boot run configuration, name it "build-and-run"
Open IntelliJ, start "build-and-run" and everything gets compiled and reloaded automatically.
We saved a lot of time with this setup.