skip to content

How do you constrain a path variable to a specific format, e.g. only digits or a fixed pattern?

level: middleimportance: should knowfreq 55%

answer

  1. {name:regex} colon syntax
  2. Regex is part of matching, not validation
  3. Fails regex = 404, wrong type = 400
  4. Disambiguate numeric id vs username
  5. PathPattern: regex within a segment, no regex in **

basics

~20 s

Add a regex after a colon inside the braces: {id:[0-9]+} matches only digits. Spring uses the regex to decide whether the URL matches that segment, so non-matching URLs fall through to other mappings or 404.

solid answer

~40 s

Spring supports regex-constrained URI template variables with the syntax {name:regex}. For example @GetMapping("/users/{id:[0-9]+}") only matches when the segment is all digits; @GetMapping("/files/{name:[a-z-]+}.{ext:[a-z]+}") splits a segment into two constrained variables. The regex participates in path matching itself, so a non-matching URL simply isn't routed to that handler — it can match a different, more general mapping or return 404. This is powerful for disambiguating overlapping routes, e.g. /users/{id:[0-9]+} vs /users/{username:[a-zA-Z]+}. Both PathPatternParser (the modern default in Spring 6 / Boot 3) and the legacy AntPathMatcher support {var:regex}, though PathPattern has some restrictions — a regex-captured variable must occupy a whole path segment (or a delimited piece of one), and you can't put a regex inside the ** multi-segment wildcard.

code

java · 16 lines
java
@RestController
@RequestMapping("/users")
public class UserLookupController {

    // Only matches all-digit ids
    @GetMapping("/{id:[0-9]+}")
    public User byId(@PathVariable Long id) { return svc.byId(id); }

    // Alphanumeric handle — coexists with the numeric route
    @GetMapping("/{username:[a-zA-Z][a-zA-Z0-9_]*}")
    public User byName(@PathVariable String username) { return svc.byName(username); }

    // Split one segment into two constrained variables
    @GetMapping("/{name:[a-z-]+}.{ext:json|xml}")
    public User export(@PathVariable String name, @PathVariable String ext) { ... }
}

go deeper

for a junior

May not know the colon-regex syntax at all; enough to recognize it exists.

for a middle

Should know {name:regex}, that it drives matching (404 not 400), and route-disambiguation use.

for a senior

Contrasts routing-regex vs bean-validation, notes ReDoS risk, and picks the right 404/400 semantics deliberately.

for a principal

Sets team conventions: minimal routing regex, validation in the domain layer, awareness of PathPattern constraints and performance.

## The {name:regex} syntax A URI template variable can carry an inline **regular expression constraint**: ```java @GetMapping("/users/{id:[0-9]+}") public User byId(@PathVariable Long id) { ... } ``` The form is `{` + variable name + `:` + regex + `}`. The regex must match the entire captured segment (it's implicitly anchored to the segment boundaries). Here `[0-9]+` means "one or more digits," so `/users/42` matches but `/users/bob` does not. ## Why it matters — matching, not validation The crucial insight is that the regex is part of **URL matching**, evaluated *before* your method runs. It is not post-hoc validation. Consequences: - A URL failing the regex is treated as *not matching this mapping at all*. It may match another handler, or produce a **404 Not Found** if nothing else matches. - This differs from `@PathVariable Long id` without a regex: there, `/users/bob` *matches* the route, then fails **type conversion**, giving a **400**. With `{id:[0-9]+}`, `/users/bob` never reaches the handler, giving a 404 (unless another route catches it). ## Disambiguating overlapping routes Regex constraints let two mappings on the same base path coexist: ```java @GetMapping("/users/{id:[0-9]+}") // numeric id public User byId(@PathVariable Long id) { ... } @GetMapping("/users/{username:[a-zA-Z][a-zA-Z0-9_]*}") // handle public User byName(@PathVariable String username) { ... } ``` `/users/42` → first; `/users/alice` → second. ## Multiple variables in one segment You can split a single path segment with literal separators: ```java @GetMapping("/files/{name:[a-z-]+}.{ext:[a-z]+}") public Resource file(@PathVariable String name, @PathVariable String ext) { ... } ``` `/files/report.pdf` → name=`report`, ext=`pdf`. ## PathPatternParser vs AntPathMatcher restrictions Spring has two matchers. **`PathPatternParser`** (the pre-parsed, high-performance default for Spring WebFlux and, since Spring Framework 5.3 / Boot 2.4+, the default for Spring MVC) supports `{var:regex}` but with constraints: - A captured variable (with or without regex) must correspond to a full path segment or a delimited part of one; it cannot straddle the `/` delimiter. - You cannot combine a regex with the `**` multi-segment wildcard, and `**` may only appear at the end of the pattern. The legacy **`AntPathMatcher`** is more permissive about placement but slower (it re-parses patterns on every request). Most `{var:regex}` uses are identical across both. ## Gotchas - **Keep regex simple**: complex patterns hurt readability and can enable ReDoS-style pathological backtracking. Prefer bean-validation (`@Pattern`) for rich business rules and reserve path regex for routing disambiguation. - **404 vs 400 surprise**: teams sometimes add a regex expecting a friendly 400 and instead get a 404. Decide which you want. - **Escaping in Java strings**: backslashes must be doubled, e.g. `{code:\\d{4}}`.

  • With {id:[0-9]+}, what status does /users/bob return, and why does it differ from a plain @PathVariable Long id?
    With the regex it returns 404 — the URL fails matching entirely. With a plain Long path variable, /users/bob matches the route then fails conversion, returning 400.
  • Can you put a regex inside a ** wildcard with PathPatternParser?
    No. With PathPattern, ** is a multi-segment wildcard allowed only at the end and cannot carry a regex. Regex constraints apply to single-segment {var} captures.

saying these in an interview costs you the question

  • Thinking {id:[0-9]+} validates after the method is called rather than during routing
  • Claiming a regex-failed URL returns 400 (it's 404 unless another route matches)
  • Believing regex path variables can span multiple / segments under PathPattern

context