358

I've got the URI like this:

https://google.com.ua/oauth/authorize?client_id=SS&response_type=code&scope=N_FULL&access_type=offline&redirect_uri=http://localhost/Callback

I need a collection with parsed elements:

NAME               VALUE
------------------------
client_id          SS
response_type      code
scope              N_FULL
access_type        offline
redirect_uri       http://localhost/Callback

To be exact, I need a Java equivalent for the C#/.NET HttpUtility.ParseQueryString method.

spongebob
  • 8,370
  • 15
  • 50
  • 83
Sergey Shafiev
  • 4,205
  • 4
  • 26
  • 37
  • Please check this solution - solid library and working example for both Parse and Format operations: http://stackoverflow.com/a/37744000/1882064 – arcseldon Jun 10 '16 at 09:00

33 Answers33

419

If you are looking for a way to achieve it without using an external library, the following code will help you.

public static Map<String, String> splitQuery(URL url) throws UnsupportedEncodingException {
    Map<String, String> query_pairs = new LinkedHashMap<String, String>();
    String query = url.getQuery();
    String[] pairs = query.split("&");
    for (String pair : pairs) {
        int idx = pair.indexOf("=");
        query_pairs.put(URLDecoder.decode(pair.substring(0, idx), "UTF-8"), URLDecoder.decode(pair.substring(idx + 1), "UTF-8"));
    }
    return query_pairs;
}

You can access the returned Map using <map>.get("client_id"), with the URL given in your question this would return "SS".

UPDATE URL-Decoding added

UPDATE As this answer is still quite popular, I made an improved version of the method above, which handles multiple parameters with the same key and parameters with no value as well.

public static Map<String, List<String>> splitQuery(URL url) throws UnsupportedEncodingException {
  final Map<String, List<String>> query_pairs = new LinkedHashMap<String, List<String>>();
  final String[] pairs = url.getQuery().split("&");
  for (String pair : pairs) {
    final int idx = pair.indexOf("=");
    final String key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), "UTF-8") : pair;
    if (!query_pairs.containsKey(key)) {
      query_pairs.put(key, new LinkedList<String>());
    }
    final String value = idx > 0 && pair.length() > idx + 1 ? URLDecoder.decode(pair.substring(idx + 1), "UTF-8") : null;
    query_pairs.get(key).add(value);
  }
  return query_pairs;
}

UPDATE Java8 version

public Map<String, List<String>> splitQuery(URL url) {
    if (Strings.isNullOrEmpty(url.getQuery())) {
        return Collections.emptyMap();
    }
    return Arrays.stream(url.getQuery().split("&"))
            .map(this::splitQueryParameter)
            .collect(Collectors.groupingBy(SimpleImmutableEntry::getKey, LinkedHashMap::new, mapping(Map.Entry::getValue, toList())));
}

public SimpleImmutableEntry<String, String> splitQueryParameter(String it) {
    final int idx = it.indexOf("=");
    final String key = idx > 0 ? it.substring(0, idx) : it;
    final String value = idx > 0 && it.length() > idx + 1 ? it.substring(idx + 1) : null;
    return new SimpleImmutableEntry<>(
        URLDecoder.decode(key, StandardCharsets.UTF_8),
        URLDecoder.decode(value, StandardCharsets.UTF_8)
    );
}

Running the above method with the URL

https://stackoverflow.com?param1=value1&param2=&param3=value3&param3

returns this Map:

{param1=["value1"], param2=[null], param3=["value3", null]}
Dariusz
  • 21,561
  • 9
  • 74
  • 114
Pr0gr4mm3r
  • 6,170
  • 1
  • 18
  • 23
  • 28
    You are forgetting to decode the names and parameters, one reason why it's usually better to let libraries do common tasks. – Ruan Mendes Nov 27 '12 at 20:52
  • 1
    My answer points out a commonly used library (apache), if you are not already using it, it's probably not worth it. The answer looks good now and provides the map the OP would like – Ruan Mendes Nov 27 '12 at 21:08
  • yeah .. of course, if I already need to use the specific library for other tasks, I'll use the provided methods instead of wasting time writing my own method. – Pr0gr4mm3r Nov 27 '12 at 21:09
  • 2
    if you have multiple parameters with same name/key, using this function will override the value that has similar key. – fatarms Jul 08 '13 at 06:24
  • 1
    @snowball147 you could replace `query_pairs.put()` by a handling function or use a `Map>`. For the second case amend the for to contain e.g. `int idx = pair.indexOf("="); final String key = URLDecoder.decode(pair.substring(0, idx), "UTF-8"); final String value = URLDecoder.decode(pair.substring( idx + 1), "UTF-8"); if(!query_pairs.containsKey(key)) { query_pairs.put(key, new ArrayList()); } query_pairs.get(key).add(value);` – TheConstructor Jan 08 '14 at 22:55
  • 1
    This also doesn't handle the case where the key doesn't have a value – Kevin Day Feb 13 '14 at 21:30
  • I didn't know there could be multiple parameters with the same key. In this case, what does HttpServletRequest.getParameter() returns? – Sharon Ben Asher Mar 27 '14 at 08:38
  • 1
    @SharonBenAsher Read the docs `You should only use this method when you are sure the parameter has only one value. If the parameter might have more than one value, use getParameterValues(java.lang.String).` http://docs.oracle.com/javaee/6/api/javax/servlet/ServletRequest.html#getParameter(java.lang.String) – Ruan Mendes Mar 28 '14 at 20:44
  • This doesn't seem to work right with encoded &'s (&). For example it breaks on this url: http://a.com/q?1=a&b&2=b&c The output of that should be {1=a&b, 2=b&c} – Chris Seline May 15 '15 at 15:48
  • 5
    @Chris You're confusing xml/html escaping with URL encoding. Your example URL should be: a.com/q?1=a%26b&2=b%26c – sceaj May 19 '15 at 23:40
  • 1
    If you have "#" in your url - URL.getQuery() return null and this solution will not work – Danylo Volokh Sep 01 '15 at 16:07
  • It should be `it.length() >= idx + 1` so that "a=&b=&c=" is parsed to {"a",""},{"b",""} and {"c",""} – radiantRazor Jan 13 '17 at 11:43
  • 1
    @Pr0gr4mm3r I edited your answer to add the null checks also for the first two solutions. Note that for "http://www.example.com" `getQuery()` returns null, instead for "http://www.example.com?" it returns an empty String. – Massimiliano Kraus Apr 06 '17 at 09:21
  • 5
    it would be nice to indicate which functions are used: Collectors.mapping(...) and Collectors.toList(...) – Thomas Rebele Apr 08 '18 at 10:40
  • Hi, it would be great if someone could update this solution with the required imports for everything, as intellij, doest have a feature to go find the imports (at least doesnt work for me). E.g. these cant be resolved: MAP, URL, UnsupportedEncodingException, URLDecoder – John Little Jun 19 '19 at 15:05
  • Ready-to-use solution (with imports) that handles multi values, parameter decoding and error handling can be found below ( see https://stackoverflow.com/a/58017826/1211082 ) ( @John Little ) – Martin Senne Sep 19 '19 at 19:32
  • @Pr0gr4mm3r great example! :) Could I use the same code for parsing a normal commanline string what has different parameters and values and looks like: t=s vo="bin\\house_1.xml" u=30? – C-lara Apr 21 '20 at 10:13
  • Simply avoiding UnsupportedEncodingException: ... return new SimpleImmutableEntry<>(URLDecoder.decode(key, StandardCharsets.UTF_8), URLDecoder.decode(value, StandardCharsets.UTF_8)); ... – MGM Feb 18 '21 at 16:33
  • Wouldnt this fail on param1=par&myparam&param2=param2? unless you have a url encoded query string already... – Daniel Mar 08 '21 at 16:36
