Spring MVC
Spring Boot
Static Content
Web Development
Java

Refreshing static content with Spring MVC and Boot

Data Structures & Algorithms practice on Codemia

Step through 300+ coding interview problems with animated visualisers that show the data structure changing as the code runs. The Blind 75 are free.

Practice algorithms

Introduction

During development, changing a CSS, JavaScript, or HTML file in a Spring Boot application requires a server restart to see the update. This slows down front-end development. Spring Boot provides several ways to serve fresh static content without restarts: Spring DevTools enables automatic reload, resource versioning with content hashing ensures browsers fetch updated files, and cache-control headers prevent stale content in production. This article covers each approach for both development-time live reload and production-ready cache-busting.

Default Static Content Locations

Spring Boot serves static files from these classpath directories (in priority order):

  1. classpath:/META-INF/resources/
  2. classpath:/resources/
  3. classpath:/static/
  4. classpath:/public/

Files can also be served from file: locations outside the classpath.

 
1src/main/resources/
2├── static/
3│   ├── css/
4│   │   └── style.css
5│   ├── js/
6│   │   └── app.js
7│   └── images/
8│       └── logo.png
9└── templates/
10    └── index.html

Development: Spring DevTools (Live Reload)

Spring DevTools automatically restarts the application when classpath files change, and includes a LiveReload server that refreshes the browser:

