[java] Parsing query strings on Android

Java EE has ServletRequest.getParameterValues().

On non-EE platforms, URL.getQuery() simply returns a string.

What's the normal way to properly parse the query string in a URL when not on Java EE?


It is popular in the answers to try and make your own parser. This is very interesting and exciting micro-coding project, but I cannot say that it is a good idea.

The code snippets below are generally flawed or broken. Breaking them is an interesting exercise for the reader. And to the hackers attacking the websites that use them.

Parsing query strings is a well defined problem but reading the spec and understanding the nuances is non-trivial. It is far better to let some platform library coder do the hard work, and do the fixing, for you!

This question is related to java android parsing url

The answer is


using Guava:

Multimap<String,String> parseQueryString(String queryString, String encoding) {
    LinkedListMultimap<String, String> result = LinkedListMultimap.create();

    for(String entry : Splitter.on("&").omitEmptyStrings().split(queryString)) {
        String pair [] = entry.split("=", 2);
        try {
            result.put(URLDecoder.decode(pair[0], encoding), pair.length == 2 ? URLDecoder.decode(pair[1], encoding) : null);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    return result;
}

On Android, you can use the Uri.parse static method of the android.net.Uri class to do the heavy lifting. If you're doing anything with URIs and Intents you'll want to use it anyways.


This works for me.. I'm not sure why every one was after a Map, List> All I needed was a simple name value Map.

To keep things simple I used the build in URI.getQuery();

public static Map<String, String> getUrlParameters(URI uri)
    throws UnsupportedEncodingException {
    Map<String, String> params = new HashMap<String, String>();
    for (String param : uri.getQuery().split("&")) {
        String pair[] = param.split("=");
        String key = URLDecoder.decode(pair[0], "UTF-8");
        String value = "";
        if (pair.length > 1) {
            value = URLDecoder.decode(pair[1], "UTF-8");
        }
        params.put(new String(key), new String(value));
    }
    return params;
}

You say "Java" but "not Java EE". Do you mean you are using JSP and/or servlets but not a full Java EE stack? If that's the case, then you should still have request.getParameter() available to you.

If you mean you are writing Java but you are not writing JSPs nor servlets, or that you're just using Java as your reference point but you're on some other platform that doesn't have built-in parameter parsing ... Wow, that just sounds like an unlikely question, but if so, the principle would be:

xparm=0
word=""
loop
  get next char
  if no char
    exit loop
  if char=='='
    param_name[xparm]=word
    word=""
  else if char=='&'
    param_value[xparm]=word
    word=""
    xparm=xparm+1
  else if char=='%'
    read next two chars
    word=word+interpret the chars as hex digits to make a byte
  else
    word=word+char

(I could write Java code but that would be pointless, because if you have Java available, you can just use request.getParameters.)



public static Map <String, String> parseQueryString (final URL url)
        throws UnsupportedEncodingException
{
    final Map <String, String> qps = new TreeMap <String, String> ();
    final StringTokenizer pairs = new StringTokenizer (url.getQuery (), "&");
    while (pairs.hasMoreTokens ())
    {
        final String pair = pairs.nextToken ();
        final StringTokenizer parts = new StringTokenizer (pair, "=");
        final String name = URLDecoder.decode (parts.nextToken (), "ISO-8859-1");
        final String value = URLDecoder.decode (parts.nextToken (), "ISO-8859-1");
        qps.put (name, value);
    }
    return qps;
}

On Android, I tried using @diyism answer but I encountered the space character issue raised by @rpetrich, for example: I fill out a form where username = "us+us" and password = "pw pw" causing a URL string to look like:

http://somewhere?username=us%2Bus&password=pw+pw

However, @diyism code returns "us+us" and "pw+pw", i.e. it doesn't detect the space character. If the URL was rewritten with %20 the space character gets identified:

http://somewhere?username=us%2Bus&password=pw%20pw

This leads to the following fix:

Uri uri = Uri.parse(url_string.replace("+", "%20"));
uri.getQueryParameter("para1");

this method takes the uri and return map of par name and par value

  public static Map<String, String> getQueryMap(String uri) {

    String queryParms[] = uri.split("\\?");

    Map<String, String> map = new HashMap<>();// 

    if (queryParms == null || queryParms.length == 0) return map;

    String[] params = queryParms[1].split("&");
    for (String param : params) {
        String name = param.split("=")[0];
        String value = param.split("=")[1];
        map.put(name, value);
    }
    return map;
}

For a servlet or a JSP page you can get querystring key/value pairs by using request.getParameter("paramname")

String name = request.getParameter("name");

There are other ways of doing it but that's the way I do it in all the servlets and jsp pages that I create.


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 the url is sent from a different activity where you set it to a value
    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")
    }

if (queryString != null)
{
    final String[] arrParameters = queryString.split("&");
    for (final String tempParameterString : arrParameters)
    {
        final String[] arrTempParameter = tempParameterString.split("=");
        if (arrTempParameter.length >= 2)
        {
            final String parameterKey = arrTempParameter[0];
            final String parameterValue = arrTempParameter[1];
            //do something with the parameters
        }
    }
}


On Android its simple as the code below:

UrlQuerySanitizer sanitzer = new UrlQuerySanitizer(url);
String value = sanitzer.getValue("your_get_parameter");

Also if you don't want to register each expected query key use:

sanitzer.setAllowUnregisteredParamaters(true)

Before calling:

sanitzer.parseUrl(yourUrl)

Parsing the query string is a bit more complicated than it seems, depending on how forgiving you want to be.

First, the query string is ascii bytes. You read in these bytes one at a time and convert them to characters. If the character is ? or & then it signals the start of a parameter name. If the character is = then it signals the start of a paramter value. If the character is % then it signals the start of an encoded byte. Here is where it gets tricky.

When you read in a % char you have to read the next two bytes and interpret them as hex digits. That means the next two bytes will be 0-9, a-f or A-F. Glue these two hex digits together to get your byte value. But remember, bytes are not characters. You have to know what encoding was used to encode the characters. The character é does not encode the same in UTF-8 as it does in ISO-8859-1. In general it's impossible to know what encoding was used for a given character set. I always use UTF-8 because my web site is configured to always serve everything using UTF-8 but in practice you can't be certain. Some user-agents will tell you the character encoding in the request; you can try to read that if you have a full HTTP request. If you just have a url in isolation, good luck.

Anyway, assuming you are using UTF-8 or some other multi-byte character encoding, now that you've decoded one encoded byte you have to set it aside until you capture the next byte. You need all the encoded bytes that are together because you can't url-decode properly one byte at a time. Set aside all the bytes that are together then decode them all at once to reconstruct your character.

Plus it gets more fun if you want to be lenient and account for user-agents that mangle urls. For example, some webmail clients double-encode things. Or double up the ?&= chars (for example: http://yoursite.com/blah??p1==v1&&p2==v2). If you want to try to gracefully deal with this, you will need to add more logic to your parser.


Origanally answered here

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

Uri class has many functions to extract query key-value pairs. 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
    }

If you have jetty (server or client) libs on your classpath you can use the jetty util classes (see javadoc), e.g.:

import org.eclipse.jetty.util.*;
URL url = new URL("www.example.com/index.php?foo=bar&bla=blub");
MultiMap<String> params = new MultiMap<String>();
UrlEncoded.decodeTo(url.getQuery(), params, "UTF-8");

assert params.getString("foo").equals("bar");
assert params.getString("bla").equals("blub");

Use Apache HttpComponents and wire it up with some collection code to access params by value: http://www.joelgerard.com/2012/09/14/parsing-query-strings-in-java-and-accessing-values-by-key/


Guava's Multimap is better suited for this. Here is a short clean version:

Multimap<String, String> getUrlParameters(String url) {
        try {
            Multimap<String, String> ret = ArrayListMultimap.create();
            for (NameValuePair param : URLEncodedUtils.parse(new URI(url), "UTF-8")) {
                ret.put(param.getName(), param.getValue());
            }
            return ret;
        } catch (URISyntaxException e) {
            throw new RuntimeException(e);
        }
    }

public static Map<String, List<String>> getUrlParameters(String url)
        throws UnsupportedEncodingException {
    Map<String, List<String>> params = new HashMap<String, List<String>>();
    String[] urlParts = url.split("\\?");
    if (urlParts.length > 1) {
        String query = urlParts[1];
        for (String param : query.split("&")) {
            String pair[] = param.split("=", 2);
            String key = URLDecoder.decode(pair[0], "UTF-8");
            String value = "";
            if (pair.length > 1) {
                value = URLDecoder.decode(pair[1], "UTF-8");
            }
            List<String> values = params.get(key);
            if (values == null) {
                values = new ArrayList<String>();
                params.put(key, values);
            }
            values.add(value);
        }
    }
    return params;
}

On Android:

import android.net.Uri;

[...]

Uri uri=Uri.parse(url_string);
uri.getQueryParameter("para1");

I don't think there is one in JRE. You can find similar functions in other packages like Apache HttpClient. If you don't use any other packages, you just have to write your own. It's not that hard. Here is what I use,

public class QueryString {

 private Map<String, List<String>> parameters;

 public QueryString(String qs) {
  parameters = new TreeMap<String, List<String>>();

  // Parse query string
     String pairs[] = qs.split("&");
     for (String pair : pairs) {
            String name;
            String value;
            int pos = pair.indexOf('=');
            // for "n=", the value is "", for "n", the value is null
         if (pos == -1) {
          name = pair;
          value = null;
         } else {
       try {
        name = URLDecoder.decode(pair.substring(0, pos), "UTF-8");
              value = URLDecoder.decode(pair.substring(pos+1, pair.length()), "UTF-8");            
       } catch (UnsupportedEncodingException e) {
        // Not really possible, throw unchecked
           throw new IllegalStateException("No UTF-8");
       }
         }
         List<String> list = parameters.get(name);
         if (list == null) {
          list = new ArrayList<String>();
          parameters.put(name, list);
         }
         list.add(value);
     }
 }

 public String getParameter(String name) {        
  List<String> values = parameters.get(name);
  if (values == null)
   return null;

  if (values.size() == 0)
   return "";

  return values.get(0);
 }

 public String[] getParameterValues(String name) {        
  List<String> values = parameters.get(name);
  if (values == null)
   return null;

  return (String[])values.toArray(new String[values.size()]);
 }

 public Enumeration<String> getParameterNames() {  
  return Collections.enumeration(parameters.keySet()); 
 }

 public Map<String, String[]> getParameterMap() {
  Map<String, String[]> map = new TreeMap<String, String[]>();
  for (Map.Entry<String, List<String>> entry : parameters.entrySet()) {
   List<String> list = entry.getValue();
   String[] values;
   if (list == null)
    values = null;
   else
    values = (String[]) list.toArray(new String[list.size()]);
   map.put(entry.getKey(), values);
  }
  return map;
 } 
}

I have methods to achieve this:

1):

