Building a File Upload Service with Spring Boot

A file upload service with Spring Boot and multipart files gives web and mobile clients a consistent way to send documents, images, and records. Multipart requests package the binary payload and form metadata in one HTTP request, allowing the server to validate, store, and describe each file.

The design should separate upload handling from storage. A service layer validates the incoming part, creates a safe object key, calculates a checksum, and records metadata. The bytes can remain on local disk during development or move to an object store in production without changing the API response.

Security belongs in the request path. Original filenames and browser-supplied media types are untrusted. The service also needs size limits, controlled file types, authenticated access, and a clear policy for scanning or quarantining content before users can download it.

Set up multipart support

Add spring-boot-starter-web and spring-boot-starter-validation to the project. Configure request limits in application.yml so oversized files are rejected consistently across every environment.

spring:
  servlet:
    multipart:
      max-file-size: 10MB
      max-request-size: 12MB
      file-size-threshold: 0B

app:
  upload:
    directory: ${UPLOAD_DIR:./uploads}

The request limit leaves a little space for metadata and multipart boundaries. Setting the threshold to 0B avoids relying on platform-specific temporary-directory behaviour. Production values should come from environment variables or a managed configuration service.

Spring stores temporary multipart files while the request is being processed. The application must therefore have enough temporary disk space, and scheduled jobs should remove abandoned files when using a manually managed upload directory.

Build a validated upload endpoint

Use @RequestPart to receive the file separately from ordinary form fields. Keep the controller thin by delegating validation and persistence to an UploadService.

@RestController
@RequestMapping("/api/uploads")
public class UploadController {

    private final UploadService uploadService;

    public UploadController(UploadService uploadService) {
        this.uploadService = uploadService;
    }

    @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public ResponseEntity<UploadResponse> upload(
            @RequestPart("file") MultipartFile file) {

        UploadResult result = uploadService.store(file);
        URI location = URI.create("/api/uploads/" + result.id());

        return ResponseEntity.created(location)
                .body(new UploadResponse(result.id(), result.originalName()));
    }
}

The service should reject empty files, enforce the configured byte limit, and allow only the formats required by the application. Checking MultipartFile.getContentType() is insufficient because clients can label any payload as an image or PDF. Inspect the file signature, verify it with a content-detection library, and combine that result with an extension allowlist.

Preserve the original filename only as display metadata. Generate a UUID-based storage name and never use the submitted name as a filesystem path. This prevents directory traversal and handles Australian filenames containing spaces, accented characters, or punctuation without creating unsafe paths.

Persist metadata and store files safely

A database record should contain the upload identifier, original filename, generated object key, detected media type, size, SHA-256 checksum, owner, status, and creation time. These fields support searches, audit trails, duplicate detection, and deletion requests without loading the binary content into application memory.

Local disk storage works well for development and modest internal deployments. Production systems commonly use Amazon S3 or an S3-compatible service, with the application storing only object keys in the database. An AWS deployment can use the ap-southeast-2 region around Sydney, while Microsoft Azure provides an Australia East region.

Database and object-store writes must coordinate carefully. Save the file first, then commit its metadata, and remove the stored object if the transaction fails. Periodic reconciliation can identify orphaned files or records that point to missing objects.

Handle failures and background work

Spring’s multipart exceptions can be translated into consistent JSON error responses with @ControllerAdvice. Return a status such as 413 Payload Too Large for oversized files, 415 Unsupported Media Type for prohibited formats, and 400 Bad Request for an empty or malformed part. Error messages should avoid exposing server paths or implementation details.

Large files may need antivirus scanning, thumbnail creation, or document conversion. Move those tasks to a background worker and mark the upload as pending until processing succeeds. Use an idempotency key or unique client reference to prevent retries from creating duplicate records.

Production controls for Australian deployments

Australian property, education, and legal platforms often receive identity documents through mobile devices. Users in Sydney, Melbourne, Brisbane, and Perth may upload over variable 4G or 5G connections, so resumable transfers and meaningful timeout settings can improve reliability. Logistics applications may also handle commercial invoices or Australian Customs Service declarations with stricter access and retention requirements.

When uploads contain personal information, assess obligations under the Privacy Act 1988 and the Australian Privacy Principles. Select an approved Australian cloud region when data residency is required, and apply retention rules according to the business purpose rather than keeping files indefinitely.

Upload safeguards:

Operational checks:

Together, these controls give Australian teams a multipart upload workflow that remains secure, observable, and practical across local development, cloud infrastructure, and mobile use.