java 57 lines · 9 steps

Parsing and building URL query strings in Java

An immutable-ish wrapper that parses a URI's query into a multimap and rebuilds it, handling repeated keys and encoding.

Explained by highlit
1public final class QueryParams {
2 
3 private final Map<String, List<String>> params;
4 
5 private QueryParams(Map<String, List<String>> params) {
6 this.params = params;
7 }
8 
9 public static QueryParams parse(URI uri) {
10 String query = uri.getRawQuery();
11 Map<String, List<String>> parsed = new LinkedHashMap<>();
12 if (query == null || query.isEmpty()) {
13 return new QueryParams(parsed);
14 }
15 for (String pair : query.split("&")) {
16 int eq = pair.indexOf('=');
17 String key = eq >= 0 ? pair.substring(0, eq) : pair;
18 String value = eq >= 0 ? pair.substring(eq + 1) : "";
19 parsed.computeIfAbsent(decode(key), k -> new ArrayList<>()).add(decode(value));
20 }
21 return new QueryParams(parsed);
22 }
23 
24 public Optional<String> first(String key) {
25 List<String> values = params.get(key);
26 return values == null || values.isEmpty() ? Optional.empty() : Optional.of(values.get(0));
27 }
28 
29 public List<String> all(String key) {
30 return params.getOrDefault(key, List.of());
31 }
32 
33 public QueryParams with(String key, String value) {
34 params.computeIfAbsent(key, k -> new ArrayList<>()).add(value);
35 return this;
36 }
37 
38 public QueryParams without(String key) {
39 params.remove(key);
40 return this;
41 }
42 
43 public String toQueryString() {
44 StringJoiner joiner = new StringJoiner("&");
45 params.forEach((key, values) -> values.forEach(v ->
46 joiner.add(encode(key) + "=" + encode(v))));
47 return joiner.toString();
48 }
49 
50 private static String decode(String s) {
51 return URLDecoder.decode(s, StandardCharsets.UTF_8);
52 }
53 
54 private static String encode(String s) {
55 return URLEncoder.encode(s, StandardCharsets.UTF_8);
56 }
57}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Query keys can repeat, so a Map of List models them correctly where a plain Map would silently drop values.
  2. 2computeIfAbsent lets you lazily create the inner collection only when a key first appears.
  3. 3Always decode on parse and encode on output so special characters survive a round trip.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Parsing and building URL query strings in Java — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code