public static String getQueryString(String url, String tag) {
    String[] params = url.split("&");
    Map<String, String> map = new HashMap<String, String>();
    for (String param : params) {
        String name = param.split("=")[0];
        String value = param.split("=")[1];
        map.put(name, value);
    }

    Set<String> keys = map.keySet();
    for (String key : keys) {
        if(key.equals(tag)){
         return map.get(key);
        }
        System.out.println("Name=" + key);
        System.out.println("Value=" + map.get(key));
    }
    return "";
}

2) and the easiest way to do this Using Uri class:

public static String getQueryString(String url, String tag) {
    try {
        Uri uri=Uri.parse(url);
        return uri.getQueryParameter(tag);
    }catch(Exception e){
        Log.e(TAG,"getQueryString() " + e.getMessage());
    }
    return "";
}

and this is an example of how to use either of two methods:

String url = "http://www.jorgesys.com/advertisements/publicidadmobile.htm?position=x46&site=reform&awidth=800&aheight=120";      
String tagValue = getQueryString(url,"awidth");

the value of tagValue is 800


If you're using Spring 3.1 or greater (yikes, was hoping that support went back further), you can use the UriComponents and UriComponentsBuilder:

UriComponents components = UriComponentsBuilder.fromUri(uri).build();
List<String> myParam = components.getQueryParams().get("myParam");

