DEV Community

Cover image for Http Client API in Java: Managing Files
Noe Lopez
Noe Lopez

Posted on • Edited on

Http Client API in Java: Managing Files

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()));
    }
Enter fullscreen mode Exit fullscreen mode

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:

  1. ofByteArray: the body is written to byte array.
  2. ofFile: the body is written to the file, and HttpResponse.body() returns a reference to its Path.
  3. ofFileDownload: the body is completely written to the file and HttpResponse.body() returns a Path object for the file.
  4. ofString: The body is writen to string and decoded using the character set specified in the Content-Type response header.
  5. ofInputStream: returns an InputStream from which the body can be read as it is received.
  6. ofLines: returns an Stream. The body may have not been received yet.
  7. 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);
Enter fullscreen mode Exit fullscreen mode

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   
Enter fullscreen mode Exit fullscreen mode

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")));
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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

  1. ofString: returns a BodyPublisher whose body is the given String, converted using the UTF_8 character.
  2. ofInputStream: A body publisher that reads its data from an InputStream.
  3. ofByteArray: Returns a request body publisher whose body is the given byte array.
  4. ofFile: A request body publisher that takes data from the contents of a File.
  5. 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();
    } 
Enter fullscreen mode Exit fullscreen mode

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"));
Enter fullscreen mode Exit fullscreen mode

Output

Status 201 
Headers Optional[http://localhost:8080/api/v1/documents/3] 
Enter fullscreen mode Exit fullscreen mode

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
    }
}
Enter fullscreen mode Exit fullscreen mode

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"));
Enter fullscreen mode Exit fullscreen mode

Output

Status 201 
Headers Optional[http://localhost:8080/api/v1/documents/4] 
Enter fullscreen mode Exit fullscreen mode

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"
  }
Enter fullscreen mode Exit fullscreen mode

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:

  1. Introduction to the API plus most common use cases.

  2. 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)