392

org.apache.http.client.utils.URLEncodedUtils

is a well known library that can do it for you

import org.apache.hc.client5.http.utils.URLEncodedUtils

String url = "http://www.example.com/something.html?one=1&two=2&three=3&three=3a";

List<NameValuePair> params = URLEncodedUtils.parse(new URI(url), Charset.forName("UTF-8"));

for (NameValuePair param : params) {
  System.out.println(param.getName() + " : " + param.getValue());
}

Outputs

one : 1
two : 2
three : 3
three : 3a
Dariusz
  • 21,561
  • 9
  • 74
  • 114
Ruan Mendes
  • 90,375
  • 31
  • 153
  • 217
  • 1
    Can I receive the value by it's name without passing all the elements? I mean something like this: System.out.print(params["one"]); – Sergey Shafiev Nov 27 '12 at 20:22
  • 3
    @SergeyShafiev It is trivial to convert a `List` into a `Map` Java doesn't have bracket access for hash maps, it would look like `map.get("one")` If you don't know how to do that, it should be another question (but try it on your own first). We prefer to keep questions slim here at SO – Ruan Mendes Nov 27 '12 at 20:24
  • 6
    Be careful that if you have two times the same parameter in your URL (i.e. ?a=1&a=2) URLEncodedUtils will throw an IllegalArgumentException – Crystark Apr 28 '14 at 12:45
  • 10
    @Crystark As of httpclient 4.3.3, query string with duplicated names does not throw any exceptions. It works as expected. `System.out.println(URLEncodedUtils.parse(new URI("http://example.com/?foo=bar&foo=baz"), "UTF-8"));` will print _[foo=bar, foo=baz]_. – Akihiro HARAI Jun 02 '14 at 06:24
  • 4
    As of Android 6, the Apache HTTP client library has been removed. This means `URLEncodedUtils and `NameValuePair` are no longer available (unless you add a dependency to the legacy Apache library as described [here](https://developer.android.com/about/versions/marshmallow/android-6.0-changes.html#behavior-apache-http-client)). – Ted Hopp Dec 10 '15 at 17:20
  • Please check this solution: http://stackoverflow.com/a/37744000/1882064 which shows details on how to use the referenced URLEncodedUtils lib. – arcseldon Jun 10 '16 at 08:58
  • Note that HttpClient will fail to parse a query component like this one `foo=bár`. – Jaime Hablutzel Dec 24 '16 at 23:03
160

If you are using Spring Framework:

public static void main(String[] args) {
    String uri = "http://my.test.com/test?param1=ab&param2=cd&param2=ef";
    MultiValueMap<String, String> parameters =
            UriComponentsBuilder.fromUriString(uri).build().getQueryParams();
    List<String> param1 = parameters.get("param1");
    List<String> param2 = parameters.get("param2");
    System.out.println("param1: " + param1.get(0));
    System.out.println("param2: " + param2.get(0) + "," + param2.get(1));
}

You will get:

param1: ab
param2: cd,ef
xxg
  • 2,048
  • 1
  • 11
  • 14
  • 4
    for URLs use `UriComponentsBuilder.fromHttpUrl(url)` – Ilya Serbis Feb 11 '20 at 22:31
  • 8
    Beware that `getQueryParams()` does **not** decode the query parameters. So for a URL of `http://foobar/path?param1=a%3Db` you get `param1: a%3Db` and not `param1: a=b`. You need to use `URLDecoder.decode()` yourself... - `getQueryParams()` is **BROKEN**. – Peter V. Mørch Jan 11 '21 at 08:00
59

use google Guava and do it in 2 lines:

import java.util.Map;
import com.google.common.base.Splitter;

public class Parser {
    public static void main(String... args) {
        String uri = "https://google.com.ua/oauth/authorize?client_id=SS&response_type=code&scope=N_FULL&access_type=offline&redirect_uri=http://localhost/Callback";
        String query = uri.split("\\?")[1];
        final Map<String, String> map = Splitter.on('&').trimResults().withKeyValueSeparator('=').split(query);
        System.out.println(map);
    }
}

which gives you