components.getQueryParams() returns a MultiValueMap<String, String>

Here's some more documentation.


Just for reference, this is what I've ended up with (based on URLEncodedUtils, and returning a Map).

Features:

  • it accepts the query string part of the url (you can use request.getQueryString())
  • an empty query string will produce an empty Map
  • a parameter without a value (?test) will be mapped to an empty List<String>

Code:

public static Map<String, List<String>> getParameterMapOfLists(String queryString) {
    Map<String, List<String>> mapOfLists = new HashMap<String, List<String>>();
    if (queryString == null || queryString.length() == 0) {
        return mapOfLists;
    }
    List<NameValuePair> list = URLEncodedUtils.parse(URI.create("http://localhost/?" + queryString), "UTF-8");
    for (NameValuePair pair : list) {
        List<String> values = mapOfLists.get(pair.getName());
        if (values == null) {
            values = new ArrayList<String>();
            mapOfLists.put(pair.getName(), values);
        }
        if (pair.getValue() != null) {
            values.add(pair.getValue());
        }
    }

    return mapOfLists;
}

A compatibility helper (values are stored in a String array just as in ServletRequest.getParameterMap()):

public static Map<String, String[]> getParameterMap(String queryString) {
    Map<String, List<String>> mapOfLists = getParameterMapOfLists(queryString);

    Map<String, String[]> mapOfArrays = new HashMap<String, String[]>();
    for (String key : mapOfLists.keySet()) {
        mapOfArrays.put(key, mapOfLists.get(key).toArray(new String[] {}));
    }

    return mapOfArrays;
}

