Spring Boot provides a clean and powerful way to handle file uploads and downloads using MultipartFile and Resource abstraction.

This article walks through a complete implementation of:

  • File Upload
  • File Storage
  • Metadata Persistence
  • File Download with proper HTTP headers

1. Document Entity

We store file metadata in the database, not the file itself.

@Entity
public class Document {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String url;
}

What this represents:

  • name → original file name
  • url → physical storage path

2. File Upload Service

This service handles:

  • directory creation
  • file storage
  • database persistence
@Service
public class DocumentService {
    private final String uploadDir = "uploads/";
    private final DocumentRepository documentRepository;
    public DocumentService(DocumentRepository documentRepository) {
        this.documentRepository = documentRepository;
    }
    public Document uploadFile(MultipartFile file) {
        try {
            // Create directory if it doesn't exist
            File dir = new File(uploadDir);
            if (!dir.exists()) {
                dir.mkdirs();
            }
            // Generate unique filename to avoid collisions
            String fileName = System.currentTimeMillis() + "_" + file.getOriginalFilename();
            Path filePath = Paths.get(uploadDir + fileName);
            // Save file to disk
            Files.write(filePath, file.getBytes());
            // Save metadata to DB
            Document document = new Document();
            document.setName(file.getOriginalFilename());
            document.setUrl(filePath.toString());
            return documentRepository.save(document);
        } catch (Exception e) {
            throw new RuntimeException("Error occurred when uploading file", e);
        }
    }
}

3. Upload Controller

This endpoint handles multipart file upload requests.

@RestController
@RequestMapping("/api/documents")
public class DocumentController {
    private final DocumentService documentService;
    public DocumentController(DocumentService documentService) {
        this.documentService = documentService;
    }
    @PostMapping("/upload")
    public ResponseEntity<Document> upload(@RequestParam("file") MultipartFile file) {
        return ResponseEntity.ok(documentService.uploadFile(file));
    }
}

Key point:

  • @RequestParam("file") binds the uploaded file from the request
  • Content-Type must be multipart/form-data

4. File Download Service

This handles:

  • retrieving file metadata
  • reading file from disk
  • returning it as downloadable response
public ResponseEntity<UrlResource> downloadDocument(
        Integer empId, Integer documentId) throws IOException {
    EmployeeDocument employeeDocument =
        employeeDocumentRepository.findById(documentId)
            .orElseThrow(() -> new ResourceNotFoundException("Document not found"));
    Path filePath = Paths.get(employeeDocument.getUrl()).normalize();
    UrlResource urlResource = new UrlResource(filePath.toUri());
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_PDF);
    headers.setContentDisposition(
        ContentDisposition.attachment()
            .filename(employeeDocument.getName())
            .build()
    );
    return ResponseEntity.ok()
        .headers(headers)
        .body(urlResource);
}

5. What Happens During Download

Step-by-step flow:

  1. Fetch document metadata from DB
  2. Resolve physical file path
  3. Load file as UrlResource
  4. Set HTTP headers
  5. Return file as response

6. Important HTTP Concepts

Content-Type

Tells browser what file type is being returned:

  • application/pdf
  • image/png
  • application/octet-stream

Content-Disposition

Controls download behavior:

ContentDisposition.attachment()

Forces browser to download instead of opening in tab.

7. Required Dependency

Make sure Spring Web is included:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

8. Design Summary

This implementation follows a clean separation of concerns:

  • Controller → handles HTTP requests
  • Service → handles business logic
  • Repository → handles persistence
  • File system → stores actual files

Database stores only metadata, not heavy binary data.

9. Common Mistakes

  • Storing raw files in database instead of filesystem
  • Not generating unique filenames → overwrites files
  • Missing multipart/form-data in requests
  • Not setting proper download headers
  • Hardcoding file paths without normalization

10. Final Thoughts

File upload and download is simple on the surface, but production-grade implementation requires careful handling of:

  • file storage strategy
  • naming collisions
  • security risks
  • proper HTTP response configuration

A clean separation between file system storage and database metadata is the most scalable approach used in real-world systems.

View github for code:

https://github.com/SandeshKhatiwada05/attendance-management-system-spring-boot/tree/main/src/main/java/com/samyuka/attendancemanagementsystem/service/documentservice