Overview
The purpose of this article is to show how to use the Http Client API to upload/download file contents from a REST endpoint.
Let's recap the steps to make a call to a web resource as this process is the same to upload/download file data.
HttpClient object is created and configured further if needed (timeout, authenticator, http version, followRedirects...)
Http Request is created passing the URL. Optionally, other features can be set. For instance, http method, http headers, timeout and http version.
Then, the HttpResponse is not created directly, but rather returned as a result of sending an HttpRequest with the HttpClient. Once it is completed, the status code and body if any can be inspected from it.
Now, let's continue with the download file use case.
Downloading file contents
We will follow the steps described above to perform this operation. But first, let's look at the backend. The endpoint is sending back the contents of the files as a byte array
@RestController
@RequestMapping("api/v1/documents")
public class DocumentController {
// ommited code here
@GetMapping(value = "/{documentId}")
ResponseEntity<Resource> downloadDocument(@PathVariable Long
documentId) {
Optional<Document> document = documentRepo.findById(documentId);
if (document.isEmpty())
return ResponseEntity.notFound().build();
return ResponseEntity
.ok()
.contentType(MediaType.parseMediaType(
document.get().getType()))
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment;
filename=\"" + document.get().getName() + "\"")
.body(new ByteArrayResource(
document.get().getContents()));
}
The API provides the BodyHandlers class (HttpResponse inner static class) to manage common response types such as String or File. Below is a list of some useful methods available in this class:
- ofByteArray: the body is written to byte array.
- ofFile: the body is written to the file, and HttpResponse.body() returns a reference to its Path.
- ofFileDownload: the body is completely written to the file and HttpResponse.body() returns a Path object for the file.
- ofString: The body is writen to string and decoded using the character set specified in the Content-Type response header.
- ofInputStream: returns an InputStream from which the body can be read as it is received.
- ofLines: returns an Stream. The body may have not been received yet.
- ofPublisher: returns a Publisher from which the body response bytes can be obtained as they are received. The publisher can and must be subscribed to only once.
As the endpoint returns a byte array, the ofByteArray method seems to be a good match. The filename is extracted from the Content-Disposition header. Code snippet shown beneath:
// Byte Array
HttpResponse<byte[]> response = client
.send(request, HttpResponse.BodyHandlers.ofByteArray());
String headerContentDisposition =
(String)response.headers().firstValue("content-
disposition").get();
String fileName =
"C:/workspace/files/"+getFileName(headerContentDisposition);
// Save to file
Files.write(Paths.get(fileName),
response.body(), StandardOpenOption.CREATE_NEW);
When the program is executed the file is successfully dowloaded to the location specified.
Directory: C:\workspace\files
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 4/1/2023 7:28 PM 388374 LinuxCommands.jpg
It is possible to use a different BodyHandler to download the file as proved in the following lines
HttpResponse<Path> response = client.send(request,
HttpResponse.BodyHandlers.ofFile(
Path.of("C:/workspace/files/DownloadedFile.jpg")));
Another option is to use the ofInputStream method.
HttpResponse<InputStream> response = client
.send(request,
HttpResponse.BodyHandlers.ofInputStream());
String headerContentDisposition =
(String)response.headers().firstValue("content-
disposition").get();
String fileName = "C:/workspace/files/"+
getFileName(headerContentDisposition);
Files.copy(response.body(), Paths.get(fileName),
StandardCopyOption.REPLACE_EXISTING);
As you can see there are plenty of choices to get the job done :-).
Uploading file contents
In this user case, two different endpoints to upload a file have been setup. Hence, we will see different ways to send the body in action.
Request bodies are provided through a BodyPublisher supplied to one of the POST or PUT methods. The class BodyPublishers provides implementations of many common publishers. Here is a list of some of the methods available in BodyPublishers
- ofString: returns a BodyPublisher whose body is the given String, converted using the UTF_8 character.
- ofInputStream: A body publisher that reads its data from an InputStream.
- ofByteArray: Returns a request body publisher whose body is the given byte array.
- ofFile: A request body publisher that takes data from the contents of a File.
- ofByteArrays: A request body publisher that takes data from an Iterable of byte arrays.
Now we can move on to the first upload example. The rest endpoint is expecting the file contents as a String. The file type and name are coming in headers.
@PostMapping()
ResponseEntity<Void> uploadDocument(@RequestBody String data,
@RequestHeader("Content-Type") String type,
@RequestHeader("fileName") String fileName) {
Document document = new Document();
document.setName(fileName);
document.setCreationDate(LocalDate.now());
document.setType(type);
document.setContents(data.getBytes());
documentRepo.save(document);
return ResponseEntity.created(URI.create(
"http://localhost:8080/api/v1/documents/"+document.getId()
)).build();
}
As you can see the BodyPublishers.ofFile method takes a Path as a parameter. When the request is sent the body will contain the contents of the file.
Path file = Paths.get("C:/workspace/files/trees.jpg");
var request = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:8080/api/v1/documents"))
.header("Content-Type", "image/jpg")
.header("fileName", file.getFileName().toString())
.header("Authorization",
getBasicAuthenticationHeader("admin1234","password5678"))
.POST(HttpRequest.BodyPublishers.ofFile(file))
.build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
System.out.printf("Status %s \n", response.statusCode());
System.out.printf("Headers %s \n",
response.headers().firstValue("location"));
Output
Status 201
Headers Optional[http://localhost:8080/api/v1/documents/3]
File is saved to the database and URL to find it is return in the location header.
In the second example the controller takes the body as a byte array.
@RestController
@RequestMapping("api/v2/documents")
public class DocumentControllerV2 {
@PostMapping()
ResponseEntity<Void> uploadDocument(@RequestBody byte[] data
,@RequestHeader("Content-Type") String type
,@RequestHeader("fileName") String fileName) {
// omitted code
}
}
The method ofByteArray is the perfect match in this situation. All that is needed is to convert the contents of the file into a byte array. For that to happen the FileInputStream will do the work.
String fileName = "C:/workspace/files/java_tutorial.pdf";
var request = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:8080/api/v2/documents"))
.header("Content-Type", "image/png")
.header("filename",
fileName.substring(fileName.lastIndexOf("/")))
.POST(HttpRequest.BodyPublishers.ofByteArray(new
FileInputStream(fileName).readAllBytes()))
.build();
var response = client.send(request,
HttpResponse.BodyHandlers.ofString());
System.out.printf("Status %s \n", response.statusCode());
System.out.printf("Headers %s \n",
response.headers().firstValue("location"));
Output
Status 201
Headers Optional[http://localhost:8080/api/v1/documents/4]
Finnaly, to verify that the new files are stored in the database, the list files endpoint is called.
[
{
"name": "PlainText.txt",
"creationDate": "2023-03-15",
"size": 26,
"type": "text/plain"
},
{
"name": "LinuxCommands.jpg",
"creationDate": "2023-02-07",
"size": 388374,
"type": "image/jpeg"
},
{
"name": "trees.jpg",
"creationDate": "2023-04-02",
"size": 21011905,
"type": "image/jpg"
},
{
"name": "/java_tutorial.pdf",
"creationDate": "2023-04-02",
"size": 1012786,
"type": "image/png"
}
Summary
So that's it. Downloading and uploading files using Java's core API is quite simple. We have to pick a suitable body publisher or body handler to send the file contents and receive the file contents respectively.
This was the last article of this mini series in which we have looked at the Java Http Client API. Previous artices can be found in the below links:
Introduction to the API plus most common use cases.
Sending Security Credentials to Authenticate
Hope you have enjoyed the read! Nore Java related article to come in the future.
Check the code on GitHub
Top comments (0)