{client_id=SS, response_type=code, scope=N_FULL, access_type=offline, redirect_uri=http://localhost/Callback}
Vadzim
  • 24,954
  • 11
  • 143
  • 151
Amin Abbaspour
  • 1,111
  • 11
  • 10
  • 24
    What about the URL decoding described in the selected answer? – Clint Eastwood Jun 05 '14 at 20:38
  • Almost, there, right? That's going to give you the protocol+host+path on the first query parameter's name. And, a minor nit, withKeyValueSeparator() doesn't accept char. Needs to be String – Kirby Aug 15 '14 at 04:41
  • 8
    This is also suspect to multiple keys with the same name. According to the javadocs this will throw an IllegalArgumentException – jontro Dec 30 '14 at 14:42
  • Also I would suggest to use .withKeyValueSeparator('=') . String as parameter is unnecessary cost compared to character. – rkosegi Feb 26 '15 at 06:35
  • 5
    Instead of manually splitting `uri` you should use `new java.net.URL(uri).getQuery()` as this buys you free input validation on the URL. – avgvstvs Mar 06 '15 at 13:07
  • 1
    For decoding: final Map queryVars = Maps.transformValues(map, new Function() { @Override public String apply(String value) { try { return URLDecoder.decode(value, "UTF-8"); } catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block e.printStackTrace(); } return value; } }); – phreakhead Jul 22 '15 at 18:22
  • @rkosegi, char-parameterized Splitter constructor creates string from char internally, so the one which more effective is String-parameterized – Kirill Gamazkov Aug 19 '15 at 10:33
  • @KirillGamazkov : nobody should rely on internal implementation details. Passing a string literal will create instance of String (by compiler) where char is primitive type. – rkosegi Aug 19 '15 at 10:49
  • @rkosegi, it's incorrect to say that one method more effective than another without digging into implementations – Kirill Gamazkov Aug 21 '15 at 11:13
  • 13
    WARNING!! It's NOT safe to do this since `splitter.split()` will throw `IllegalArgumentException` if there're duplicate key in query string. See http://stackoverflow.com/questions/1746507/authoritative-position-of-duplicate-http-get-query-keys – Anderson Nov 27 '15 at 03:57
  • In my case, the URL is safely controlled, so this is a nice fast way. Thanks. – Philip Callender Feb 13 '16 at 13:21
  • `url.split("\\?")[1].split(";")[0]` because cut cookie –  Oct 01 '17 at 02:44
  • Thanks you i use guava split – Aung Aung May 15 '19 at 17:46
  • 1
    Beware that this solution would throw in case some value has extra '=' inside. Here is the workaround: https://stackoverflow.com/a/37402817/603516 – Vadzim Jun 14 '19 at 13:31
45

The shortest way I've found is this one:

MultiValueMap<String, String> queryParams =
            UriComponentsBuilder.fromUriString(url).build().getQueryParams();

UPDATE: UriComponentsBuilder comes from Spring. Here the link.

Dmitry Senkovich
  • 5,521
  • 8
  • 37
  • 74
  • 6
    Without knowing where this UriComponentsBuilder class come from it's not very useful. – Thomas Mortagne Nov 06 '17 at 10:50
  • 1
    **N.B.** This takes URIs. Java's version of URIs are not a superset of URLs (this is why toURI can throw exceptions). – Adam Gent Jun 28 '19 at 13:47
  • 1
    Beware that `getQueryParams()` does **not** decode the query parameters. So for a URL of `http://foobar/path?param1=a%3Db` you get `param1: a%3Db` and not `param1: a=b`. You need to use `URLDecoder.decode()` yourself... - `getQueryParams()` is **BROKEN**. – Peter V. Mørch Jan 11 '21 at 08:03
  • Also, if the URL comes from an HTML document, you must unescape it first: `org.springframework.web.util.HtmlUtils.htmlUnescape(myUrl)` – Michael Böckling Sep 14 '22 at 08:09
33

For Android, if you are using OkHttp in your project. You might get a look at this. It simple and helpful.

final HttpUrl url = HttpUrl.parse(query);
if (url != null) {
    final String target = url.queryParameter("target");
    final String id = url.queryParameter("id");
}
THANN Phearum
  • 1,969
  • 22
  • 19
  • HttpUrl is kind of a weird name but this is exactly what I needed. Thanks. – GuiSim Jun 21 '18 at 21:29
  • 1
    update: `HttpUrl.parse()` is deprecated as of OkHttp 4, but this is still possible with the new OkHttp extension function described here: https://stackoverflow.com/a/63118203/2888763 – Trevor Halvorson Oct 03 '21 at 00:57
24

PLAIN Java 11

Given the URL to analyse:

URL url = new URL("https://google.com.ua/oauth/authorize?client_id=SS&response_type=code&scope=N_FULL&access_type=offline&redirect_uri=http://localhost/Callback");

This solution collects a list of pairs:

List<Map.Entry<String, String>> list = Pattern.compile("&")
   .splitAsStream(url.getQuery())
   .map(s -> Arrays.copyOf(s.split("=", 2), 2))
   .map(o -> Map.entry(decode(o[0]), decode(o[1])))
   .collect(Collectors.toList());

This solution on the other hand collects a map (given that in a url there can be more parameters with same name but different values).

Map<String, List<String>> list = Pattern.compile("&")
   .splitAsStream(url.getQuery())
   .map(s -> Arrays.copyOf(s.split("=", 2), 2))
   .collect(groupingBy(s -> decode(s[0]), mapping(s -> decode(s[1]), toList())));

Both the solutions must use an utility function to properly decode the parameters.

private static String decode(final String encoded) {
    return Optional.ofNullable(encoded)
                   .map(e -> URLDecoder.decode(e, StandardCharsets.UTF_8))
                   .orElse(null);
}
freedev
  • 25,946
  • 8
  • 108
  • 125
  • 6
    This is more a Java 8 *approach* rather than a Java 8 oneliner. – Stephan Jun 16 '17 at 08:36
  • @Stephan well :) maybe both. But I'm more interested to understand if you like this solution. – freedev Jun 16 '17 at 08:39
  • 6
    IMO, a oneliner should be short and shouldn't span over multiple lines. – Stephan Jun 16 '17 at 08:54
  • IMO a oneliner is when it is a single statement. I wrote in this way for readability. – freedev Jun 16 '17 at 09:29
  • 2
    There are multiple statements involved here. – Stephan Jun 16 '17 at 09:47
  • I've double checked, this is a statement. Have a look at [Expressions, Statements, and Blocks](https://docs.oracle.com/javase/tutorial/java/nutsandbolts/expressions.html) – freedev Jun 16 '17 at 10:32
  • The solutions must use an utility function. So there are more than one statements involved. – Stephan Jun 16 '17 at 11:51
  • I guess I would call these statements: two oneliners that have a depenpency to an utility function :) – freedev Jun 16 '17 at 14:21
  • One liner is one line, this is a multiline statement – Ruan Mendes May 07 '18 at 16:31
  • 3
    I guess you could write a whole class on a single line, but that's not what is usually meant by the phrase "one-liner". – Abhijit Sarkar Aug 09 '18 at 18:26
  • 3
    One slight improvement if you happen to have Java 10 or higher - URLDecoder#decode (finally) has an overload that takes a Charset (e.g. StandardCharsets.UTF_8) instead of a string for the encoding, meaning you don't need to catch UnsupportedEncodingException. – chut Aug 25 '20 at 06:23
  • @AhmadShahwan as far as I remember, it was an ugly trick to be sure split() returns always an array of two elements (even if split() was unable to find the matching string =) – freedev Apr 06 '21 at 13:17
