As we saw in the Setting a request body section, we can send a file (text or binary) to the server via BodyPublishers.ofFile() and a POST request.
But sending a classical upload request may involve a multipart form POST with Content-Type as multipart/form-data.
In this case, the request body is made of parts that are delimited by a boundary, as shown in the following illustration (--779d334bbfa... is the boundary):
However, JDK 11's HTTP Client API doesn't provide built-in support for building this kind of request body. Nevertheless, by following the preceding screenshot, we can define a custom BodyPublisher as follows:
public class MultipartBodyPublisher {
private static final String LINE_SEPARATOR = System.lineSeparator();
public static HttpRequest.BodyPublisher ofMultipart(
Map<Object, Object> data, String boundary) throws IOException {
final byte[] separator = ("--" + boundary +
LINE_SEPARATOR + "Content-Disposition: form-data;
name = ").getBytes(StandardCharsets.UTF_8);
final List<byte[] > body = new ArrayList<>();
for (Object dataKey: data.keySet()) {
body.add(separator);
Object dataValue = data.get(dataKey);
if (dataValue instanceof Path) {
Path path = (Path) dataValue;
String mimeType = fetchMimeType(path);
body.add((""" + dataKey + ""; filename="" +
path.getFileName() + """ + LINE_SEPARATOR +
"Content-Type: " + mimeType + LINE_SEPARATOR +
LINE_SEPARATOR).getBytes(StandardCharsets.UTF_8));
body.add(Files.readAllBytes(path));
body.add(LINE_SEPARATOR.getBytes(StandardCharsets.UTF_8));
} else {
body.add((""" + dataKey + """ + LINE_SEPARATOR +
LINE_SEPARATOR + dataValue + LINE_SEPARATOR)
.getBytes(StandardCharsets.UTF_8));
}
}
body.add(("--" + boundary
+ "--").getBytes(StandardCharsets.UTF_8));
return HttpRequest.BodyPublishers.ofByteArrays(body);
}
private static String fetchMimeType(
Path filenamePath) throws IOException {
String mimeType = Files.probeContentType(filenamePath);
if (mimeType == null) {
throw new IOException("Mime type could not be fetched");
}
return mimeType;
}
}
Now, we can create a multipart request, as follows (we try to upload a text file called LoremIpsum.txt to a server that simply sent back the raw form data):
Map<Object, Object> data = new LinkedHashMap<>();
data.put("author", "Lorem Ipsum Generator");
data.put("filefield", Path.of("LoremIpsum.txt"));
String boundary = UUID.randomUUID().toString().replaceAll("-", "");
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.header("Content-Type", "multipart/form-data;boundary=" + boundary)
.POST(MultipartBodyPublisher.ofMultipart(data, boundary))
.uri(URI.create("http://jkorpela.fi/cgi-bin/echoraw.cgi"))
.build();
HttpResponse<String> response = client.send(
request, HttpResponse.BodyHandlers.ofString());
The response should be similar to the following (the boundary is just a random UUID):
--7ea7a8311ada4804ab11d29bcdedcc55
Content-Disposition: form-data; name="author"
Lorem Ipsum Generator
--7ea7a8311ada4804ab11d29bcdedcc55
Content-Disposition: form-data; name="filefield"; filename="LoremIpsum.txt"
Content-Type: text/plain
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua.
--7ea7a8311ada4804ab11d29bcdedcc55--