How do you bind multi-valued or dynamic query parameters — repeated params, a Map of all params, and MultiValueMap?
answer
- repeated ?tag=a&tag=b -> List/array, per-element convert
- ?id=1,2,3 -> split into collection too
- bare @RequestParam Map = all params, one value/key
- MultiValueMap = Map<K,List<V>>, keeps duplicates (getFirst/get)
- Map loses required/default/validation -> prefer @ModelAttribute DTO
basics
~10 sFor a repeated param (?tag=a&tag=b) bind List<String> or String[]. To grab every query param at once, use @RequestParam Map<String,String> (one value per name) or @RequestParam MultiValueMap<String,String> (preserves all values per name).
solid answer
~40 sA parameter that repeats — ?id=1&id=2&id=3 — binds to a collection: @RequestParam List<Long> id or Long[] id, with per-element type conversion. A single comma-delimited value ?id=1,2,3 can also bind to List/array via conversion. To accept arbitrary, unknown parameter names, declare @RequestParam without a name as a Map<String,String> (collapses to the first value per key) or, to preserve every value of repeated keys, MultiValueMap<String,String> (Spring's multimap where get() returns a List). The same Map/MultiValueMap trick works for @RequestHeader and @PathVariable. Caveat: when you bind a bare Map you lose required/defaultValue per-key and get no validation — you're accepting whatever the client sends, so validate explicitly. Prefer named params or a @ModelAttribute command object when the shape is known.
code
java · 9 lines@GetMapping("/items")
public List<Item> find(
@RequestParam(required = false) List<Long> id, // ?id=1&id=2 OR ?id=1,2
@RequestParam MultiValueMap<String,String> allParams // every query param, duplicates kept
) {
List<String> tags = allParams.get("tag"); // full list for repeated 'tag'
String q = allParams.getFirst("q"); // first value only
return service.query(id, tags, q);
}go deeper
Knows a repeated param can bind to a List or array.
Distinguishes List binding from Map bulk binding.
Explains MultiValueMap vs Map semantics and the loss of validation with bare Map, per-element conversion.
Weighs dynamic Map binding vs typed @ModelAttribute DTOs as an API-contract/validation decision.
Query strings and headers are inherently multi-valued: the same name can appear repeatedly, and clients may send parameters you didn't statically declare. Spring offers several binding shapes. **1. Repeated parameter -> collection or array.** For `GET /items?tag=red&tag=blue`: ```java @RequestParam List<String> tag // ["red", "blue"] @RequestParam String[] tag // same, as array @RequestParam List<Long> id // per-element conversion to Long ``` Each occurrence is converted individually to the element type via the ConversionService. If any element fails conversion you get a 400. **2. Comma-delimited single value -> collection.** For `?id=1,2,3` (one occurrence, comma-separated), Spring's conversion can split into a `List<Long>`/`Long[]` too, because there's a built-in String-to-collection converter that splits on commas. So both `?id=1&id=2` and `?id=1,2` can land in the same List<Long> parameter. (Be aware which format your clients actually send; they're different wire formats that happen to converge.) **3. All params at once — Map.** ```java @RequestParam Map<String,String> params // every query param, ONE value per name ``` Declaring a bare @RequestParam (no name) with a Map type captures the whole query string. A plain Map keeps only a single value per key (the first) — repeated names are collapsed. **4. All params preserving multiplicity — MultiValueMap.** ```java @RequestParam MultiValueMap<String,String> params ``` org.springframework.util.MultiValueMap is Spring's multimap: it's a Map<K, List<V>> where getFirst(key) returns the first value and get(key) returns the full List. Use this when repeated names matter (e.g. faceted filters). LinkedMultiValueMap is the common implementation. The **same Map / MultiValueMap bulk-binding** works for @RequestHeader (all headers) and @PathVariable (all URI template variables as Map<String,String>). **Type conversion recap.** Every one of these paths runs values through the WebConversionService (a FormattingConversionService): scalars, enums, and Formatter/Converter-registered types (LocalDate, custom value objects) all convert. Collection element types convert element-by-element. **Gotchas and trade-offs:** - **Bare Map/MultiValueMap disables per-param safety.** You lose `required`, `defaultValue`, and any per-parameter @Validated constraints — you accept an open bag of strings. Validate/whitelist keys yourself. This is why a typed **@ModelAttribute command object** (or a records-based DTO) is usually better when the parameter set is known: you get names, types, defaults, and Bean Validation. - **Empty list vs missing.** If a repeated param is absent and required=true (the default), you get a 400; use required=false to allow an empty/absent collection. - **Map is not @RequestBody.** @RequestParam Map reads the query string / form fields, not a JSON body. - **Ordering / duplicates**: plain Map silently drops duplicates — reach for MultiValueMap when that's data, not noise. - Note there's a separate MultiValueMap use with @RequestBody for form posts; here we're talking about @RequestParam binding of the query/form data. **When to use what:** named typed params for a small fixed set; List/array for a known repeated param; @ModelAttribute DTO for a known rich set (with validation); Map/MultiValueMap only for genuinely dynamic pass-through (e.g. a search proxy) where you accept the loss of type safety.
- What's the difference between binding to Map<String,String> vs MultiValueMap<String,String>?Map keeps a single value per parameter name (repeats collapse to one). MultiValueMap is a Map<K, List<V>>: get(key) returns every value for a repeated name and getFirst(key) returns the first. Use MultiValueMap when duplicate parameters carry meaning.
- Why might you prefer a @ModelAttribute DTO over @RequestParam Map for query params?A command object gives you named, typed fields with per-field type conversion, defaults, and Bean Validation (@Valid/@NotNull). A bare Map is an untyped string bag with no required/default/validation, so you'd have to validate manually and risk accepting unexpected input.
saying these in an interview costs you the question
- Thinking a plain Map<String,String> preserves multiple values for a repeated parameter.
- Believing @RequestParam Map reads a JSON body.
- Assuming repeated params can't be type-converted per element.