34

I'm trying to send a POST request using the new http client api. Is there a built in way to send parameters formatted as x-www-form-urlencoded ?

My current code:

HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create(url))
        .header("Content-Type", "application/x-www-form-urlencoded")
        .POST(BodyPublishers.ofString("a=get_account&account=" + URLEncoder.encode(account, "UTF-8")))
        .build();

What I'm looking is for a better way to pass the parameters. Something like this:

Params p=new Params();
p.add("a","get_account");
p.add("account",account);

Do I need to build myself this functionality or is something already built in?

I'm using Java 12.

Doua Beri
  • 10,612
  • 18
  • 89
  • 138
  • 4
    Frustratingly, you need to do it yourself. There’s been [an open bug for it](https://bugs.java.com/bugdatabase/view_bug.do?bug_id=6306820) since Java 6. – VGR Jun 24 '19 at 01:09
  • 2
    as described here: https://golb.hplar.ch/2019/01/java-11-http-client.html#formdata-x-www-form-urlencoded you can build your own BodyPublisher which takes a Map interface as input – pero_hero Mar 04 '20 at 09:39

5 Answers5

35

I think the following is the best way to achieve this using Java 11:

Map<String, String> parameters = new HashMap<>();
parameters.put("a", "get_account");
parameters.put("account", account);

String form = parameters.entrySet()
    .stream()
    .map(e -> e.getKey() + "=" + URLEncoder.encode(e.getValue(), StandardCharsets.UTF_8))
    .collect(Collectors.joining("&"));

HttpClient client = HttpClient.newHttpClient();

HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create(url))
    .headers("Content-Type", "application/x-www-form-urlencoded")
    .POST(HttpRequest.BodyPublishers.ofString(form))
    .build();

HttpResponse<?> response = client.send(request, HttpResponse.BodyHandlers.ofString());

System.out.println(response.statusCode() + " " + response.body().toString());
blacktide
  • 10,654
  • 8
  • 33
  • 53
deloyar
  • 438
  • 6
  • 9
  • I'm getting a "The method ofString(String) from the type HttpRequest.BodyPublishers refers to the missing type HttpRequest$BodyPublisher" error. Couldn't find a solution over the internet. Do you know why this might happend? – blah Sep 28 '22 at 05:42
  • you need to encode the parameter's names `URLEncoder.encode(e.getKey(), StandardCharsets.UTF_8)` – Rafael Lima Feb 10 '23 at 15:00
4

As Łukasz Olszewski said , worked correctly :

String params = Map.of(
                    Constants.PARAM_CLIENT_ID, apiObject.getClientId(),
                    Constants.PARAM_SCOPE, apiObject.getScope(),
                    Constants.PARAM_CODE, apiObject.getCode(),
                    Constants.PARAM_REDIRECT_URI, apiObject.getRedirectUri(),
                    Constants.PARAM_GRANT_TYPE, apiObject.getGrantType(),
                    Constants.PARAM_CODE_VERIFIER, apiObject.getCodeVerifier())
                    .entrySet()
                    .stream()
                    .map(entry -> Stream.of(
                            URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8),
                            URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8))
                            .collect(Collectors.joining("="))
                    ).collect(Collectors.joining("&"));

HttpResponse<?> response = utils.consumeHttpPostFormUrlEncodedClientByRequestUrl(Constants.URL_BASE + Constants.URL_GET_TOKEN, params);

and consumeHttpPostFormUrlEncodedClientByRequestUrl

public HttpResponse<?> consumeHttpPostFormUrlEncodedClientByRequestUrl(String url, String map) throws IOException, InterruptedException {
        HttpClient httpClient = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder(URI.create(url))
                .header("Content-Type", String.valueOf(MediaType.APPLICATION_FORM_URLENCODED))
                .POST(HttpRequest.BodyPublishers.ofString(map))
                .build();
        return httpClient.send(request, HttpResponse.BodyHandlers.ofString());
    }
Eliopez
  • 41
  • 1
3

This way could be useful:

String param = Map.of("param1", "value1", "param2", "value2")
      .entrySet()
      .stream()
      .map(entry -> Stream.of(
               URLEncoder.encode(entry.getKey(), UTF_8),
               URLEncoder.encode(entry.getValue(), UTF_8))
                .collect(Collectors.joining("="))
      ).collect(Collectors.joining("&"));

You can use up to 10 pairs (param, value) by Map.of(...). It returns an unmodifiable map.

2

Instead of Stream.of you can use more compact String.join (according to Łukasz Olszewski answer):

String form = Map.of("param1", "value1", "param2", "value2")
  .entrySet()
  .stream()
  .map(entry -> String.join("=",
                        URLEncoder.encode(entry.getKey().toString(), StandardCharsets.UTF_8),
                        URLEncoder.encode(entry.getValue().toString(), StandardCharsets.UTF_8)))
                .collect(Collectors.joining("&"));
return HttpRequest.BodyPublishers.ofString(form);
  • Please provide additional details in your answer. As it's currently written, it's hard to understand your solution. – Community Sep 08 '21 at 08:43
1

Check out Methanol. It's got a nice FormBodyPublisher for x-www-form-urlencoded bodies.

var formBody = FormBodyPublisher.newBuilder()
      .query("a", "get_account")
      .query("account", account)
      .build();
var request = MutableRequest.POST("https://example.com", formBody);

// Methanol implements an HttpClient that does nice things to your request/response.
// Here, the Content-Type header will be added for you.
var client = Methanol.create();
var response = client.send(request, BodyHandlers.ofString());