Examples related to java

Under what circumstances can I call findViewById with an Options Menu / Action Bar item? How much should a function trust another function How to implement a simple scenario the OO way Two constructors How do I get some variable from another class in Java? this in equals method How to split a string in two and store it in a field How to do perspective fixing? String index out of range: 4 My eclipse won't open, i download the bundle pack it keeps saying error log

Examples related to android

Under what circumstances can I call findViewById with an Options Menu / Action Bar item? How to implement a simple scenario the OO way My eclipse won't open, i download the bundle pack it keeps saying error log getting " (1) no such column: _id10 " error java doesn't run if structure inside of onclick listener Cannot retrieve string(s) from preferences (settings) strange error in my Animation Drawable how to put image in a bundle and pass it to another activity FragmentActivity to Fragment A failure occurred while executing com.android.build.gradle.internal.tasks

Examples related to parsing

Got a NumberFormatException while trying to parse a text file for objects Uncaught SyntaxError: Unexpected end of JSON input at JSON.parse (<anonymous>) Python/Json:Expecting property name enclosed in double quotes Correctly Parsing JSON in Swift 3 How to get response as String using retrofit without using GSON or any other library in android UIButton action in table view cell "Expected BEGIN_OBJECT but was STRING at line 1 column 1" How to convert an XML file to nice pandas dataframe? How to extract multiple JSON objects from one file? How to sum digits of an integer in java?

Examples related to url

What is the difference between URL parameters and query strings? Allow Access-Control-Allow-Origin header using HTML5 fetch API File URL "Not allowed to load local resource" in the Internet Browser Slack URL to open a channel from browser Getting absolute URLs using ASP.NET Core How do I load an HTTP URL with App Transport Security enabled in iOS 9? Adding form action in html in laravel React-router urls don't work when refreshing or writing manually URL for public Amazon S3 bucket How can I append a query parameter to an existing URL?