javaspring-bootmodel-view-controllermultipartfile

Can Spring Boot set the name of a file being downloaded from a controller endpoint?


Java 11 and Spring Boot 2.5.x here. I understand that I can set up a controller to return the contents of a file like so:

@GetMapping(
  value = "/get-image-with-media-type",
  produces = MediaType.IMAGE_JPEG_VALUE
)
public @ResponseBody byte[] getImageWithMediaType() throws IOException {
    InputStream in = getClass()
      .getResourceAsStream("/path/to/some/image.jpg");
    return IOUtils.toByteArray(in);
}

But what if I want to control the name of the file that is sent back? For instance, on the server-side the file name might be stored as "image.jpg" but say I want to have it returned as "<userId>-<YYYY-mm-DD>-image.jpg", where <userId> is the user ID of the authenticated user making the request, and where <YYYY-mm-DD> is the date the request is made at?

For instance, if user 123 made the request on 12/10/2021, the file would be downloaded as "123-2021-12-10-image.jpg" and if user 234 made the request on 1/17/2022 it would be downloaded as "234-2022-01-17-image.jpg". Is this possible to control on the Spring/Java/server-side, or is it up to the HTTP client (browser, PostMan, whatever) to decide on the file name?


Solution

  • Please try this, comments inline:

    package com.example;
    
    import java.io.IOException;
    import java.security.Principal;
    import java.util.Date;
    import org.apache.commons.io.IOUtils;
    import org.springframework.core.io.FileSystemResource;
    import org.springframework.http.HttpHeaders;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.MediaType;
    import org.springframework.http.ResponseEntity;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;
    
    
    @Controller
    public class SomeController {
    
      @GetMapping(
          value = "/get-image-with-media-type",
          produces = MediaType.IMAGE_JPEG_VALUE
      ) // we can inject user like this (can be null, when not secured):
      public ResponseEntity<byte[]> getImageWithMediaType(Principal user) throws IOException {
        // XXXResource is the "spring way", FileSystem- alternatively: ClassPath-, ServletContext-, ...
        FileSystemResource fsr = new FileSystemResource("/path/to/some/image.jpg");
        HttpHeaders responseHeaders = new HttpHeaders();
        responseHeaders.set(HttpHeaders.CONTENT_DISPOSITION,
            // for direct downlad "inline", for "save as" dialog "attachment" (browser dependent)
            // filename placeholders: %1$s: user id string, 2$tY: year 4 digits, 2$tm: month 2 digits, %2$td day of month 2 digits
            String.format("inline; filename=\"%1$s-%2$tY-%2$tm-%2$td-image.jpg\"",
                // user name, current (server) date:
                user == null ? "anonymous" : user.getName(), new Date()));
    
        // and fire:
        return new ResponseEntity<>(
            IOUtils.toByteArray(fsr.getInputStream()),
            responseHeaders,
            HttpStatus.OK
        );
      }
    }
    

    Relevant reference:

    With ContentDisposition it can look (just) like:

    responseHeaders.setContentDisposition(
      ContentDisposition
        .inline()// or .attachment()
        .filename(// format file name:
          String.format(
            "%1$s-%2$tY-%2$tm-%2$td-image.jpg",
            user == null ? "anonymous" : user.getName(),
            new Date()
          )
        )
        .build()
    );
    

    TYVM: How to set 'Content-Disposition' and 'Filename' when using FileSystemResource to force a file download file?