I ended up writing my own. It can be called like
URIUtils.withQuery(uri, "param1", "value1", "param2", "value2");
which isn't so bad.
/** * Concatenates <code>uri</code> with a query string generated from * <code>params</code>. * * @param uri the base URI * @param params a <code>Map</code> of key/value pairs * @return a new <code>URI</code> */ public static URI withQuery(URI uri, Map<String, String> params) { StringBuilder query = new StringBuilder(); char separator = '?'; for (Entry<String, String> param : params.entrySet()) { query.append(separator); separator = '&'; try { query.append(URLEncoder.encode(param.getKey(), "UTF-8")); if (!StringUtils.isEmpty(param.getValue())) { query.append('='); query.append(URLEncoder.encode(param.getValue(), "UTF-8")); } } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } return URI.create(uri.toString() + query.toString()); } /** * Concatenates <code>uri</code> with a query string generated from * <code>params</code>. The members of <code>params</code> will be * interpreted as {key1, val1, key2, val2}. Empty values can be given * as <code>""</code> or <code>null</code>. * * @param uri the base URI * @param params the key/value pairs in sequence * @return a new <code>URI</code> */ public static URI withQuery(URI uri, String... params) { Map<String, String> map = new LinkedHashMap<String, String>(); for (int i = 0; i < params.length; i += 2) { String key = params[i]; String val = i + 1 < params.length ? params[i + 1] : ""; map.put(key, val); } return withQuery(uri, map); }