Spring Boot 404 Errors Are Usually Your Fault

You request a URL and get a 404 back. Your first instinct is to blame Spring. It wasn't Spring. I spent three hours on a 404 once because my RequestMapping was annotated on the class with /api/v2 and I was hitting /api/v1. The method-level path doesn't override the class-level one. It appends to it. This happens more often than you'd think when you're refactoring versioned endpoints.

What You Need To Know Spring Boot 404 Errors A Devs Guide

A 404 in Spring Boot means the dispatcher servlet couldn't match your request to any HandlerMapping. That's the technical definition. What it actually means in practice is something else entirely, depending on whether you're running a web application, a reactive one, or something that got deployed behind a reverse proxy. Most 404s fall into one of four buckets: wrong URL, wrong HTTP method, the controller isn't actually on the component scan path, or the server context path is eating part of your URL. The last one is the most annoying because it produces a 404 that looks correct in Postman and completely wrong in production.

How Spring Actually Routes a Request

Spring's dispatcher servlet is a front controller. It receives the incoming HTTP request and hands it to a chain of HandlerMapping beans. The default is RequestMappingHandlerMapping, which inspects your @Controller and @RestController classes for path mappings. If it finds a match, it passes the request to the appropriate handler. If it finds nothing, it returns a 404. The chain matters. Handler mappings are ordered. If you have a custom mapping that runs before the default one, it can intercept and block the request before Spring even checks your controllers. I had a case where a security filter was returning a 404 for all unauthenticated requests to paths it didn't recognize, and the real controller was sitting right there underneath it, never getting called. The fix was changing the filter's order so it only ran after a successful handler match.

Get the Full Details

A Technical Guide to Fixing Soft 404 Errors
A Technical Guide to Fixing Soft 404 Errors

The Context Path Problem

When you set server.servlet.context-path=/myapp in application.properties, every URL in your application shifts by that prefix. A mapping of @GetMapping("/users") becomes /myapp/users. This is obvious when you set it yourself. It's not obvious when a DevOps person sets it in a deployment config file and forgets to tell you. Similarly, server.port changes matter. If your dev environment runs on 8080 and production runs on 9090, your health checks and monitoring tools might be hitting the wrong port and getting 404s from a completely different application. My specific headache was deploying a Spring Boot app behind Nginx with a location /api/ block that proxied to the backend. The backend had no context path set, but Nginx was stripping the /api prefix before forwarding. So the request arrived at Spring as /users instead of /api/users, and Spring returned a 404 because the controller was mapped to /api/users. The fix was adding proxy_pass http://backend/api; to keep the prefix intact, or remapping the controller to just /users.

Component Scanning Gaps

Spring Boot scans for components starting from the package where your @SpringBootApplication class lives. Everything below that package is included. Everything above or to the side is ignored. This is by design, but it catches people who put their controllers in a separate module or a sibling package and wonder why they're getting 404s. If you've organized your project with com.example.app as the main package and your controllers live in com.example.api.controllers, you're fine. But if they're in com.example.service.controllers or just com.example.controllers at the same level as the main class, Spring won't find them. You can work around this with @ComponentScan on your main class, specifying additional base packages. Or you can use @EnableJpaRepositories-style annotations on specific packages. But the cleanest fix is usually restructuring the packages so everything falls under the main application's root package.

HTTP Method Mismatches

A @PostMapping hitting a GET request returns a 404, not a 405. Spring's default behavior treats method mismatches as no handler found. This is counter-intuitive. You'd expect a 405 Method Not Allowed. Spring could give you that, but it doesn't by default. To get proper methodNotAllowed responses, you need to configure a HandlerExceptionResolver or use spring.mvc.throw-exception-if-no-handler-found=true combined with a custom exception handler. Even then, you're fighting Spring's default design choice. Most teams just accept the 404 and document it for their frontend developers.

How Do You Handle Validation Errors Globally in Spring Boot? - DEV ...
How Do You Handle Validation Errors Globally in Spring Boot? - DEV ...

Static Resource Conflicts

