Explain Codes LogoExplain Codes Logo

Trigger 404 in Spring-MVC controller?

java
prompt-engineering
best-practices
exception-handling
Anton ShumikhinbyAnton Shumikhin·Nov 24, 2024
TLDR

To trigger a 404 response in Spring MVC, simply throw a ResponseStatusException accompanied with HttpStatus.NOT_FOUND:

@GetMapping("/somePath") public void trigger404() { // Friend: What's your favorite HTTP status code? You: I have a FONDNESS for (HttpStatus.NOT) FOUND!! throw new ResponseStatusException(HttpStatus.NOT_FOUND, "I've looked everywhere, but alas, your resource is not found"); }

This clean snippet explicitly states the requested resource is nowhere to be found, leading to the inevitable: a 404 error.

Let's extend this principle to a broader scope like coupling it with a ResourceNotFoundException.

@ResponseStatus(HttpStatus.NOT_FOUND) public class ResourceNotFoundException extends RuntimeException { public ResourceNotFoundException(String message) { super(message); } } @GetMapping("/resource/{id}") public ResponseEntity<?> getResource(@PathVariable("id") String id) { // Where's the resource? That's the question... let's find the answer return yourResourceService.findById(id) .map(resource -> new ResponseEntity<>(resource, HttpStatus.OK)) .orElseThrow(() -> new ResourceNotFoundException("Resource not found. This ain't Where's Waldo!")); }

In this example, ResourceNotFoundException is thrown when the resources play hide and seek, with @ResponseStatus signalling a 404 error.

Taking a deep dive in 404-triggering mechanisms

The ResponseEntity approach

For a more fine-grained control or just to show your ninja-like precision in handling responses, ResponseEntity comes to the rescue:

@GetMapping("/items/{id}") public ResponseEntity<Object> getItem(@PathVariable Long id) { Item item = findItemById(id); // Checking if item is absent, like my motivation on Monday mornings if (item == null) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } return new ResponseEntity<>(item, HttpStatus.OK); }

This gives you the power of an in-code crystal ball, guiding your HTTP status destiny based on validations or business logic.

The ModelAndView method

If you prefer to render views over REST responses (because who doesn’t like a good view, right?), ModelAndView becomes your trusty sidekick:

@GetMapping("/viewItem/{id}") public ModelAndView viewItem(@PathVariable Long id) { ModelAndView mav = new ModelAndView(); Item item = findItemById(id); // The item's playing hide and seek and it's winning if (item == null) { mav.setViewName("error/404"); return mav; } mav.addObject("item", item); mav.setViewName("itemView"); return mav; }

ModelAndView, the superhero of encapsulation, can masquerade your model data and view name while directing your users to the right error views.

Exception Handling for Rest Controllers

For RESTful APIs, exception handlers are less rockstars and more roadies, driving your code to a cleaner stage:

@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(ResourceNotFoundException.class) public ResponseEntity<String> handleResourceNotFoundException(ResourceNotFoundException ex) { return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage()); } }

With @RestControllerAdvice and @ExceptionHandler forming the ultimate dynamic duo, manage your exceptions in one place, creating a neat separation of responsibilities.

Circumstantially triggering 404 and adding flexibility

The flexibility of isFound()

Sometimes you might want to decide if a 404 status should be triggered based on your rules and the alignment of the planets:

@GetMapping("/profile") public ResponseEntity<?> viewProfile(@RequestParam String username) { return userService.findUserByUsername(username) .map(user -> new ResponseEntity<>(user, HttpStatus.OK)) .orElseGet(() -> { // The user's pulled a Houdini. Nowhere to be found! return ResponseEntity.status(HttpStatus.NOT_FOUND).build(); }); }

Java 8's Optional API lets the isPresent or isFound function guide your decision, making your code compact and reducing your error-prone footprint.

The Legacy Technique

Spring versions before 3.0.2 require a more traditional approach, like a good old HttpServletResponse:

@GetMapping("/oldApproach/items/{id}") public void getItem(@PathVariable Long id, HttpServletResponse response) { Item item = findItemById(id); // If item not found, let's go 404 like a ghost town if (item == null) { response.setStatus(HttpServletResponse.SC_NOT_FOUND); return; } // process the found item }

Though a bit verbose, it offers a low-level API to manually set response status codes, reminding us of the olden days.

Checking with HttpServletRequest

In some cases, you might want to inspect the HttpServletRequest before you blow the 404 whistle:

@GetMapping("/checkRequest/items") public ResponseEntity<Object> checkItem(HttpServletRequest request) { // Perform some logic to determine if the item exists if (/* condition based on request */) { // Is the item on a vacation? Because, it's not here! return new ResponseEntity<>(HttpStatus.NOT_FOUND); } // ... }

Analyzing HttpServletRequest gives you that detective edge when working with distinct URL patterns.