When you are developing a web application minimizing the time it takes changes you make to show up is a key factor in your productivity. The quicker you can see the results of your change, the quicker you know if you have fixed things, or made things worse:).
If you are using SpringBoot you may be used to saving your changes, and then restarting your application by stopping and re-running your maven or gradle run or bootRun command. Thankfully SpringBoot Dev Tools can automate application reloads and restarts to pick up your back end changes, and can also automatically refresh your browser window to pick up front end changes.
Getting this working correctly can be a little tricky, so I wanted to give you an easy guide to get this working. I am using SpringBoot and Gradle for my build system. I am using VSCode as my IDE. For Maven you’ll need to translate things from my build.gradle file into your pom.xml, but the overall structure should be the same.
Caveat: I’ve gotten this working organically with a lot of trial and error, so there may be some residual cruft from the various things I’ve tried that is not necessary.
Let’s start with SpringBoot Dev Tools and the back end!
First you’ll need to include the Dev Tools library into your project
build.gradle:
dependencies {
.....
developmentOnly 'org.springframework.boot:spring-boot-devtools'
.....
Now you need to make a couple changes to the bootRun declaration in your build.gradle file:
bootRun {
// Use Spring Boot DevTool only when we run Gradle bootRun task
classpath = sourceSets.main.runtimeClasspath + configurations.developmentOnly
sourceResources sourceSets.main
if (project.hasProperty('profiles')) {
environment SPRING_PROFILES_ACTIVE: profiles
} else {
def profiles = 'local'
environment SPRING_PROFILES_ACTIVE: profiles
}
}
Those two lines are key. You can ignore the profile setting stuff below.
Next you need to configure SpringBoot DevTools in the SpringBoot application.properties or application.yml. Normally you only want this to run locally, so I used a profile called “local” for my local instance. As such, I configure my devtools in application-local.yml. But you can use the appropriate file. If you’re still using .properties, you can convert these configs to the correct format easily enough.
application-local.yml:
spring:
devtools:
restart:
enabled: 'true'
poll-interval: '2s'
quiet-period: '1s'
additional-paths:
- src/main/java/
In this config we are enabling SpringBoot DevTools’ restart functionality, setting quick polling and a short quiet period. We’re also adding the source path as an additional monitored path to trigger restart (or classpath reload).
Technically I don’t think this should be necessary as it should monitor the class path location build/classes/java/main, however in my testing I had to monitor the source files instead even after solving the compiling issue I’m about to dig into.
The next hurdle I ran into is that VSCode will auto compile Java on save, but it will save it to bin/main/com… and not to the gradle build/classes/java/main location. This means that, with the above config in place, when you save the Java file, it will be detected by devtools and trigger a restart, however the restart will not pickup the new class file which is only updated under bin/.
I cannot figure out how to get VSCode to compile to the gradle directory, or get devtools to look at the VSCode directory, etc… So as a workaround…
You can run a separate gradle classes task (which handles the java compilation) continuously, by running this in another terminal:
gradle -t classes
Now, when you save a Java file, this second gradle process will detect the change and compile it into build/classes/java/main. While I THINK devtools should auto detect that, it wasn’t doing it for me, so I had to monitor the source directory in the additional-paths configuration above.
Basically what is happening is that when you save the Java file, the second gradle process compiles it, meanwhile the bootRun devtools detect the changed source file, triggers a reload, which picks up the new .class file written by the second gradle process.
It feels VERY hacky to me, but it works. If you have a cleaner solution please let me know!
Okay, so now you have it setup so when you save a Java file it will automatically reload your SpringBoot application’s class path. If your application is large and slow to start, and you don’t WANT to auto reload on every source file save, you can disable the restart mechanism of SpringBoot DevTools by setting enabled: ‘false’ in your application.yml.
Now onto LiveReload and the Thymeleaf front end!
Some settings to add to your application-local.yml or whatever the profile configuration file you are using is:
spring:
devtools:
livereload:
enabled: 'true'
thymeleaf:
cache: 'false'
prefix: file:src/main/resources/templates/
web:
resources:
static-locations: file:src/main/resources/static/, classpath:/static/
cache:
period: 0
In this config we are enabling livereload in the spring devtools. We are disabling the thymeleaf cache, and setting a mapping to the files in the file system rather than having them be loaded through the classpath, so we can display thymeleaf front end changes quickly. We are also ensuring that static files (images, css, js, etc..) are loaded directly from the file system without a cache. It’s important that these settings are ONLY applied to your local development environment and do not make it into production!
LiveReload has two components, a front end javascript and a back end websocket server. The front end javascript connects to the websocket server, and is notified whenever a thymeleaf page is updated, and forces the browser to reload the page automatically. So you edit and save your Thymeleaf template (or css, js, etc..) and your browser you have open for testing immediately reloads the page so you can see your changes without even having to leave your IDE.
The configuration above enables the websocket server within the SpringBoot application. You need to add the javascript to the front end. The easiest way to do this is to add the following line immediately before the close body tag in your Thymeleaf template or layout file:
<script th:if="${@environment.acceptsProfiles('dev','local')}" src="http://localhost:35729/livereload.js"></script>
</body>
In this case I am configuring it so that the javascript is only loaded if my SpringBoot application is running with either the “dev” or “local” profile, so it will never show up in production. You may want to change the name(s) of the profiles to match your setup. The 35729 port is the server side LiveReload server, which is also used for the websocket connection. Make sure you don’t have a local firewall blocking access to that port.
You can test if this is working, by checking to see that the script tag shows up in the page source in your browser. And that the browser does not have any errors while loading the livereload.js file.
Once that is setup, you should be able to edit and save a Thymeleaf file and see your browser reload the page and show your updates immediately.
If you’re using HTTPS either directly, with a proxy or web server, or a ngrok tunnel in front of your SpringBoot application you will need to do some extra work. I will discuss this in a future post.
Again, if you have any suggestions on how to make this work more cleanly, please let me know!
Leave a Reply