🎛️ B5 · Controller: @RestController, path/query param
B5 · Controller: @RestController: The controller layer is the API's **receptionist**: it receives the incoming HTTP request, parses "which path, which method, which parameters",
The controller layer is the API's **receptionist**: it receives the incoming HTTP request, parses "which path, which method, which parameters", and routes to the right service method — it does no work itself, it routes. It distinguishes two parameter kinds: a **path variable** (INSIDE the path, IDENTIFIES a resource — the `42` in `/api/v1/bugs/42`) and a **query param** (at the END with `?`, FILTERS/paginates the list — `?status=OPEN&page=2`). But if both are "parameters", why separate concepts? Because their intents differ: a path variable says "which record" (singular, required), a query param says "how do I filter" (optional, plural) — swapping one for the other breaks URL design and tests. In Java the equivalent is the `@PathVariable Long id` vs `@RequestParam(required=false) String status` distinction; Spring binds URL parts to method parameters via these annotations. In QA the controller is the gate of **boundary tests**: missing/wrong-typed path variable (`/bugs/abc`), undefined query param, pagination bounds (`page=-1`, `size=99999`) — these inputs are where most bugs are born and are tested at the controller level.
Controller: Path, Path Variable, Query Param
**🐞 Defect Birth — if pagination `size` is not capped** **Code:** `@RequestParam(defaultValue = "20") int size` — no upper bound; `size` flows straight to the service/DB. **What happens:** `GET /api/v1/bugs?size=1000000` tries to pull millions of records in one response. Server memory balloons, the response takes seconds or returns 500 with OutOfMemory. A single malicious request can slow the service (DoS). **Why sneaky:** in normal use (`size=20`) everything works perfectly and tests pass. The problem only appears with an out-of-bound value — since nobody thinks "what if someone sends size=1000000?", it stays hidden until production. **Where the tester catches it:** in a boundary test — sending `size=0`, `size=-1`, `size=999999` and expecting sane behavior (400 or clamping to a fixed max) while seeing the server struggle/return 500. The controller is the layer where such input bounds are tested.
🎬 Path or Query? How a URL Is Routed Correctly
/bugs/42?status=OPEN
A URL arrives: `/api/v1/bugs/42?status=OPEN`. It has two kinds of parameters — how does the controller separate them?
The `42` INSIDE the path is a path variable: it says "which record". The controller binds it to @PathVariable Long id — singular, required.
The `status=OPEN` after `?` is a query param: it says "how do I filter". The controller binds it to @RequestParam — optional, for filtering.
The controller binds both parts correctly and delegates to the service: "fetch bug 42 with the OPEN filter". It does no work, it routes.
The lesson — a path variable says "which record" (identity), a query param says "how do I filter" (option). The tester pushes bounds here: /bugs/abc, size=-1, undefined params.
Boundary Tests at the Controller Level
Wrong-typed path variable…
/api/v1/bugs/abc — id expects Long but text arrives. Spring should reject with 400; if not, a bug.
size=-1, size=0, size=999999 — sane behavior (400/clamp) or a crash? Bounds are always tested.