93

I'd like to append key-value pair as a query parameter to an existing URL. While I could do this by checking for the existence of whether the URL has a query part or a fragment part and doing the append by jumping though a bunch of if-clauses but I was wondering if there was clean way if doing this through the Apache Commons libraries or something equivalent.

http://example.com would be http://example.com?name=John

http://example.com#fragment would be http://example.com?name=John#fragment

http://example.com?email=john.doe@email.com would be http://example.com?email=john.doe@email.com&name=John

http://example.com?email=john.doe@email.com#fragment would be http://example.com?email=john.doe@email.com&name=John#fragment

I've run this scenario many times before and I'd like to do this without breaking the URL in any way.

icza
  • 389,944
  • 63
  • 907
  • 827
Mridang Agarwalla
  • 43,201
  • 71
  • 221
  • 382
  • 1
    Related: [A good library to do URL Query String manipulation in Java](http://stackoverflow.com/q/218608) – Duncan Jones Oct 03 '14 at 11:13
  • @Mridang Agarwalla Does my answer solve your problem? Let me know if you require any more information. – Adam Oct 06 '14 at 12:11
  • Possible duplicate of [What is the idiomatic way to compose a URL or URI in Java?](http://stackoverflow.com/questions/883136/what-is-the-idiomatic-way-to-compose-a-url-or-uri-in-java) – Nick Grealy Apr 13 '17 at 04:55

9 Answers9

233

There are plenty of libraries that can help you with URI building (don't reinvent the wheel). Here are three to get you started:


Java EE 7

import javax.ws.rs.core.UriBuilder;
...
return UriBuilder.fromUri(url).queryParam(key, value).build();

org.apache.httpcomponents:httpclient:4.5.2

import org.apache.http.client.utils.URIBuilder;
...
return new URIBuilder(url).addParameter(key, value).build();

org.springframework:spring-web:4.2.5.RELEASE

import org.springframework.web.util.UriComponentsBuilder;
...
return UriComponentsBuilder.fromUriString(url).queryParam(key, value).build().toUri();

See also: GIST > URI Builder Tests

Nick Grealy
  • 24,216
  • 9
  • 104
  • 119
72

This can be done by using the java.net.URI class to construct a new instance using the parts from an existing one, this should ensure it conforms to URI syntax.

The query part will either be null or an existing string, so you can decide to append another parameter with & or start a new query.

public class StackOverflow26177749 {

    public static URI appendUri(String uri, String appendQuery) throws URISyntaxException {
        URI oldUri = new URI(uri);

        String newQuery = oldUri.getQuery();
        if (newQuery == null) {
            newQuery = appendQuery;
        } else {
            newQuery += "&" + appendQuery;  
        }

        return new URI(oldUri.getScheme(), oldUri.getAuthority(),
                oldUri.getPath(), newQuery, oldUri.getFragment());
    }

    public static void main(String[] args) throws Exception {
        System.out.println(appendUri("http://example.com", "name=John"));
        System.out.println(appendUri("http://example.com#fragment", "name=John"));
        System.out.println(appendUri("http://example.com?email=john.doe@email.com", "name=John"));
        System.out.println(appendUri("http://example.com?email=john.doe@email.com#fragment", "name=John"));
    }
}

Shorter alternative

public static URI appendUri(String uri, String appendQuery) throws URISyntaxException {
    URI oldUri = new URI(uri);
    return new URI(oldUri.getScheme(), oldUri.getAuthority(), oldUri.getPath(),
            oldUri.getQuery() == null ? appendQuery : oldUri.getQuery() + "&" + appendQuery, oldUri.getFragment());
}

Output

http://example.com?name=John
http://example.com?name=John#fragment
http://example.com?email=john.doe@email.com&name=John
http://example.com?email=john.doe@email.com&name=John#fragment
Adam
  • 35,919
  • 9
  • 100
  • 137
  • @Charlie Correct a ternary would reduce line count, but may also reduce readability – Adam Jun 02 '20 at 18:57
  • It eliminates some variables too which means less complexity. – Charlie Jun 02 '20 at 19:39
  • 1
    @Charlie I've added alternative to my answer with ternary. – Adam Jun 02 '20 at 19:52
  • Even simpler, don't bother pulling out the bits, you really just need to know if you should use a ? or a &, which the ternary would do: return uri + new URI(uri).getQuery() == null ? "?" : "&" + appendQuery; – Charlie Jun 02 '20 at 23:08
  • 2
    Here it is assumed that `appendQuery` is escaped properly and not called like: `uri = appendUri(uri, "foo=" + bar)`. I'd strongly prefer an API with distinct `name` and `value` parameters and then have the escaping done *inside* `appendQuery` – Peter V. Mørch Aug 18 '20 at 23:17
  • Downvote because it is insecure. String concatenation is vulnerable to parameter injection as the added value is not encoded. – Robert Jun 09 '21 at 12:50
7

For android, Use: https://developer.android.com/reference/android/net/Uri#buildUpon()

URI oldUri = new URI(uri);
Uri.Builder builder = oldUri.buildUpon();
 builder.appendQueryParameter("newParameter", "dummyvalue");
 Uri newUri =  builder.build();
satyendra
  • 79
  • 2
  • 1
6

Use the URI class.

Create a new URI with your existing String to "break it up" to parts, and instantiate another one to assemble the modified url:

URI u = new URI("http://example.com?email=john@email.com&name=John#fragment");

// Modify the query: append your new parameter
StringBuilder sb = new StringBuilder(u.getQuery() == null ? "" : u.getQuery());
if (sb.length() > 0)
    sb.append('&');
sb.append(URLEncoder.encode("paramName", "UTF-8"));
sb.append('=');
sb.append(URLEncoder.encode("paramValue", "UTF-8"));

// Build the new url with the modified query:
URI u2 = new URI(u.getScheme(), u.getAuthority(), u.getPath(),
    sb.toString(), u.getFragment());
icza
  • 389,944
  • 63
  • 907
  • 827
3

I suggest an improvement of the Adam's answer accepting HashMap as parameter

/**
 * Append parameters to given url
 * @param url
 * @param parameters
 * @return new String url with given parameters
 * @throws URISyntaxException
 */
public static String appendToUrl(String url, HashMap<String, String> parameters) throws URISyntaxException
{
    URI uri = new URI(url);
    String query = uri.getQuery();

    StringBuilder builder = new StringBuilder();

    if (query != null)
        builder.append(query);

    for (Map.Entry<String, String> entry: parameters.entrySet())
    {
        String keyValueParam = entry.getKey() + "=" + entry.getValue();
        if (!builder.toString().isEmpty())
            builder.append("&");

        builder.append(keyValueParam);
    }

    URI newUri = new URI(uri.getScheme(), uri.getAuthority(), uri.getPath(), builder.toString(), uri.getFragment());
    return newUri.toString();
}
tryp
  • 1,120
  • 20
  • 26
2

For Android, use this function to append a new parameter to your existing URI.

Java

private Uri appendUriParameter(Uri uri, String key, String newValue) {
  final Set<String> params = uri.getQueryParameterNames();
  final Uri.Builder newUri = uri.buildUpon().clearQuery();
  for (String param : params) {
    newUri.appendQueryParameter(param, uri.getQueryParameter(param));
  }
  newUri.appendQueryParameter(key, newValue);

  return newUri.build();
}

Kotlin

private fun appendUriParameter(uri: Uri, key: String, newValue: String) {
    val params = uri.queryParameterNames()
    val newUri = uri.buildUpon().clearQuery()
    for (param in params) {
        newUri.appendQueryParameter(param, uri.queryParameter(param))
    }
    return newUri.appendQueryParameter(key, newValue)
}
Divyanshu Kumar
  • 1,272
  • 15
  • 15
0

Kotlin & clean, so you don't have to refactor before code review:

private fun addQueryParameters(url: String?): String? {
        val uri = URI(url)

        val queryParams = StringBuilder(uri.query.orEmpty())
        if (queryParams.isNotEmpty())
            queryParams.append('&')

        queryParams.append(URLEncoder.encode("$QUERY_PARAM=$param", Xml.Encoding.UTF_8.name))
        return URI(uri.scheme, uri.authority, uri.path, queryParams.toString(), uri.fragment).toString()
    }
Andrii Kovalchuk
  • 4,351
  • 2
  • 36
  • 31
0

Using lambda and StringJoiner, code can be more concise:

public static URI appendUri(String url, Map<String, String> parameters) throws URISyntaxException {
    final var uri = new URI(url);
    final var sj = new StringJoiner("&");

    parameters.forEach((name, value) -> sj.add(name + "=" + value));
    final var query = uri.getQuery() == null ? sj.toString() : sj.length() > 0 ? uri.getQuery() + "&" + sj : uri.getQuery();

    return new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(), uri.getPath(), query, uri.getFragment());
}
Enrico Bianchi
  • 1,901
  • 1
  • 10
  • 8
-1

An update to Adam's answer considering tryp's answer too. Don't have to instantiate a String in the loop.

public static URI appendUri(String uri, Map<String, String> parameters) throws URISyntaxException {
    URI oldUri = new URI(uri);
    StringBuilder queries = new StringBuilder();

    for(Map.Entry<String, String> query: parameters.entrySet()) {
        queries.append( "&" + query.getKey()+"="+query.getValue());
    }

    String newQuery = oldUri.getQuery();
    if (newQuery == null) {
        newQuery = queries.substring(1);
    } else {
        newQuery += queries.toString();
    }

    URI newUri = new URI(oldUri.getScheme(), oldUri.getAuthority(),
            oldUri.getPath(), newQuery, oldUri.getFragment());

    return newUri;
}