Spring Boot serves static content from classpath:/static/, classpath:/public/, classpath:/resources/, and classpath:/META-INF/resources/ by default. If you have a file at static/index.html, a request to / will serve that file. If you then add a controller mapped to /, the controller wins. Wait, no. The web MVC configuration serves static resources through a separate chain. A controller mapping to / will actually take precedence over the default static resource handler in most configurations. The real conflict is subtler. If your static file is named api with no extension and you have a controller mapped to /api, Spring might serve the static file instead of routing to your controller, or vice versa, depending on your WebMvcConfigurer setup. I wasted half a day on this once because I had an api directory in my static resources folder that was shadowing my REST controller. Renaming the directory fixed it immediately.

Actuator Endpoints and 404s

Spring Boot Actuator exposes management endpoints like /actuator/health and /actuator/info. By default, only health and info are accessible without authentication. If you enable all endpoints with management.endpoints.web.exposure.include=*, you might expose sensitive data. But more relevantly, actuator endpoints can conflict with your own mappings if you're not careful about the base path. The actuator context path defaults to /actuator, but if you change it to something else with management.endpoints.web.base-path, your health check URLs change too. And if your monitoring tool is hardcoded to /actuator/health, it'll start getting 404s the moment you rename the path.

How to Actually Debug a 404

First, check the request mapping registry. Add spring.mvc.log-resolved-request=false is wrong. You want logging.level.org.springframework.web=DEBUG in your application.properties. This gives you detailed routing information in the logs, including which handler mappings were consulted and why each one rejected the request. Second, verify the URL exactly. Copy it from your browser or Postman. Check for trailing slashes. Spring treats /users and /users/ differently by default. The spring.mvc.pathmatch.use-trailing-slash-match property (removed in Spring Boot 3.x) used to control this behavior. In Boot 3, trailing slashes are never matched unless you explicitly configure it. Third, check that your controller is actually being instantiated. Add a log statement to the constructor or an @PostConstruct method. If it doesn't print, the bean isn't in the context. Check your package structure again. Check for conditional annotations like @ConditionalOnProperty that might be preventing instantiation.

Common Spring Boot Errors and Their Solutions: Problems Developers ...
Common Spring Boot Errors and Their Solutions: Problems Developers ...

Fourth, look at the deployed artifact. If you're using Maven or Gradle, make sure your pom.xml or build.gradle includes the source directory in the build. I've seen projects where the controller was in a src/main/java subdirectory that wasn't marked as a source root in the IDE, so the compiler silently skipped it. The build succeeded, the application started, and half the endpoints were missing.

Common Misconfigurations That Cause 404s

Missing @RestController or @Controller annotation. The class won't be picked up as a handler without one of these. A plain POJO with @RequestMapping methods does nothing. Scope issues. If your controller is defined as a @Scope that doesn't include singleton or prototype (rare, but possible with custom scopes), Spring might not register it properly. Filter chains blocking requests. Any Filter or OncePerRequestFilter that returns without calling chain.doFilter() will effectively make that path disappear. A 404-looking response from a filter is indistinguishable from a real 404.

Profile-specific beans. A controller annotated with @Profile("prod") won't exist in your local dev environment. This is useful but easy to forget when switching contexts.

Spring Boot Error Handling Guide
Spring Boot Error Handling Guide

What Spring Boot 404s Can't Tell You

A 404 gives you zero information about why the match failed. Was it the path? The method? The media type? The handler mapping that rejected it? The framework doesn't say. You have to dig into logs, enable debug mode, and sometimes write temporary diagnostic code to trace the request through the handler chain. This is the main limitation of relying solely on Spring Boot's default error handling. For production systems, consider adding a custom ErrorController implementation that logs the full request details before returning the 404 response. It won't fix the problem, but it'll save you from guessing what went wrong. The reality is that most 404s in Spring Boot are configuration problems, not code bugs. The framework is doing exactly what you told it to do. The question is whether what you told it to do matches what you intended it to do. Check the URL, check the package, check the annotations, and check the deployment config before you start rewriting code.