java 116 lines · 3 tabs

Spring Boot Multipart File Upload with Metadata Persistence and Validation

Shared by codesnips Jul 2026
3 tabs
@Entity
@Table(name = "stored_files")
public class StoredFile {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String filename;

    @Column(nullable = false)
    private String contentType;

    @Column(nullable = false)
    private long size;

    @Column(name = "storage_key", nullable = false, unique = true)
    private String storageKey;

    @Column(length = 512)
    private String description;

    @Column(name = "uploaded_at", nullable = false)
    private Instant uploadedAt;

    @PrePersist
    void onCreate() {
        if (this.storageKey == null) {
            this.storageKey = UUID.randomUUID().toString().replace("-", "");
        }
        this.uploadedAt = Instant.now();
    }

    public String getStorageKey() { return storageKey; }
    public void setStorageKey(String storageKey) { this.storageKey = storageKey; }
    public void setFilename(String filename) { this.filename = filename; }
    public void setContentType(String contentType) { this.contentType = contentType; }
    public void setSize(long size) { this.size = size; }
    public void setDescription(String description) { this.description = description; }
    public Long getId() { return id; }
}
3 files · java Explain with highlit

This snippet shows how a Spring Boot service accepts an uploaded file over multipart/form-data, streams it to disk, and persists a durable metadata record so the raw bytes and their descriptive information stay decoupled. The pattern is common whenever an application needs to serve, audit, or garbage-collect uploads later: the binary content lives on a filesystem (or object store), while a database row carries the searchable facts about it.

In StoredFile entity, the JPA entity models exactly that metadata: the original client-provided filename, the negotiated contentType, the byte size, and a storageKey that points at the physical location. A UUID-based storageKey is generated in @PrePersist so the on-disk name never collides even when two clients upload files with the same original name, and it avoids leaking user-controlled paths. The entity deliberately stores no bytes — keeping large blobs out of the row keeps queries and backups cheap.

FileStorageService isolates all disk I/O behind one class. store derives a fresh storageKey, resolves it against a configured rootLocation, and calls Files.copy with REPLACE_EXISTING while streaming from MultipartFile.getInputStream(), so the whole file is never buffered in memory at once. The crucial detail is the normalize().startsWith(rootLocation) guard: it defeats path-traversal attempts (a ../ in a crafted name) before anything touches the filesystem. Any IOException is wrapped in a domain StorageException so callers deal with one failure type.

FileUploadController ties it together. The @PostMapping consumes MULTIPART_FORM_DATA_VALUE and binds the part to a @RequestParam("file") MultipartFile, alongside an optional free-text description field from the same form. It rejects empty uploads early, delegates byte-writing to the service, then builds and saves a StoredFile transactionally. The handler returns 201 Created with a Location header pointing at the new resource, following REST conventions. A @ControllerAdvice-style @ExceptionHandler maps MaxUploadSizeExceededException to 413, which is the pitfall most first implementations miss — Spring's multipart size limits throw before the controller body runs, so they must be handled at the framework boundary rather than inside the method.


Related snips

graphql
type User {
    id: ID!
    name: String!
    email: String!
    posts: [Post!]!
    createdAt: String!

GraphQL API with Spring Boot

java graphql spring-boot
by David Kumar 3 tabs
ruby
class SignupForm
  include ActiveModel::Model
  include ActiveModel::Attributes

  attribute :account_name, :string
  attribute :email, :string

Shallow Controller, Deep Params: Form Object Pattern

rails activemodel form-object
by codesnips 3 tabs
java
package com.example.starter.config;

import com.example.starter.properties.CustomProperties;
import com.example.starter.service.CustomService;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;

Custom Spring Boot starters

java spring-boot starter
by David Kumar 4 tabs
html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Form Validation Example</title>
  <style>

HTML forms with validation and accessibility

html forms validation
by Alex Chang 1 tab
ruby
# Installation
# rails active_storage:install
# rails db:migrate

# config/storage.yml
local:

ActiveStorage for file uploads and attachments

ruby rails active-storage
by Sarah Mitchell 2 tabs
java
package com.example.demo.config;

import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.apache.kafka.common.serialization.StringSerializer;

Messaging with Apache Kafka

java kafka messaging
by David Kumar 3 tabs

Share this code

Here's the card — post it anywhere.

Spring Boot Multipart File Upload with Metadata Persistence and Validation — share card
Link copied