15

On Android, there is a Uri class in package android.net . Note that Uri is part of android.net, whereas URI is part of java.net .

Uri class has many functions to extract key-value pairs from a query. enter image description here

Following function returns key-value pairs in the form of HashMap.

In Java:

Map<String, String> getQueryKeyValueMap(Uri uri){
    HashMap<String, String> keyValueMap = new HashMap();
    String key;
    String value;

    Set<String> keyNamesList = uri.getQueryParameterNames();
    Iterator iterator = keyNamesList.iterator();

    while (iterator.hasNext()){
        key = (String) iterator.next();
        value = uri.getQueryParameter(key);
        keyValueMap.put(key, value);
    }
    return keyValueMap;
}

In Kotlin:

fun getQueryKeyValueMap(uri: Uri): HashMap<String, String> {
        val keyValueMap = HashMap<String, String>()
        var key: String
        var value: String

        val keyNamesList = uri.queryParameterNames
        val iterator = keyNamesList.iterator()

        while (iterator.hasNext()) {
            key = iterator.next() as String
            value = uri.getQueryParameter(key) as String
            keyValueMap.put(key, value)
        }
        return keyValueMap
    }
Ramakrishna Joshi
  • 1,442
  • 17
  • 22
13

If you are using servlet doGet try this

request.getParameterMap()

Returns a java.util.Map of the parameters of this request.

Returns: an immutable java.util.Map containing parameter names as keys and parameter values as map values. The keys in the parameter map are of type String. The values in the parameter map are of type String array.

(Java doc)

Roshana Pitigala
  • 8,437
  • 8
  • 49
  • 80
Ansar Ozden
  • 487
  • 6
  • 8
  • This works with Spring Web as well as in your controller you can have a parameter of type `HttpServletRequest` and it works with `MockHttpServletRequest` as well in Mock MVC unit tests. – GameSalutes May 23 '19 at 15:49
10

Netty also provides a nice query string parser called QueryStringDecoder. In one line of code, it can parse the URL in the question. I like because it doesn't require catching or throwing java.net.MalformedURLException.

In one line:

Map<String, List<String>> parameters = new QueryStringDecoder(url).parameters();

See javadocs here: https://netty.io/4.1/api/io/netty/handler/codec/http/QueryStringDecoder.html

Here is a short, self contained, correct example:

import io.netty.handler.codec.http.QueryStringDecoder;
import org.apache.commons.lang3.StringUtils;

import java.util.List;
import java.util.Map;

public class UrlParse {

  public static void main(String... args) {
    String url = "https://google.com.ua/oauth/authorize?client_id=SS&response_type=code&scope=N_FULL&access_type=offline&redirect_uri=http://localhost/Callback";
    QueryStringDecoder decoder = new QueryStringDecoder(url);
    Map<String, List<String>> parameters = decoder.parameters();
    print(parameters);
  }

  private static void print(final Map<String, List<String>> parameters) {
    System.out.println("NAME               VALUE");
    System.out.println("------------------------");
    parameters.forEach((key, values) ->
        values.forEach(val ->
            System.out.println(StringUtils.rightPad(key, 19) + val)));
  }
}

which generates

NAME               VALUE
------------------------
client_id          SS
response_type      code
scope              N_FULL
access_type        offline
redirect_uri       http://localhost/Callback
Kirby
  • 15,127
  • 10
  • 89
  • 104
8

If you're using Java 8 and you're willing to write a few reusable methods, you can do it in one line.

private Map<String, List<String>> parse(final String query) {
    return Arrays.asList(query.split("&")).stream().map(p -> p.split("=")).collect(Collectors.toMap(s -> decode(index(s, 0)), s -> Arrays.asList(decode(index(s, 1))), this::mergeLists));
}

private <T> List<T> mergeLists(final List<T> l1, final List<T> l2) {
    List<T> list = new ArrayList<>();
    list.addAll(l1);
    list.addAll(l2);
    return list;
}

private static <T> T index(final T[] array, final int index) {
    return index >= array.length ? null : array[index];
}

private static String decode(final String encoded) {
    try {
        return encoded == null ? null : URLDecoder.decode(encoded, "UTF-8");
    } catch(final UnsupportedEncodingException e) {
        throw new RuntimeException("Impossible: UTF-8 is a required encoding", e);
    }
}

But that's a pretty brutal line.

Fuwjax
  • 2,327
  • 20
  • 18
5

There a new version of Apache HTTP client - org.apache.httpcomponents.client5 - where URLEncodedUtils is now deprecated. URIBuilder should be used instead:

import org.apache.hc.core5.http.NameValuePair;
import org.apache.hc.core5.net.URIBuilder;

private static Map<String, String> getQueryParameters(final String url) throws URISyntaxException {
    return new URIBuilder(new URI(url), StandardCharsets.UTF_8).getQueryParams()
                                                               .stream()
                                                               .collect(Collectors.toMap(NameValuePair::getName,
                                                                                         nameValuePair -> URLDecoder.decode(nameValuePair.getValue(), StandardCharsets.UTF_8)));
}
Enigo
  • 3,685
  • 5
  • 29
  • 54
3

A ready-to-use solution for decoding of URI query part (incl. decoding and multi parameter values)

Comments