xml
1<dependency>
2    <groupId>org.springframework.boot</groupId>
3    <artifactId>spring-boot-devtools</artifactId>
4    <scope>runtime</scope>
5    <optional>true</optional>
6</dependency>
properties
1# application.properties
2spring.devtools.restart.enabled=true
3spring.devtools.livereload.enabled=true
4
5# Trigger restart only on specific file changes
6spring.devtools.restart.additional-paths=src/main/resources/static
7# Exclude paths from triggering restart (just live reload)
8spring.devtools.restart.exclude=static/**,public/**,templates/**

With restart.exclude=static/**, changing CSS/JS files triggers a LiveReload (browser refresh) without a full application restart. Install the LiveReload browser extension for automatic refresh.

IDE Configuration

For IntelliJ IDEA, enable automatic build on save:

  1. Settings > Build, Execution, Deployment > Compiler > "Build project automatically"
  2. Settings > Advanced Settings > "Allow auto-make to start even if developed application is currently running"

For Eclipse/STS, automatic build is enabled by default.

Development: Serve from File System

Serve static files directly from the file system so changes are visible immediately without any restart:

properties
# Serve static files from the project directory (not classpath)
spring.web.resources.static-locations=file:src/main/resources/static/,classpath:/static/
java
1@Configuration
2public class WebConfig implements WebMvcConfigurer {
3    @Override
4    public void addResourceHandlers(ResourceHandlerRegistry registry) {
5        // Serve from file system with no caching
6        registry.addResourceHandler("/static/**")
7                .addResourceLocations("file:src/main/resources/static/")
8                .setCachePeriod(0);  // No caching in development
9    }
10}

Files served from file: locations are read directly from disk on each request, so edits are visible immediately.

Production: Cache-Busting with Content Hashing

In production, static files should be cached aggressively but invalidated when content changes. Spring's VersionResourceResolver appends a content hash to filenames:

java
1@Configuration
2public class WebConfig implements WebMvcConfigurer {
3    @Override
4    public void addResourceHandlers(ResourceHandlerRegistry registry) {
5        registry.addResourceHandler("/static/**")
6                .addResourceLocations("classpath:/static/")
7                .resourceChain(true)  // Enable resource chain
8                .addResolver(new VersionResourceResolver()
9                    .addContentVersionStrategy("/**"));  // Hash-based versioning
10    }
11}
properties
# application.properties
spring.web.resources.chain.strategy.content.enabled=true
spring.web.resources.chain.strategy.content.paths=/**

This transforms URLs like /static/css/style.css into /static/css/style-abc123def.css. When the file changes, the hash changes, and browsers fetch the new version.

Using in Thymeleaf Templates

html
1<!-- Thymeleaf automatically resolves versioned URLs -->
2<link rel="stylesheet" th:href="@{/static/css/style.css}" />
3<!-- Renders as: /static/css/style-abc123def.css -->
4
5<script th:src="@{/static/js/app.js}"></script>
6<!-- Renders as: /static/js/app-xyz789ghi.js -->

Using ResourceUrlProvider in JSP

java
1@ControllerAdvice
2public class ResourceUrlAdvice {
3
4    @Autowired
5    private ResourceUrlProvider resourceUrlProvider;
6
7    @ModelAttribute("urls")
8    public ResourceUrlProvider urls() {
9        return resourceUrlProvider;
10    }
11}
jsp
<link rel="stylesheet" href="${urls.getForLookupPath('/static/css/style.css')}" />

Production: Cache-Control Headers

properties
1# Cache static resources for 1 year (versioned URLs make this safe)
2spring.web.resources.cache.cachecontrol.max-age=365d
3
4# Or configure per resource type
5spring.web.resources.cache.cachecontrol.cache-public=true
java
1@Configuration
2public class WebConfig implements WebMvcConfigurer {
3    @Override
4    public void addResourceHandlers(ResourceHandlerRegistry registry) {
5        registry.addResourceHandler("/static/**")
6                .addResourceLocations("classpath:/static/")
7                .setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS)
8                    .cachePublic());
9    }
10}

With content-hashed URLs, setting a long cache duration is safe because changed files get new URLs.

Production: Fixed Version Strategy

If content hashing is not suitable, use a fixed version string (e.g., application version):

properties
spring.web.resources.chain.strategy.fixed.enabled=true
spring.web.resources.chain.strategy.fixed.paths=/**
spring.web.resources.chain.strategy.fixed.version=v2.1.0

This transforms /css/style.css into /v2.1.0/css/style.css. Update the version on each deployment.

Profile-Based Configuration

properties
1# application-dev.properties
2spring.web.resources.cache.cachecontrol.no-cache=true
3spring.web.resources.chain.enabled=false
4spring.devtools.livereload.enabled=true
5
6# application-prod.properties
7spring.web.resources.cache.cachecontrol.max-age=365d
8spring.web.resources.chain.strategy.content.enabled=true
9spring.web.resources.chain.strategy.content.paths=/**

Common Pitfalls

  • Forgetting to disable caching in development: Without setCachePeriod(0) or no-cache headers, the browser caches static files and you do not see changes even after restarting. Set spring.web.resources.cache.cachecontrol.no-cache=true for development profiles.
  • Using DevTools in production: Spring DevTools is intended for development only. It is automatically disabled when running from a packaged JAR, but ensure it is declared with <scope>runtime</scope> and <optional>true</optional> to prevent accidental inclusion.
  • Setting long cache durations without content hashing: If you set max-age=365d but do not use versioned URLs, browsers serve stale files until the cache expires. Always pair long cache durations with content hashing or a fixed version strategy.
  • Not using Thymeleaf's @{} syntax for resource URLs: Hardcoding paths like href="/css/style.css" bypasses Spring's resource versioning. Use th:href="@{/css/style.css}" so Spring resolves the versioned URL automatically.
  • Mixing file: and classpath: locations incorrectly: When both are configured, file: takes precedence for matching paths. In production, remove file: locations to ensure static files are served from the packaged JAR, not from a nonexistent file path.

Summary

  • Use Spring DevTools with LiveReload for instant static content updates during development
  • Serve from file: locations to avoid restarts entirely for front-end changes
  • Use VersionResourceResolver with content hashing for production cache-busting
  • Set Cache-Control: max-age=365d with versioned URLs for optimal browser caching
  • Use Spring profiles to separate development (no-cache, DevTools) from production (long cache, versioning) configurations

Course
Intermediate
27 lessons
14 hours
OOD Fundamentals

Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.

View the course
Data Structures & Algorithms practice on Codemia

Step through 300+ coding interview problems with animated visualisers that show the data structure changing as the code runs. The Blind 75 are free.

Practice algorithms