You open http://localhost:8080/api/orders in a browser and instead of JSON you get this:
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Sat Oct 04 10:15:30 IST 2026
There was an unexpected error (type=Not Found, status=404).On Spring Boot 3.2 and later the console usually shows the real reason a moment earlier:
WARN ... o.s.w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.servlet.resource.NoResourceFoundException: No static resource api/orders.]The page itself is harmless. It is Spring Boot’s default error view, rendered because nothing in your application handles /error. The important part is status=404: Spring MVC found no controller method for that path and method, fell back to looking for a static file with that name, found none, and forwarded to the error page.
Quick fix checklist
- Compare the URL you called with the full mapping: class-level
@RequestMappingplus method-level@GetMapping, plusserver.servlet.context-path. - Make sure the controller is in the same package as the
@SpringBootApplicationclass or a sub-package. - Use
@RestController(or@ResponseBody) when you want JSON, not a view. - Check the HTTP method: a
@PostMappingcalled from the browser address bar returns 405, not 404. - Remove a stray trailing slash: Spring Framework 6 no longer treats
/orders/as/orders. - If you return a view name, add a template engine and put the template in
src/main/resources/templates.
Before you start
You need a Spring Boot 3.x web application (spring-boot-starter-web) and the ability to read the console log. Know the difference between a request path and a Java package name, and know which port and context path the app runs on: the startup line Tomcat started on port 8080 (http) with context path '/' tells you both. Code in this article targets Spring Boot 3.x.
Why it happens
Every request goes through DispatcherServlet, which asks its handler mappings, in order, who can serve the request:
RequestMappingHandlerMappingholds every@RequestMappingmethod found on beans annotated@Controlleror@RestController. It matches on path, HTTP method, and optionally headers,consumesandproduces.- If nothing matches, the static resource handler looks in
classpath:/static/,/public/,/resources/and/META-INF/resources/for a file with the request path. Since Spring Framework 6.1 a miss throwsNoResourceFoundException, which becomes a 404.
The servlet container then forwards to /error. Spring Boot registers BasicErrorController there. For browser requests (Accept: text/html) it renders the Whitelabel view; for API clients it returns JSON such as {"status":404,"error":"Not Found","path":"/api/orders"}. The “no explicit mapping for /error” sentence only means you have not supplied your own error page.
So the question is always: why did no handler match? The usual answers are that the controller is not a bean, the path is different from what you typed, or the handler matched but returned a view name that could not be resolved.
Step-by-step walkthrough
Step 1: Reproduce with curl and read the log
Browsers add noise (favicons, cached redirects). Use curl and watch the log:
curl -i http://localhost:8080/api/ordersA 404 with NoResourceFoundException in the log means no controller matched. A 405 with HttpRequestMethodNotSupportedException means the path matched but the method did not. A 500 means a handler ran and something failed after it, often view resolution.
Step 2: List the mappings Spring actually registered
Turn on mapping logs temporarily:
logging.level._org.springframework.web.servlet.HandlerMapping.Mappings=TRACEThe leading underscore is not a typo: Spring logs mappings on a deliberately hidden logger name. At startup it prints each controller and its mapped paths. If your controller is absent, it is not a bean. If it is present, compare its paths with your URL character by character. With Actuator, /actuator/mappings shows the same list (expose it only locally).
Step 3: Fix component scanning
@SpringBootApplication scans the package of the class it annotates and everything below it. This layout is a classic trap:
com.shop.app.ShopApplication // scans com.shop.app.**
com.shop.web.OrderController // never scannedMove the application class up to com.shop, or move the controller under com.shop.app. Avoid scanBasePackages as the first fix; a predictable package layout keeps every other annotation-driven feature working too.
Step 4: Check the full path and method
The real path is context path + class mapping + method mapping:
package com.shop.app.orders;
import java.util.List;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@GetMapping
public List<OrderDto> list() {
return List.of(new OrderDto(1L, "OPEN"));
}
@GetMapping("/{id}")
public OrderDto one(@PathVariable Long id) {
return new OrderDto(id, "OPEN");
}
public record OrderDto(Long id, String status) {}
}With server.servlet.context-path=/shop, this is served at /shop/api/orders, and /api/orders returns 404. Since Spring Framework 6, /shop/api/orders/ (trailing slash) is also a 404 unless you map it explicitly.
Step 5: Fix view-returning controllers
@Controller treats a returned String as a view name. Without a template engine on the classpath, Spring forwards to a resource with that name. When that resource does not exist you get a 404, and when the view name equals the current path you get a 500 with Circular view path [orders]. Either use @RestController for data, or add spring-boot-starter-thymeleaf and create src/main/resources/templates/orders.html.
Worked scenario
A developer builds a small admin page. The controller compiles and the app starts, but /admin/orders shows the Whitelabel page with status 404.
package com.shop.admin;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class AdminController {
@GetMapping("/admin/orders")
public String orders() {
return "admin/orders";
}
}The application class is com.shop.store.StoreApplication. The TRACE mapping log does not list AdminController at all: the controller is in com.shop.admin, a sibling of com.shop.store, so component scanning never reaches it. That is the 404.
The developer moves StoreApplication to com.shop. Now the mapping log lists AdminController and a breakpoint in orders() is hit, yet the browser still shows a 404. The handler did run. It returned the view name admin/orders, and with no template engine on the classpath Spring forwarded the request to a resource derived from that name, which does not exist. The NoResourceFoundException in the log now mentions a path built from the view name rather than the URL that was typed: that difference is the clue that a forward happened. Adding Thymeleaf and the template completes the fix:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>src/main/resources/templates/admin/orders.htmlTwo different causes produced the same 404 here, and only the log told them apart. With Thymeleaf present but the template missing, the status would change to 500 with Error resolving template [admin/orders], which is progress: the request now fails in the view layer, not in routing.
Common mistake
The most tempting wrong fix is server.error.whitelabel.enabled=false. It removes the page, not the problem: the request still fails, and now the container’s plain error page appears instead. Disable Whitelabel only after you have your own error handling.
Another: creating an @RequestMapping("/error") method in your own controller to “handle” it. That conflicts with BasicErrorController. If you need custom error pages, use Boot’s conventions. Static pages go in src/main/resources/static/error/404.html; with a template engine use templates/error/404.html or templates/error/4xx.html. For APIs, a @RestControllerAdvice that returns ProblemDetail, or spring.mvc.problemdetails.enabled=true, gives clients a structured body. Useful settings while debugging (not in production):
server.error.include-message=always
server.error.include-stacktrace=on_paramFinally, putting HTML in templates/ and expecting it to be served at /about.html. Templates are only rendered through a controller; static files belong in static/.
Verify the behavior
A @WebMvcTest proves the mapping without starting the whole app:
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.web.servlet.MockMvc;
@WebMvcTest(OrderController.class)
class OrderControllerTest {
@Autowired
MockMvc mvc;
@Test
void listsOrders() throws Exception {
mvc.perform(get("/api/orders"))
.andExpect(status().isOk())
.andExpect(jsonPath("$[0].status").value("OPEN"));
}
@Test
void trailingSlashIsNotMatched() throws Exception {
mvc.perform(get("/api/orders/"))
.andExpect(status().isNotFound());
}
}MockMvc ignores server.servlet.context-path, so also check the running app with curl -i http://localhost:8080/shop/api/orders when you use one. Expect HTTP/1.1 200 and no NoResourceFoundException in the log.
Interview exercise
“A colleague says the Whitelabel Error Page means Spring Boot has a bug in its error handling. What does the page actually tell you, and how would you trace a 404 to its cause?”
Answer and reasoning
The page is a fallback view served by BasicErrorController at /error after something else failed; the message “no explicit mapping for /error” only says the app has no custom error page. The useful information is the status. For 404, DispatcherServlet found no handler and the static resource handler found no file, which Spring Framework 6.1+ logs as NoResourceFoundException. I would reproduce with curl to avoid browser noise, enable mapping logs or check /actuator/mappings to see whether the controller is registered, and then compare the registered path with the requested one, including context path, HTTP method and trailing slash. If the controller is missing, the cause is usually component scanning or a missing stereotype annotation. If a 500 appears instead, the handler ran and the failure is later, often view resolution with @Controller.