Implementing File Storage and Handling

1. Configuring File Upload Properties

Property Default
spring.servlet.multipart.enabled true
spring.servlet.multipart.max-file-size 1MB
spring.servlet.multipart.max-request-size 10MB
spring.servlet.multipart.location System temp
spring.servlet.multipart.file-size-threshold 0 (always disk)

2. Handling Multipart File Uploads

Example: Reactive multipart file info

@PostMapping(value="/upload", consumes=MediaType.MULTIPART_FORM_DATA_VALUE)
public Map<String,Object> upload(@RequestPart("file") MultipartFile file) throws IOException {
  return Map.of("name", file.getOriginalFilename(),
                "size", file.getSize(),
                "type", file.getContentType());
}

3. Saving Files to Local Filesystem

Example: Save file with path traversal guard

@Service
public class FileStorageService {
  private final Path root = Path.of("/var/data/uploads");
  public Path save(MultipartFile file) throws IOException {
    Files.createDirectories(root);
    Path target = root.resolve(uniqueName(file.getOriginalFilename())).normalize();
    if (!target.startsWith(root)) throw new SecurityException("Path traversal");
    file.transferTo(target);
    return target;
  }
}
Warning: Always validate the resolved path stays within the storage root to prevent path traversal attacks.

4. Generating Unique Filenames (UUID)

Strategy Example
UUID + ext uuid + "." + ext
Timestamp + random System.currentTimeMillis() + "-" + random
Content hash SHA-256 → dedup

5. Serving Static Files

Location URL
classpath:/static/ /
classpath:/public/ /
classpath:/resources/ /
classpath:/META-INF/resources/ /

Example: Serve uploaded files as static resource

@Override
public void addResourceHandlers(ResourceHandlerRegistry r) {
  r.addResourceHandler("/files/**").addResourceLocations("file:/var/data/uploads/");
}

6. Implementing File Download Endpoints

Example: File download with Content-Disposition header

@GetMapping("/download/{id}")
public ResponseEntity<Resource> download(@PathVariable String id) throws IOException {
  Path file = storage.locate(id);
  Resource resource = new UrlResource(file.toUri());
  return ResponseEntity.ok()
    .contentType(MediaType.APPLICATION_OCTET_STREAM)
    .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + file.getFileName() + "\"")
    .contentLength(Files.size(file))
    .body(resource);
}

7. Streaming Large Files

Example: Stream large file to response

@GetMapping("/stream/{id}")
public ResponseEntity<StreamingResponseBody> stream(@PathVariable String id) {
  StreamingResponseBody body = out -> {
    try (InputStream in = storage.openStream(id)) { in.transferTo(out); }
  };
  return ResponseEntity.ok().contentType(MediaType.APPLICATION_OCTET_STREAM).body(body);
}

8. Integrating AWS S3 for Cloud Storage

Example: Upload to S3 and generate presigned URL

@Service
public class S3Storage {
  private final S3Client s3;
  public String upload(String key, MultipartFile file) throws IOException {
    s3.putObject(b -> b.bucket("app-uploads").key(key).contentType(file.getContentType()),
                 RequestBody.fromInputStream(file.getInputStream(), file.getSize()));
    return key;
  }
  public URL presignedGet(String key, Duration ttl) {
    try (S3Presigner p = S3Presigner.create()) {
      return p.presignGetObject(r -> r.signatureDuration(ttl)
        .getObjectRequest(g -> g.bucket("app-uploads").key(key))).url();
    }
  }
}

9. Using Spring Content for File Management

Module Purpose
spring-content-fs Filesystem store
spring-content-s3 AWS S3 store
spring-content-rest HTTP endpoints (PUT/GET/DELETE)
@ContentId/@ContentLength Entity associations

10. Validating File Types and Sizes

Example: Validate file type and size

private static final Set<String> ALLOWED = Set.of("image/png", "image/jpeg", "application/pdf");

private void validate(MultipartFile f) {
  if (f.isEmpty()) throw new BadRequestException("Empty file");
  if (f.getSize() > 10 * 1024 * 1024) throw new BadRequestException("> 10MB");
  String type = Optional.ofNullable(f.getContentType()).orElse("");
  if (!ALLOWED.contains(type)) throw new BadRequestException("Type not allowed");
  // Magic bytes check (Apache Tika) for true content type
}

11. Implementing File Compression

Example: Stream files as ZIP archive

@GetMapping("/zip")
public void zip(HttpServletResponse res) throws IOException {
  res.setContentType("application/zip");
  res.setHeader("Content-Disposition", "attachment; filename=files.zip");
  try (ZipOutputStream zos = new ZipOutputStream(res.getOutputStream())) {
    for (Path file : storage.list()) {
      zos.putNextEntry(new ZipEntry(file.getFileName().toString()));
      Files.copy(file, zos);
      zos.closeEntry();
    }
  }
}