Fix: Spring Boot Whitelabel Error Page - This application has no explicit mapping
Part of: Java & JVM Errors
Quick Answer
How to fix the Spring Boot Whitelabel Error Page caused by missing controller mappings, wrong component scan, wrong package structure, and missing Thymeleaf templates.
The Error
You open your Spring Boot app in the browser and see:
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
There was an unexpected error (type=Not Found, status=404).Or variations:
There was an unexpected error (type=Internal Server Error, status=500).There was an unexpected error (type=Method Not Allowed, status=405).Spring Boot could not find a controller method to handle the requested URL. Instead of a raw HTTP error, it shows the Whitelabel Error Page as a fallback.
Why This Happens
Spring Boot maps HTTP requests to @Controller or @RestController methods using @RequestMapping, @GetMapping, @PostMapping, etc. When no mapping matches the requested URL, the DispatcherServlet falls through to the default error path /error. Because the default BasicErrorController is registered automatically, that path renders the Whitelabel page instead of a raw HTTP error. The whitelabel page is therefore not the bug — it is the symptom that no mapping handled your request.
The dispatch flow has three places where the request can be silently dropped. First, the controller class itself might never be registered as a Spring bean, which happens when component scanning does not reach its package or the stereotype annotation is missing. Second, the class might be registered as a bean but its handler methods are not exposed as request mappings, which happens when you use @Component instead of @Controller/@RestController. Third, the method might exist but the URL, HTTP verb, or content type does not line up with the incoming request. Each of these produces the same generic fallback page, which is why this error is so often misdiagnosed.
Common causes:
- Missing or wrong
@RequestMapping. The URL you are accessing does not match any controller endpoint. - Controller not scanned. The controller class is not in a package that Spring Boot scans.
- Wrong HTTP method. You send a GET request but the controller only handles POST.
- Missing
@RestControlleror@Controller. The class is not annotated as a controller. - Application class in the wrong package. The
@SpringBootApplicationclass must be in a root package that encompasses all controllers. - Thymeleaf template not found. A
@Controllermethod returns a view name but the template does not exist.
In Production: Incident Lens
The dangerous pattern is “fine in dev, whitelabel in prod.” Local builds run from your IDE, where the run configuration sets the working directory and classpath in a way that pulls every controller. The fat JAR built by mvn package or the Docker image built from a multi-stage Dockerfile might exclude a module, skip an @Configuration class because of an @ConditionalOnProperty, or boot with a different spring.profiles.active that disables a WebMvcConfigurer. The first signal in production is a flat 100% 4xx rate on the affected route — the blast radius is every request to that path, not a slow degradation.
Detection should not depend on a user filing a ticket. Wire an alert on the 5xx and 4xx rate per route in your APM or load balancer (CloudWatch target group metrics, ALB 4xx_Count, Datadog http.requests by http.route). A sudden cliff to all-404 or all-500 immediately after a deploy is the telltale fingerprint. The recovery sequence is almost always rollback first, debug second: redeploy the previous image, restore the SLO, then reproduce the issue locally against the bad artifact. Trying to forward-fix a whitelabel storm under pressure is what causes the second incident.
The postmortem preventive has two layers. At the platform layer, lock down what the page reveals — set server.error.include-stacktrace=never and server.error.include-message=never in any non-dev profile, and register a @ControllerAdvice that returns structured JSON with a correlation ID instead of the HTML whitelabel. At the test layer, add a smoke test that hits every advertised route after the application context starts. A @SpringBootTest with TestRestTemplate that loops over your route registry and asserts non-404 status catches missing mappings before the artifact ever leaves CI.
Fix 1: Add a Controller with the Right Mapping
The most common fix. Create a controller that handles the URL you are accessing:
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HomeController {
@GetMapping("/")
public String home() {
return "Hello, World!";
}
@GetMapping("/api/status")
public Map<String, String> status() {
return Map.of("status", "running");
}
}Key annotations:
@RestController— for REST APIs (returns JSON/text directly)@Controller— for server-rendered views (returns template names)@GetMapping("/path")— handles GET requests to/path@PostMapping("/path")— handles POST requests to/path
Check that the URL matches:
@GetMapping("/api/users") // Accessible at http://localhost:8080/api/users
public List<User> getUsers() { ... }If you access http://localhost:8080/users (missing /api), you get the whitelabel page.
Fix 2: Fix the Package Structure
Spring Boot’s @SpringBootApplication enables component scanning starting from the package the main class is in. If your controllers are in a package outside this scan range, they are invisible to Spring.
Broken structure:
com.example.app.MyApplication.java ← @SpringBootApplication
com.other.controllers.UserController.java ← NOT scanned!Fixed — controllers in a sub-package:
com.example.app.MyApplication.java
com.example.app.controller.UserController.java ← Scanned ✓
com.example.app.service.UserService.java ← Scanned ✓The @SpringBootApplication class at com.example.app scans com.example.app and all sub-packages.
Fix — explicitly specify scan packages:
@SpringBootApplication(scanBasePackages = {"com.example.app", "com.other.controllers"})
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}Pro Tip: Always put your
@SpringBootApplicationclass in the root package of your project. Every controller, service, and repository should be in a sub-package. This is the standard convention and avoids scanning issues.
Fix 3: Check @Controller vs @RestController
Using @Controller without Thymeleaf or a view resolver causes a different problem — Spring looks for a template file instead of returning the string:
Broken:
@Controller
public class HomeController {
@GetMapping("/")
public String home() {
return "Hello, World!"; // Spring looks for a template named "Hello, World!"
}
}Fixed — use @RestController for API responses:
@RestController
public class HomeController {
@GetMapping("/")
public String home() {
return "Hello, World!"; // Returns the string directly
}
}Or use @ResponseBody with @Controller:
@Controller
public class HomeController {
@GetMapping("/")
@ResponseBody
public String home() {
return "Hello, World!";
}
}For template-based responses, create the template:
If you want @Controller to render HTML templates, add Thymeleaf and create the template:
@Controller
public class HomeController {
@GetMapping("/")
public String home(Model model) {
model.addAttribute("message", "Hello!");
return "home"; // Looks for src/main/resources/templates/home.html
}
}Create src/main/resources/templates/home.html:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<h1 th:text="${message}">Placeholder</h1>
</body>
</html>Fix 4: Fix the HTTP Method
A 405 whitelabel error means the URL exists but not for the HTTP method you used:
@PostMapping("/api/users")
public User createUser(@RequestBody User user) { ... }Accessing GET http://localhost:8080/api/users in the browser returns 405 because only POST is mapped.
Fix: Add the correct mapping:
@GetMapping("/api/users")
public List<User> getUsers() { ... }
@PostMapping("/api/users")
public User createUser(@RequestBody User user) { ... }Or use @RequestMapping for multiple methods:
@RequestMapping(value = "/api/users", method = {RequestMethod.GET, RequestMethod.POST})
public ResponseEntity<?> handleUsers(HttpServletRequest request) { ... }Fix 5: Check Server Port and Context Path
You might be accessing the wrong URL:
Check the port:
# application.properties
server.port=8081If the port is 8081, access http://localhost:8081/, not http://localhost:8080/.
Check the context path:
# application.properties
server.servlet.context-path=/api/v1With this setting, all URLs are prefixed: http://localhost:8080/api/v1/users instead of http://localhost:8080/users.
Check for a reverse proxy prefix:
Behind Nginx or a load balancer, the path might be different. Check your proxy configuration.
Fix 6: Debug Registered Mappings
See exactly which URLs Spring Boot has registered:
Enable Actuator:
Add to pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>In application.properties:
management.endpoints.web.exposure.include=mappingsAccess http://localhost:8080/actuator/mappings to see all registered URL mappings.
Or add debug logging:
logging.level.org.springframework.web=DEBUGSpring logs every registered mapping at startup:
Mapped "{[/api/users],methods=[GET]}" onto public java.util.List<User> UserController.getUsers()If your controller is not listed, it is not being scanned (see Fix 2).
Common Mistake: Adding a controller class but forgetting to annotate it with
@RestControlleror@Controller. Without the annotation, Spring does not register it as a request handler. Check that the annotation is present and imported fromorg.springframework.web.bind.annotation.
Fix 7: Customize the Error Page
If you want to replace the whitelabel page with a custom error page:
Disable the whitelabel page:
server.error.whitelabel.enabled=falseAdd a custom error controller:
@Controller
public class CustomErrorController implements ErrorController {
@RequestMapping("/error")
public String handleError(HttpServletRequest request) {
Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
if (status != null) {
int statusCode = Integer.parseInt(status.toString());
if (statusCode == 404) {
return "error-404";
}
}
return "error";
}
}Add static error pages:
Place HTML files in src/main/resources/static/error/:
404.html— displayed for 404 errors500.html— displayed for 500 errors4xx.html— catch-all for 4xx errors5xx.html— catch-all for 5xx errors
Spring Boot automatically serves these based on the error status code.
Fix 8: Fix Spring Security Blocking
Spring Security might block access to your controller before it is even reached:
There was an unexpected error (type=Forbidden, status=403).Fix: Configure security to permit your endpoints:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/", "/api/public/**").permitAll()
.anyRequest().authenticated()
);
return http.build();
}
}For development, permit all (not for production):
http.authorizeHttpRequests(auth -> auth.anyRequest().permitAll());If Spring Security keeps returning 403 after you whitelist a path, check the order of your filter chain beans and make sure your permitAll() rule comes before anyRequest().authenticated().
Still Not Working?
If the whitelabel error persists:
Check for classpath issues. Run mvn dependency:tree or gradle dependencies to verify all Spring Boot dependencies are present. A missing spring-boot-starter-web dependency means no embedded server.
Check for port conflicts. If port 8080 is already in use, Spring Boot might fail to start or start on a different port. See Fix: Spring Boot Port Already in Use for resolving the bind exception cleanly.
Check for profile-specific configuration. A different application-{profile}.properties might override your settings:
java -jar app.jar --spring.profiles.active=prodA profile-specific spring.mvc.servlet.path or a profile that excludes WebMvcAutoConfiguration can unregister every controller without changing your Java code. Diff the resolved properties between profiles with --debug at startup to confirm which file actually won.
Check the build output. If using Maven, ensure the project builds without errors:
mvn clean packageFor Maven dependency resolution failures, see Fix: Maven could not resolve dependencies.
Check for circular dependencies. Spring might fail to create beans if there are circular dependencies. The error message in the console will mention BeanCurrentlyInCreationException. When the application context fails halfway, some controllers are registered and others are not, producing a mixed pattern where only certain routes return whitelabel. See Fix: Spring Bean Creation Exception for unwinding the dependency graph.
Enable full stack trace logging (dev only):
server.error.include-stacktrace=always
server.error.include-message=alwaysThis shows the full error details on the whitelabel page, making debugging easier. Revert these in production — leaking stack traces is a recon goldmine for attackers.
Check the request actually reaches the application. A reverse proxy that strips or rewrites the path can produce a whitelabel for a route that works fine when you curl the container directly. Compare the path that hits the JVM (logging.level.org.springframework.web=TRACE) with what your client sends.
Check for shadowed JAR resources. If you build a fat JAR with overlapping resources, META-INF/spring.factories files can be overwritten and disable autoconfiguration. Use spring-boot-maven-plugin’s repackage goal rather than a plain shade plugin. Symptom: the JAR boots without errors but BasicErrorController is the only mapping that registers, so every URL returns whitelabel.
Check the embedded servlet container choice. A spring-boot-starter-undertow or spring-boot-starter-jetty pulled in alongside the default tomcat starter can cause the wrong DispatcherServlet to be registered. Open mvn dependency:tree -Dincludes=org.springframework.boot:spring-boot-starter-* and confirm only one web starter is on the path.
Check for @WebFilter or @WebServlet registrations. Filters and servlets registered via @ServletComponentScan run before the DispatcherServlet and can short-circuit the request with chain.doFilter() never called. Comment out the filter temporarily and confirm whether the whitelabel page disappears.
If the Gradle build itself fails before you can run the application, see Fix: Gradle build failed.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Spring Boot @Cacheable Not Working — Cache Miss Every Time or Stale Data
How to fix Spring Boot @Cacheable issues — @EnableCaching missing, self-invocation bypass, key generation, TTL configuration, cache eviction, and Caffeine vs Redis setup.
Fix: Spring Data JPA Query Not Working — @Query, Derived Methods, and N+1 Problems
How to fix Spring Data JPA query issues — JPQL vs native SQL, derived method naming, @Modifying for updates, pagination, projections, and LazyInitializationException.
Fix: Spring Boot @Transactional Not Rolling Back — Transaction Committed Despite Exception
How to fix Spring @Transactional not rolling back — checked vs unchecked exceptions, self-invocation proxy bypass, rollbackFor, transaction propagation, and nested transactions.
Fix: Hibernate LazyInitializationException — Could Not Initialize Proxy
How to fix Hibernate LazyInitializationException — loading lazy associations outside an active session, fetch join, @Transactional scope, DTO projection, and Open Session in View.