Line At A Time
Lesson 8: Not Found: Status Codes
Not Found: Status Codes Answer 200 OK when you can, and 404 Not Found when you can't.
1import java.util.List;
2import org.springframework.http.ResponseEntity;
3import org.springframework.web.bind.annotation.*;
4
5@RestController
6public class PizzaFinder {
7 private final List<String> menu = List.of("Margherita", "Pepperoni");
8
9 @GetMapping("/pizzas/{id}")
10 public ResponseEntity<String> pizza(@PathVariable int id) {
11 if (id >= 0 && id < menu.size()) {
12 return ResponseEntity.ok(menu.get(id));
13 }
14 return ResponseEntity.notFound().build();
15 }
16}
Terminal
$ ./mvnw spring-boot:run
:: Spring Boot :: (v3.3.0)
Tomcat started on port 8080 (http)
Started DemoApplication in 1.24 seconds
$ curl -i localhost:8080/pizzas/0
HTTP/1.1 200 OK
Margherita
$ curl -i localhost:8080/pizzas/9
HTTP/1.1 404 Not Found
Every line, explained
import java.util.List;
The standard-library List import again.
import org.springframework.http.ResponseEntity;
ResponseEntity is Spring's wrapper for a full response: the data plus a status code.
import org.springframework.web.bind.annotation.*;
This import brings in Spring's web annotations (@RestController, @GetMapping and friends). The .* means "everything in this package", which saves one import line per annotation.
@RestController
@RestController is an annotation: a label that starts with @ and gives Spring information about your class. This one says "this class answers web requests, and whatever its methods return should be sent back to the caller". You never call this class yourself; Spring creates it and calls it for you.
public class PizzaFinder {
A controller is a perfectly ordinary Java class; the annotations are what make it special.
private final List<String> menu = List.of("Margherita", "Pepperoni");
Two pizzas on the menu, at positions 0 and 1.
@GetMapping("/pizzas/{id}")
A path variable again: /pizzas/0 asks for the pizza at position 0.
public ResponseEntity<String> pizza(@PathVariable int id) {
Returning ResponseEntity<String> instead of plain String lets the method choose its status code. Every web response carries one: 200 means OK, 404 means Not Found (you have met it on broken links!), and 500 means something went wrong inside the server while it was answering.
if (id >= 0 && id < menu.size()) {
Only positions 0 and 1 exist, and sneaky negative numbers are out too. A good server checks before it grabs. (menu.size() asks the list how many items it holds, just like .length on an array.)
return ResponseEntity.ok(menu.get(id));
menu.get(id) fetches the item at that position, and ResponseEntity.ok(...) wraps it in a response with status 200 OK: "here you go".
}
This } closes the if block.
return ResponseEntity.notFound().build();
No pizza there, so the honest answer is status 404 Not Found with no body. Sending clear status codes instead of pretending everything is fine is what makes a server pleasant to build on: the caller can tell exactly what happened. Your server now speaks proper HTTP!
}
This } closes the method.
}
This } closes the class.