1. The role of static content in an API service
When we say “backend service,” an image easily appears in our heads: only JSON, only hardcore, only the terminal. But in real life, a small service is almost always better off having a minimal “entry point for a human”: a page that says what the service is, that it is alive, and where you can click to see the result. And this should happen without template engines, without frontend builds, and without turning the course into “React on max settings.”
Before this, we configured request-side MVC behavior: how request parameters turn into LocalDate, CourseTrack, and other types. But the web layer has another calm surface where Boot already knows a lot by itself: static content and the welcome page. For a regular index.html, CSS, and a couple of links, WebMvcConfigurer is not needed at all: just place the files in the standard location and let the defaults do their job.
Static resources in Spring Boot are like a sticker on a box. The box (your service) may be technically perfect, but if it has neither a name nor instructions saying “open here,” anyone new to it (and even you a week later) will feel disappointed. A small index.html helps solve a very practical task: making the service “self-explanatory” when opened manually in a browser.
The important thing is that we are not building a UI as a separate product. We are doing what is especially valuable in a training project: a fast way to “poke” the service with your eyes and hands without remembering exact URLs and parameters. You already have GET /api/catalog/courses and GET /api/catalog/featured. The browser does not care that this is an “API”: it will open the link and show JSON. And that is perfectly normal for a training read-only service.
Another advantage of static files is that they do not require Spring MVC to participate as a “business layer.” Yes, technically they are served inside the MVC machinery, but your code is not called at all. In other words, if you want an informational page, you do not need to write a controller that returns an HTML string (that looks like “trying to write frontend with annotations”).
2. Static locations and the classpath
When a beginner first hears “static files,” they often, out of habit, remember old-school Java web: webapp/, JSP, some WAR archives, and “deploy to an application server.” In Spring Boot (in the executable jar mode we use in the course), the mindset is different: most resources live in src/main/resources and end up on the classpath. That means something simple: “everything you put into resources will later end up inside the jar and be available to the application.”
By default, Spring Boot knows how to serve static resources from several standard classpath locations. The key is not to memorize them like a spell, but to understand the idea: Boot gives you several “shelves” where you can place HTML/CSS/images, and it will automatically wire up a handler to serve them.
Below is a table worth seeing once and then simply remembering: “I have static/, and that is enough almost all the time.”
| Classpath location inside resources | What is usually placed there | Note |
|---|---|---|
| src/main/resources/static/ | index.html, styles.css, favicon.ico, images | The most popular and intuitive choice for a jar project |
| src/main/resources/public/ | The same | An alternative to static; the difference is often historical |
| src/main/resources/resources/ | The same | Occurs less often |
| src/main/resources/META-INF/resources/ | Library static content, “webjars”-style | More often needed by library authors; we mostly do not need it |
For our catalog-service, the simplest and most “course-correct” path is src/main/resources/static/. That is where you will keep index.html.
To reinforce the mental model, it helps to think of it like this: the static/ folder is a “mini public showcase” that Boot can display directly. And the less we complicate it, the better.
3. Priority: controller and the file for /
After static resources appear, a subtle moment comes up that almost always catches beginners: “I placed index.html, but it does not open at /.” And then the magic rituals begin: cache, the browser is guilty, Tomcat is enchanted... In reality, the culprit is usually a very simple fact: you already have an explicit route in a controller.
To avoid guessing, let us look at the situation schematically. A request in a Boot application (in servlet MVC) follows roughly this route:
flowchart TD
%% Simplified diagram: we show only the key steps and handler selection
A[HTTP request] --> B[Tomcat]
B --> C[DispatcherServlet]
C --> D{Who will handle the path?}
D -->|"Our @Controller / @RestController"| E[Controller method]
D -->|Static resource handler| F[File from classpath]
E --> G[HTTP response]
F --> G[HTTP response]
The key idea: Spring MVC does not “guess” by mood. It looks for a suitable handler. If you have a controller with @GetMapping("/"), then for a request to / it will almost certainly be chosen before the “welcome page.” And the welcome page is designed exactly as a fallback: “if the root is not occupied, let us show index.”
That is why, in the course, we keep a very convenient split: the API lives under /api/..., and the human entry point lives at /. Then there are fewer conflicts, and the project is easier to read.
4. Welcome page via index.html
There is a nice Spring Boot magic that is important to understand correctly: as a “platform convenience,” not as “well, it somehow just works.” If Boot finds index.html in one of the standard static locations, it does two things. First, this file becomes available as a regular static resource (for example, via /index.html). Second, it can become the welcome page, meaning it opens at / — but only if you have not occupied / with an explicit route.
This mechanism is useful precisely because it does not require Java code. No HomeController, no return "<html>...</html>", no annotations. You just need to place the file in the right place.
For clarity, here is the minimal structure we want to get in the project:
src/main/resources/
|-- static/
| `-- index.html
And now an important anti-example: if you create an explicit route / somewhere, the welcome page will stop being the “main” page.
package com.example.catalogservice.catalog.web;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HomeController {
// Explicitly occupy the application root: after this, the welcome page (index.html) on "/" will no longer work
@GetMapping("/")
public String home() {
// For the example, return a simple text so it is clear that the response came from the controller
return "explicit home route";
}
}
If such a controller exists, the GET / request will go here, not to index.html. This is not a bug and not “Spring is glitching”; it is normal routing logic: explicit behavior has priority over implicit behavior.
In catalog-service, the welcome page is usually more useful, because we do not need a smart HTML page that depends on data. We need a “business card”: a title and links. So we choose the simplest and most honest option.
5. index.html for catalog-service
When the service is already returning JSON, the next small victory is to make it so that a person can open http://localhost:8080/ and understand, in literally 10 seconds, “where to click.” This page should not turn into a UI. We are not building an admin panel, not building forms, and not building a template engine. We are making a “navigation sign” — like a list of links in a README, only directly in the service.
Create a file:
`src/main/resources/static/index.html`
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!-- Browser tab title -->
<title>catalog-service</title>
</head>
<body>
<h1>catalog-service</h1>
<p>A mini showcase for manual API checks.</p>
<!-- These links point directly to API endpoints and will show JSON in the browser -->
<a href="/api/catalog/courses">All courses</a><br/>
<a href="/api/catalog/featured">Featured</a><br/>
<a href="/api/catalog/courses/spring-boot">Course by slug: spring-boot</a><br/>
</body>
</html>
There is an important “teaching honesty” here: the browser will open the links and show JSON as is. For services without a UI, that is normal practice. Yes, it is not pretty like an application. But it is practical as a learning tool.
Now let us make the page a little more useful and connect it with what we already know from previous parts: query parameters, converters, and LocalDate formatting in ISO. In other words, we can provide links “with filters” directly on the landing page.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>catalog-service</title>
</head>
<body>
<h1>catalog-service</h1>
<p>Links for manual filter checks.</p>
<h3>Catalog</h3>
<!-- Example of query parameters: limit -->
<a href="/api/catalog/courses?limit=5">First 5 courses</a><br/>
<!-- Example of a boolean filter -->
<a href="/api/catalog/courses?featuredOnly=true">Featured only</a><br/>
<!-- Example of a “human-friendly” value that the converter will turn into an enum -->
<a href="/api/catalog/courses?track=java-backend">Track: java-backend</a><br/>
<!-- Example of an ISO date (useful if you enabled the global ISO format) -->
<a href="/api/catalog/courses?launchedAfter=2026-01-01">Launched after 2026-01-01</a><br/>
</body>
</html>
Notice two things. First, launchedAfter=2026-01-01 is exactly the ISO format that is convenient to support globally through spring.mvc.format.date: iso. Second, track=java-backend is the case where Converter<String, CourseTrack> turns the “human-friendly” variant into an enum inside Java.
If you want to add a little aesthetics without turning it into a design system, you can place a simple CSS file nearby — and that will be a good example of Spring Boot serving more than just HTML.
/* Basic typography: make the page read like a page, not like raw output */
body {
font-family: sans-serif;
max-width: 720px;
margin: 40px auto;
line-height: 1.5;
}
/* A little “breathing room” between links */
a { display: inline-block; margin: 4px 0; }
And then include it in index.html (important: the path is absolute from the root, because the file lives in a static location and is served directly):
<link rel="stylesheet" href="/styles.css">
Yes, this is still a page for engineers, not a UI product. But now at least it looks like a page, not like an error message.
6. Useful static-content nuances
spring.mvc.static-path-pattern and /assets/**
Sometimes you want static files to live not “all over the place” at the root (/styles.css, /logo.png), but in a separate namespace, for example /assets/.... The reasons are usually mundane and practical: you do not want to accidentally collide with future controller routes, you want a more obvious URL structure, or it is easier to configure external caching with a single prefix.
In Spring Boot, this can be done with the spring.mvc.static-path-pattern setting. It changes the public URL pattern for static content, but does not force you to move files to another folder. In other words, the files remain in src/main/resources/static/, but externally they become available under a different path.
Example configuration in application.yaml:
spring:
mvc:
# All static resources will be available under a URL that starts with /assets/
static-path-pattern: "/assets/**"
After that, your CSS will no longer be available at /styles.css. It will be available at /assets/styles.css, and the HTML will need to be adjusted:
<link rel="stylesheet" href="/assets/styles.css">
For the welcome page (/), it is better not to rely on memory or old articles. After changing static-path-pattern, check both paths separately in your Boot version: the application root / and the path under the new prefix, for example /assets/index.html. If you do not need an explicit URL prefix for static content, it is simpler for the training catalog-service to keep the default behavior and avoid adding an extra branch for yourself.
For a training catalog-service, the default behavior is usually enough. Changing static-path-pattern only makes sense if you actually have a conflict or if you deliberately want a single URL prefix for all static files. Otherwise, it turns into a “configuration for the sake of configuration” — and those are better avoided in Boot, otherwise one day you will wake up in a YAML labyrinth and not remember why it was built.
src/main/webapp in a jar project
Many people who have seen old Java EE tutorials have a reflex: “HTML must live in webapp.” In the Spring Boot jar approach, this is usually not needed and only adds confusion. Your project is built as an executable jar, which means resources should be available on the classpath. The most direct and understandable path is src/main/resources/static.
When Gradle builds the application, everything from src/main/resources is packed into the artifact as resources. That means your index.html will really live inside the jar and be served from there. We will talk separately about running “outside the IDE,” but even now it is useful to keep in mind: if a resource lives in resources, the chance that it will travel correctly with the application is much higher than if it is placed somewhere “on the side.”
In short: static/ is not a “temporary folder for development,” but a normal part of the application that survives build and run.
7. Typical mistakes when working with static content
Mistake #1: the file was placed “almost there,” but not there.
Classic case: index.html sits in src/main/resources/templates (because someone heard about templates somewhere), or in src/main/resources/ next to application.yaml, or even in src/main/java. Spring Boot is not a telepath: it will look for the welcome page in the standard static locations. For our course, the safest path is src/main/resources/static/index.html.
Mistake #2: the welcome page does not work because there is already @GetMapping("/").
This looks like mysticism: “I added index.html, but at / I still get some text/JSON.” In reality, this is just the priority of the explicit route. If you already created a HomeController with /, it will win. Either free up / for static content, or accept that the welcome page will not be available as the root.
Mistake #3: you changed spring.mvc.static-path-pattern but forgot to update the links.
After moving static content under /assets/**, old links like href="/styles.css" stop working, and you get a “broken design” (and sometimes the impression that the service “cannot see the file”). There is no magic in such settings: the URL changes, so the links must change too.
Mistake #4: trying to make the landing page “dynamic” and dragging logic into HTML.
Sometimes you want index.html to show the name from spring.application.name or the number of courses. That is already dynamic behavior, and a static page cannot do that by itself. In the course, it is better to accept the limitation and keep the landing page simple. Otherwise, you will very quickly drift into template engines, frontend builds, and other topics that are not the goal of the current block.
Mistake #5: “I changed index.html, but the browser shows the old version.”
This can be ordinary browser cache. It is especially noticeable with styles (styles.css): you fixed the file, but visually nothing changed. In such cases, a hard reload or temporarily disabling cache in devtools helps. This is not a Spring Boot-specific problem — it is a normal “joy” of the web.
GO TO FULL VERSION