I wasn't happy with the code provided by @Pr0gr4mm3r in https://stackoverflow.com/a/13592567/1211082 . The Stream-based solution does not do URLDecoding, the mutable version clumpsy.

Thus I elaborated a solution that

  • Can decompose a URI query part into a Map<String, List<Optional<String>>>
  • Can handle multiple values for the same parameter name
  • Can represent parameters without a value properly (Optional.empty() instead of null)
  • Decodes parameter names and values correctly via URLdecode
  • Is based on Java 8 Streams
  • Is directly usable (see code including imports below)
  • Allows for proper error handling (here via turning a checked exception UnsupportedEncodingExceptioninto a runtime exception RuntimeUnsupportedEncodingException that allows interplay with stream. (Wrapping regular function into functions throwing checked exceptions is a pain. And Scala Try is not available in the Java language default.)

Java Code

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.*;
import static java.util.stream.Collectors.*;

public class URIParameterDecode {
    /**
     * Decode parameters in query part of a URI into a map from parameter name to its parameter values.
     * For parameters that occur multiple times each value is collected.
     * Proper decoding of the parameters is performed.
     * 
     * Example
     *   <pre>a=1&b=2&c=&a=4</pre>
     * is converted into
     *   <pre>{a=[Optional[1], Optional[4]], b=[Optional[2]], c=[Optional.empty]}</pre>
     * @param query the query part of an URI 
     * @return map of parameters names into a list of their values.
     *         
     */
    public static Map<String, List<Optional<String>>> splitQuery(String query) {
        if (query == null || query.isEmpty()) {
            return Collections.emptyMap();
        }

        return Arrays.stream(query.split("&"))
                    .map(p -> splitQueryParameter(p))
                    .collect(groupingBy(e -> e.get0(), // group by parameter name
                            mapping(e -> e.get1(), toList())));// keep parameter values and assemble into list
    }

    public static Pair<String, Optional<String>> splitQueryParameter(String parameter) {
        final String enc = "UTF-8";
        List<String> keyValue = Arrays.stream(parameter.split("="))
                .map(e -> {
                    try {
                        return URLDecoder.decode(e, enc);
                    } catch (UnsupportedEncodingException ex) {
                        throw new RuntimeUnsupportedEncodingException(ex);
                    }
                }).collect(toList());

        if (keyValue.size() == 2) {
            return new Pair(keyValue.get(0), Optional.of(keyValue.get(1)));
        } else {
            return new Pair(keyValue.get(0), Optional.empty());
        }
    }

    /** Runtime exception (instead of checked exception) to denote unsupported enconding */
    public static class RuntimeUnsupportedEncodingException extends RuntimeException {
        public RuntimeUnsupportedEncodingException(Throwable cause) {
            super(cause);
        }
    }

    /**
     * A simple pair of two elements
     * @param <U> first element
     * @param <V> second element
     */
    public static class Pair<U, V> {
        U a;
        V b;

        public Pair(U u, V v) {
            this.a = u;
            this.b = v;
        }

        public U get0() {
            return a;
        }

        public V get1() {
            return b;
        }
    }
}

Scala Code

... and for the sake of completeness I can not resist to provide the solution in Scala that dominates by brevity and beauty

import java.net.URLDecoder

object Decode {
  def main(args: Array[String]): Unit = {
    val input = "a=1&b=2&c=&a=4";
    println(separate(input))
  }

  def separate(input: String) : Map[String, List[Option[String]]] = {
    case class Parameter(key: String, value: Option[String])

    def separateParameter(parameter: String) : Parameter =
      parameter.split("=")
               .map(e => URLDecoder.decode(e, "UTF-8")) match {
      case Array(key, value) =>  Parameter(key, Some(value))
      case Array(key) => Parameter(key, None)
    }

    input.split("&").toList
      .map(p => separateParameter(p))
      .groupBy(p => p.key)
      .mapValues(vs => vs.map(p => p.value))
  }
}
Martin Senne
  • 5,939
  • 6
  • 30
  • 47
2

Using above mentioned comments and solutions, I am storing all the query parameters using Map<String, Object> where Objects either can be string or Set<String>. The solution is given below. It is recommended to use some kind of url validator to validate the url first and then call convertQueryStringToMap method.

private static final String DEFAULT_ENCODING_SCHEME = "UTF-8";

public static Map<String, Object> convertQueryStringToMap(String url) throws UnsupportedEncodingException, URISyntaxException {
    List<NameValuePair> params = URLEncodedUtils.parse(new URI(url), DEFAULT_ENCODING_SCHEME);
    Map<String, Object> queryStringMap = new HashMap<>();
    for(NameValuePair param : params){
        queryStringMap.put(param.getName(), handleMultiValuedQueryParam(queryStringMap, param.getName(), param.getValue()));
    }
    return queryStringMap;
}

private static Object handleMultiValuedQueryParam(Map responseMap, String key, String value) {
    if (!responseMap.containsKey(key)) {
        return value.contains(",") ? new HashSet<String>(Arrays.asList(value.split(","))) : value;
    } else {
        Set<String> queryValueSet = responseMap.get(key) instanceof Set ? (Set<String>) responseMap.get(key) : new HashSet<String>();
        if (value.contains(",")) {
            queryValueSet.addAll(Arrays.asList(value.split(",")));
        } else {
            queryValueSet.add(value);
        }
        return queryValueSet;
    }
}
SUMIT
  • 540
  • 1
  • 4
  • 19
2

I had a go at a Kotlin version seeing how this is the top result in Google.

@Throws(UnsupportedEncodingException::class)
fun splitQuery(url: URL): Map<String, List<String>> {

    val queryPairs = LinkedHashMap<String, ArrayList<String>>()

    url.query.split("&".toRegex())
            .dropLastWhile { it.isEmpty() }
            .map { it.split('=') }
            .map { it.getOrEmpty(0).decodeToUTF8() to it.getOrEmpty(1).decodeToUTF8() }
            .forEach { (key, value) ->

                if (!queryPairs.containsKey(key)) {
                    queryPairs[key] = arrayListOf(value)
                } else {

                    if(!queryPairs[key]!!.contains(value)) {
                        queryPairs[key]!!.add(value)
                    }
                }
            }

    return queryPairs
}

And the extension methods

fun List<String>.getOrEmpty(index: Int) : String {
    return getOrElse(index) {""}
}

fun String.decodeToUTF8(): String { 
    URLDecoder.decode(this, "UTF-8")
}
Graham Smith
  • 25,627
  • 10
  • 46
  • 69
2

Also, I would recommend regex based implementation of URLParser

import java.util.regex.Matcher;
import java.util.regex.Pattern;

class URLParser {
    private final String query;
    
    public URLParser(String query) {
        this.query = query;
    }
    
    public String get(String name) {
        String regex = "(?:^|\\?|&)" + name + "=(.*?)(?:&|$)";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(this.query);

        if (matcher.find()) {
            return matcher.group(1);
        }
        
        return "";
    }
}

This class is easy to use. It just needs the URL or the query string on initialization and parses value by given key.

class Main {
    public static void main(String[] args) {
        URLParser parser = new URLParser("https://www.google.com/search?q=java+parse+url+params&oq=java+parse+url+params&aqs=chrome..69i57j0i10.18908j0j7&sourceid=chrome&ie=UTF-8");
        System.out.println(parser.get("q"));  // java+parse+url+params
        System.out.println(parser.get("sourceid"));  // chrome
        System.out.println(parser.get("ie"));  // UTF-8
    }
}
1

Kotlin's Answer with initial reference from https://stackoverflow.com/a/51024552/3286489, but with improved version by tidying up codes and provides 2 versions of it, and use immutable collection operations

Use java.net.URI to extract the Query. Then use the below provided extension functions

  1. Assuming you only want the last value of query i.e. page2&page3 will get {page=3}, use the below extension function
    fun URI.getQueryMap(): Map<String, String> {
        if (query == null) return emptyMap()

        return query.split("&")
                .mapNotNull { element -> element.split("=")
                        .takeIf { it.size == 2 && it.none { it.isBlank() } } }
                .associateBy({ it[0].decodeUTF8() }, { it[1].decodeUTF8() })
    }

    private fun String.decodeUTF8() = URLDecoder.decode(this, "UTF-8") // decode page=%22ABC%22 to page="ABC"
  1. Assuming you want a list of all value for the query i.e. page2&page3 will get {page=[2, 3]}
    fun URI.getQueryMapList(): Map<String, List<String>> {
        if (query == null) return emptyMap()

        return query.split("&")
                .distinct()
                .mapNotNull { element -> element.split("=")
                        .takeIf { it.size == 2 && it.none { it.isBlank() } } }
                .groupBy({ it[0].decodeUTF8() }, { it[1].decodeUTF8() })
    }

    private fun String.decodeUTF8() = URLDecoder.decode(this, "UTF-8") // decode page=%22ABC%22 to page="ABC"

The way to use it as below

    val uri = URI("schema://host/path/?page=&page=2&page=2&page=3")
    println(uri.getQueryMapList()) // Result is {page=[2, 3]}
    println(uri.getQueryMap()) // Result is {page=3}
Elye
  • 53,639
  • 54
  • 212
  • 474
1

There are plenty of answers which work for your query as you've indicated when it has single parameter definitions. In some applications it may be useful to handle a few extra query parameter edge cases such as:

  • list of parameter values such as param1&param1=value&param1= meaning param1 is set to List.of("", "value", "")
  • invalid permutations such as querypath?&=&&=noparamname&.
  • use empty string not null in maps a= means "a" is List.of("") to match web servlet handling

This uses a Stream with filters and groupingBy to collect to Map<String, List<String>>:

public static Map<String, List<String>> getParameterValues(URL url) {
    return Arrays.stream(url.getQuery().split("&"))
            .map(s -> s.split("="))
            // filter out empty parameter names (as in Tomcat) "?&=&&=value&":
            .filter(arr -> arr.length > 0 && arr[0].length() > 0)
            .collect(Collectors.groupingBy(arr -> URLDecoder.decode(arr[0], StandardCharsets.UTF_8),
                     // drop this line for not-name definition order Map:
                     LinkedHashMap::new, 
                     Collectors.mapping(arr -> arr.length < 2 ? "" : URLDecoder.decode(arr[1], StandardCharsets.UTF_8), Collectors.toList())));
}
DuncG
  • 12,137
  • 2
  • 21
  • 33
0

If you are using Spring, add an argument of type @RequestParam Map<String,String> to your controller method, and Spring will construct the map for you!

mancini0
  • 4,285
  • 1
  • 29
  • 31
0

Just an update to the Java 8 version

public Map<String, List<String>> splitQuery(URL url) {
    if (Strings.isNullOrEmpty(url.getQuery())) {
        return Collections.emptyMap();
    }
    return Arrays.stream(url.getQuery().split("&"))
            .map(this::splitQueryParameter)
            .collect(Collectors.groupingBy(SimpleImmutableEntry::getKey, LinkedHashMap::new, **Collectors**.mapping(Map.Entry::getValue, **Collectors**.toList())));
}

mapping and toList() methods have to be used with Collectors which was not mentioned in the top answer. Otherwise it would throw compilation error in IDE

Aarish Ramesh
  • 6,745
  • 15
  • 60
  • 105
  • 2
    looks like you need to also share your `splitQueryParameters()` method? And what's with the `**Collectors**`? – Kirby May 15 '19 at 15:31
0

Answering here because this is a popular thread. This is a clean solution in Kotlin that uses the recommended UrlQuerySanitizer api. See the official documentation. I have added a string builder to concatenate and display the params.

    var myURL: String? = null

    if (intent.hasExtra("my_value")) {
        myURL = intent.extras.getString("my_value")
    } else {
        myURL = intent.dataString
    }

    val sanitizer = UrlQuerySanitizer(myURL)
    // We don't want to manually define every expected query *key*, so we set this to true
    sanitizer.allowUnregisteredParamaters = true
    val parameterNamesToValues: List<UrlQuerySanitizer.ParameterValuePair> = sanitizer.parameterList
    val parameterIterator: Iterator<UrlQuerySanitizer.ParameterValuePair> = parameterNamesToValues.iterator()

    // Helper simply so we can display all values on screen
    val stringBuilder = StringBuilder()

    while (parameterIterator.hasNext()) {
        val parameterValuePair: UrlQuerySanitizer.ParameterValuePair = parameterIterator.next()
        val parameterName: String = parameterValuePair.mParameter
        val parameterValue: String = parameterValuePair.mValue

        // Append string to display all key value pairs
        stringBuilder.append("Key: $parameterName\nValue: $parameterValue\n\n")
    }

    // Set a textView's text to display the string
    val paramListString = stringBuilder.toString()
    val textView: TextView = findViewById(R.id.activity_title) as TextView
    textView.text = "Paramlist is \n\n$paramListString"

    // to check if the url has specific keys
    if (sanitizer.hasParameter("type")) {
        val type = sanitizer.getValue("type")
        println("sanitizer has type param $type")
    }
jungledev
  • 4,195
  • 1
  • 37
  • 52
0

Here is my solution with reduce and Optional:

private Optional<SimpleImmutableEntry<String, String>> splitKeyValue(String text) {
    String[] v = text.split("=");
    if (v.length == 1 || v.length == 2) {
        String key = URLDecoder.decode(v[0], StandardCharsets.UTF_8);
        String value = v.length == 2 ? URLDecoder.decode(v[1], StandardCharsets.UTF_8) : null;
        return Optional.of(new SimpleImmutableEntry<String, String>(key, value));
    } else
        return Optional.empty();
}

private HashMap<String, String> parseQuery(URI uri) {
    HashMap<String, String> params = Arrays.stream(uri.getQuery()
            .split("&"))
            .map(this::splitKeyValue)
            .filter(Optional::isPresent)
            .map(Optional::get)
            .reduce(
                // initial value
                new HashMap<String, String>(), 
                // accumulator
                (map, kv) -> {
                     map.put(kv.getKey(), kv.getValue()); 
                     return map;
                }, 
                // combiner
                (a, b) -> {
                     a.putAll(b); 
                     return a;
                });
    return params;
}
  • I ignore duplicate parameters (I take the last one).
  • I use Optional<SimpleImmutableEntry<String, String>> to ignore garbage later
  • The reduction start with an empty map, then populate it on each SimpleImmutableEntry

In case you ask, reduce requires this weird combiner in the last parameter, which is only used in parallel streams. Its goal is to merge two intermediate results (here HashMap).

0

If you happen to have cxf-core on the classpath and you know you have no repeated query params, you may want to use UrlUtils.parseQueryString.

dschulten
  • 2,994
  • 1
  • 27
  • 44
0

The Eclipse Jersey REST framework supports this through UriComponent. Example:

import org.glassfish.jersey.uri.UriComponent;

String uri = "https://google.com.ua/oauth/authorize?client_id=SS&response_type=code&scope=N_FULL&access_type=offline&redirect_uri=http://localhost/Callback";
MultivaluedMap<String, String> params = UriComponent.decodeQuery(URI.create(uri), true);
for (String key : params.keySet()) {
  System.out.println(key + ": " + params.getFirst(key));
}
Jasper de Vries
  • 19,370
  • 6
  • 64
  • 102
0

If just want the parameters after the URL from a String. Then the following code will work. I am just assuming the simple Url. I mean no hard and fast checking and decoding. Like in one of my test case I got the Url and I know I just need the value of the paramaters. The url was simple. No encoding decoding needed.

String location = "https://google.com.ua/oauth/authorize?client_id=SS&response_type=code&scope=N_FULL&access_type=offline&redirect_uri=http://localhost/Callback";
String location1 = "https://stackoverflow.com?param1=value1&param2=value2&param3=value3";
String location2 = "https://stackoverflow.com?param1=value1&param2=&param3=value3&param3";
    
    Map<String, String> paramsMap = Stream.of(location)
        .filter(l -> l.indexOf("?") != -1)
        .map(l -> l.substring(l.indexOf("?") + 1, l.length()))
        .flatMap(q -> Pattern.compile("&").splitAsStream(q))
        .map(s -> s.split("="))
        .filter(a -> a.length == 2)
        .collect(Collectors.toMap(
            a -> a[0], 
            a -> a[1],
            (existing, replacement) -> existing + ", " + replacement,
            LinkedHashMap::new
        ));
    
    System.out.println(paramsMap);

Thanks

Basit
  • 8,426
  • 46
  • 116
  • 196
0

That seems tidy to me the best way:

static Map<String, String> decomposeQueryString(String query, Charset charset) {
    return Arrays.stream(query.split("&"))
        .map(pair -> pair.split("=", 2))
        .collect(Collectors.toMap(
            pair -> URLDecoder.decode(pair[0], charset),
            pair -> pair.length > 1 ? URLDecoder.decode(pair[1], charset) : null)
        );
}

The prerequisite is that your query syntax does not allow repeated parameters.

Matthias Ronge
  • 9,403
  • 7
  • 47
  • 63
0

The Hutool framework supports this through HttpUtil. Example:

import cn.hutool.http.HttpUtil;

    String url ="https://google.com.ua/oauth/authorize?client_id=SS&response_type=code&scope=N_FULL&access_type=offline&redirect_uri=http://localhost/Callback";
    Map<String, List<String>> stringListMap = HttpUtil.decodeParams(url, "UTF-8");
    System.out.println("decodeParams:" + stringListMap);

You will get:

decodeParams:{client_id=[SS], response_type=[code], scope=[N_FULL], access_type=[offline], redirect_uri=[http://localhost/Callback]}
aimilin
  • 11
  • 2
0

A kotlin version

of the answer Answer by matthias provided

fun decomposeQueryString(query: String, charset: Charset): Map<String, String?> {
   return if (query.split("?").size <= 1)
       emptyMap()
   else {
       query.split("?")[1]
            .split("&")
            .map { it.split(Pattern.compile("="), 2) }
            .associate {
                Pair(
                        URLDecoder.decode(it[0], charset.name()),
                        if (it.size > 1) URLDecoder.decode(it[1], charset.name()) else null
                )
            }
     }
}

This takes of the first parameter after the question mark '?' as well.

Udayaditya Barua
  • 1,151
  • 12
  • 26
0

Plain Java, No Special Libraries, Nothing Fancy

    // assumes you are parsing a line that looks like:
    // /path/resource?key=value&parameter=value
    // which you got from a request header line that looks like:
    // GET /path/resource?key=value&parameter=value HTTP/1.1

    public HashMap<String, String> parseQuery(String path){
        if(path == null || path.isEmpty()){         //basic sanity check
            return null;
        }
        int indexOfQ = path.indexOf("?");           //where the query string starts
        if(indexOfQ == -1){return  null;}           //check query exists
        String queryString = path.substring(indexOfQ + 1);
        String[] queryStringArray = queryString.split("&");
        Map<String, String> kvMap = new HashMap<>();
        for(String kvString : queryStringArray){
            int indexOfE = kvString.indexOf("=");      //check query is formed correctly
            if(indexOfE == -1 || indexOfE == 0){return  null;}
            String[] kvPairArray = kvString.split("=");
            kvMap.put(kvPairArray[0], kvPairArray[1]);
        }

        return kvMap;
    }
Adam Winter
  • 1,680
  • 1
  • 12
  • 26
0

In groovy

    static Map parseQueryParams(String queryString) {
        if(!queryString) return [:]
        queryString.split("&")
            .collect({ it.split("=") })
            .collectEntries { String[] arr ->
                String k = arr.length > 0 ? arr[0] : ""
                String v = arr.length > 1 ? arr[1] : ""
                return [decode(k), decode(v)]
            }
    }

    private static String decode(String s) {
        URLDecoder.decode(s, StandardCharsets.UTF_8.toString())
    }
Sudhir N
  • 4,008
  • 1
  • 22
  • 32
0

this is based on the solution above by Dariusz and Pr0gr4mm3r, but it didn't quite work because their version didn't handle decoding of names/parameters, it also wasn't properly handling situations where a key was defined but no value was defined or when a value was defined but no key defined. this version essentially tries to match the functionality of the original HttpUtils.parseQueryString() method as much as possible:

public Map<String, List<String>> splitQuery(URL url) {
    if (Strings.isNullOrEmpty(url.getQuery())) {
        return Collections.emptyMap();
    }
    try {
        String qs = url.getQuery();
        return splitQuery(qs);
    } catch (IllegalArgumentException e) {
        String qs = url.getQuery();
        String decoded = URLDecoder.decode(qs, StandardCharsets.UTF_8);
        return splitQuery(decoded);
    }
}

public Map<String, List<String>> splitQuery(String qs) {
    return Arrays.stream(qs.split("&"))
            .map(this::splitQueryParameter)
            .collect(Collectors.groupingBy(AbstractMap.SimpleImmutableEntry::getKey, LinkedHashMap::new, Collectors.mapping(Map.Entry::getValue, Collectors.toList())));
}

public AbstractMap.SimpleImmutableEntry<String, String> splitQueryParameter(String it) {
    final int idx = it.indexOf("=");
    if(idx == -1) {
        throw new IllegalArgumentException();
    }
    final String key = idx > 0 ? it.substring(0, idx) : "";
    final String value = (idx > 0 && it.length() > idx + 1) || (it.indexOf('=') == it.length()-1) || (it.indexOf('=') == 0)  ? it.substring(idx + 1) : it.substring(idx);
    return new AbstractMap.SimpleImmutableEntry<>(URLDecoder.decode(key, StandardCharsets.UTF_8), URLDecoder.decode(value, StandardCharsets.UTF_8));
}

JUnit:

@Test
public void testSplitQuery2() throws MalformedURLException {
    URL url = new URL("https://google.com.ua/oauth/authorize?client_id=SS&response_type=code&scope=N_FULL&access_type=offline&redirect_uri=http://localhost/Callback");
    Map<String, List<String>> listMap = splitQuery(url);
    for (String s : listMap.keySet()) {
        List<String> stringList = listMap.get(s);
        logger.info("s: " + s + ", stringList: " + stringList);
    }
}

Response:

testSplitQuery2 s: client_id, stringList: [SS]                           
testSplitQuery2 s: response_type, stringList: [code]                     
testSplitQuery2 s: scope, stringList: [N_FULL]                           
testSplitQuery2 s: access_type, stringList: [offline]                    
testSplitQuery2 s: redirect_uri, stringList: [http://localhost/Callback] 
user3892260
  • 965
  • 2
  • 10
  • 19
0

As part of my task, it was necessary to parse the encoded parameters into a Map<String, Object> so that single occurrences could be a string, and multiple ones a list.

Requirements:

  • Java 8 version
  • org.apache.http.client.utils.URLEncodedUtils

Code:

var params = "orderNumber=111&orderNumber=222&orderNumber=333&amount=100&returnUrl=https%3A%2F%2Ftest.local%2Fe9f50435&description=payment&jsonParams=%7B%22emails%22%3A%5B%22test%40test.local%22%5D%2C%22headerId%22%3A%5B%226669666%22%5D%2C%22id%22%3A%5B%22123123%22%5D%7D&clientId=clientId";

LinkedHashMap<String, Object> collect = URLEncodedUtils.parse(params, StandardCharsets.UTF_8)
    .stream()
    .collect(groupingBy(
        NameValuePair::getName,
        LinkedHashMap::new,
        collectingAndThen(toList(), list -> {
            if (1 == list.size()) {
                return list.get(0).getValue();
            }

            return list.stream()
                    .map(NameValuePair::getValue)
                    .collect(toList());
        })));

Returns this Map:

{
    orderNumber=[111, 222, 333],
    amount=100,
    returnUrl=https://test.local/e9f50435,
    description=payment,
    jsonParams={"emails":["test@test.local"],"headerId":["6669666"],"id":["123123"]},
    clientId=clientId
}
Alexey Bril
  • 479
  • 4
  • 14
-1

org.keycloak.common.util.UriUtils

I had to parse URIs and Query Parameters in a Keycloak extension and found this utility classes very useful:

org.keycloak.common.util.UriUtils:
static MultivaluedHashMap<String,String> decodeQueryString(String queryString) 

There is also a useful method to delete one query parameter:

static String   stripQueryParam(String url, String name)

And to parse the URL there is org.keycloak.common.util.KeycloakUriBuilder:

KeycloakUriBuilder  uri(String uriTemplate)
String  getQuery()

and lots of other goodies.

Frerk
  • 44
  • 5