17

I have a code that makes a POST request for a specific endpoint. This code is using Apache's HttpClient and I would like to start using the native HttpClient from Java (JDK11). But I didn't understand how to specify the parameters of my request.

This is my code using Apache Httpclient:

var path = Path.of("file.txt");
var entity = MultipartEntityBuilder.create()
            .addPart("file", new FileBody(path.toFile()))
            .addTextBody("token", "<any-token>")
            .build();

And the code using HttpClient:

var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder()
                         .uri(URI.create("https://myendpoint.com/"))
                         .POST( /* How can I set the parameters here? */ );

How can I set file and token parameters?

3
  • The Java 11 HttpClient doesn't appear to support file uploads. Also, it seems to require that you have all the POST arguments (which look identical to GET arguments) already set up somewhere instead of having a builder for them. Commented Jun 6, 2019 at 17:43
  • 1
    Having said that, someone else has already written a custom BodyPublisher to do fileuploads. Commented Jun 6, 2019 at 17:46
  • I am not very sure, but maybe try this out HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://myendpoint.com/")) .header("Content-Type", "multipart/form-data") .POST(HttpRequest.BodyPublishers.ofFile(Path.of("file.txt"))) .POST(HttpRequest.BodyPublishers.ofString("token")) .build(); Commented Jun 7, 2019 at 4:02

2 Answers 2

19

Unfortunately the Java 11 HTTP client does not provide any convenient support for multipart kind of body. But we can build custom implementation on top of it:

Map<Object, Object> data = new LinkedHashMap<>();
data.put("token", "some-token-value");
data.put("file", File.createTempFile("temp", "txt").toPath());

// add extra parameters if needed

// Random 256 length string is used as multipart boundary
String boundary = new BigInteger(256, new Random()).toString();

HttpRequest.newBuilder()
              .uri(URI.create("http://example.com"))
              .header("Content-Type", "multipart/form-data;boundary=" + boundary)
              .POST(ofMimeMultipartData(data, boundary))
              .build();

public HttpRequest.BodyPublisher ofMimeMultipartData(Map<Object, Object> data,
                                                     String boundary) throws IOException {
        // Result request body
        List<byte[]> byteArrays = new ArrayList<>();

        // Separator with boundary
        byte[] separator = ("--" + boundary + "\r\nContent-Disposition: form-data; name=").getBytes(StandardCharsets.UTF_8);

        // Iterating over data parts
        for (Map.Entry<Object, Object> entry : data.entrySet()) {

            // Opening boundary
            byteArrays.add(separator);

            // If value is type of Path (file) append content type with file name and file binaries, otherwise simply append key=value
            if (entry.getValue() instanceof Path) {
                var path = (Path) entry.getValue();
                String mimeType = Files.probeContentType(path);
                byteArrays.add(("\"" + entry.getKey() + "\"; filename=\"" + path.getFileName()
                        + "\"\r\nContent-Type: " + mimeType + "\r\n\r\n").getBytes(StandardCharsets.UTF_8));
                byteArrays.add(Files.readAllBytes(path));
                byteArrays.add("\r\n".getBytes(StandardCharsets.UTF_8));
            } else {
                byteArrays.add(("\"" + entry.getKey() + "\"\r\n\r\n" + entry.getValue() + "\r\n")
                        .getBytes(StandardCharsets.UTF_8));
            }
        }

        // Closing boundary
        byteArrays.add(("--" + boundary + "--").getBytes(StandardCharsets.UTF_8));

        // Serializing as byte array
        return HttpRequest.BodyPublishers.ofByteArrays(byteArrays);
    }

Here's working example on Github (you need to change VirusTotal API key)

Sign up to request clarification or add additional context in comments.

5 Comments

Just asking BodyPublishers.ofMimeMultipartData(data) exists in Java-11? I was unable to find it in Java-12 at least.
@Naman same here, I just found reference of ofMimeMultipartData here and here. Looks like a custom implementation, not part of JDK.
@Renan true, so the crux would be relying on BodyPublishers.ofByteArray(s) mostly.
Sorry guys, missed the part. Yes it's a custom implementation. The links provided by @Renan were correct. Updated the answer.
The boundary length per RFC 2046 is limited to 70 chars. That BigInteger method produces 77 chars, which is problematic in some backends. Using e.g. UUID.randomUUID().toString() alternative is safer.
0

The HttpClient doesn't provide any high level API to compose or format data in POST requests. You could either compose and format your post data manually, and then use either one of BodyPublishers.ofString(), BodyPublishers.ofInputStream(), or BodyPublishers.ofByteArrays() etc... to send it, or write your own implementation of BodyPublisher.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.