Backend Development 16 min read

Integrating Spring Boot with Minio for Direct File Upload Using Presigned Credentials, Chunked Uploads, and Merging

This article demonstrates how to integrate Spring Boot with Minio, compare uploading files via the backend versus direct client uploads with presigned credentials, and provides complete code examples for environment setup, configuration, presign endpoint, front‑end upload logic, chunked uploads, and server‑side file merging.

Java Architect Essentials
Java Architect Essentials
Java Architect Essentials
Integrating Spring Boot with Minio for Direct File Upload Using Presigned Credentials, Chunked Uploads, and Merging

Spring Boot can be integrated with Minio to handle file uploads in two ways: uploading through the backend (which allows centralized authentication, permission control, and additional processing) and uploading directly from the client using a presigned credential generated by the backend.

Environment preparation

Deploy a Minio instance (e.g., http://mylocalhost:9001 ).

Spring Boot integration

1. Add the Minio dependency:

<dependency>
    <groupId>io.minio</groupId>
    <artifactId>minio</artifactId>
    <version>7.1.0</version>
</dependency>

2. Configure Minio properties in application.yml :

# application.yml
minio:
  endpoint: http://mylocalhost:9001
  accessKey: minio
  secretKey: minio123
  bucket: demo

3. Define a properties class:

@Component
@ConfigurationProperties(prefix = "minio")
public class MinioProperties {
    private String endpoint;
    private String accessKey;
    private String secretKey;
    private String bucket;
    // getters and setters
}

4. Create a Minio configuration bean:

@Configuration
public class MinioConfig {
    @Bean
    public MinioClient minioClient(MinioProperties properties) {
        try {
            MinioClient.Builder builder = MinioClient.builder();
            builder.endpoint(properties.getEndpoint());
            if (StringUtils.hasLength(properties.getAccessKey()) && StringUtils.hasLength(properties.getSecretKey())) {
                builder.credentials(properties.getAccessKey(), properties.getSecretKey());
            }
            return builder.build();
        } catch (Exception e) {
            return null;
        }
    }
}

After starting the service, the backend can generate a presigned upload policy.

Presign endpoint

@RequestMapping(value = "/presign", method = {RequestMethod.POST})
public Map
presign(@RequestBody PresignParam presignParam) {
    if (StringUtils.isEmpty(presignParam.getBucket())) {
        presignParam.setBucket("demo");
    }
    if (StringUtils.isEmpty(presignParam.getFilename())) {
        presignParam.setFilename(UUID.randomUUID().toString());
    }
    ZonedDateTime expirationDate = ZonedDateTime.now().plusMinutes(10);
    PostPolicy policy = new PostPolicy(presignParam.getBucket(), presignParam.getFilename(), expirationDate);
    try {
        Map
map = minioClient.presignedPostPolicy(policy);
        return map;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

The returned map contains fields such as bucket , x-amz-date , x-amz-signature , key , x-amz-algorithm , x-amz-credential , and policy , which the front‑end must include in the multipart form data when uploading.

Front‑end upload example

uploadFile(file, policy) {
    var formData = new FormData();
    formData.append('file', file);
    formData.append('key', policy['key']);
    formData.append('x-amz-algorithm', policy['x-amz-algorithm']);
    formData.append('x-amz-credential', policy['x-amz-credential']);
    formData.append('x-amz-signature', policy['x-amz-signature']);
    formData.append('x-amz-date', policy['x-amz-date']);
    formData.append('policy', policy['policy']);
    return new Promise((resolve, reject) => {
        $.ajax({
            method: 'POST',
            url: 'http://mylocalhost:9001/' + policy['bucket'],
            data: formData,
            contentType: false,
            processData: false,
            xhr: function () {
                var xhr = $.ajaxSettings.xhr();
                if (xhr.upload) {
                    xhr.upload.addEventListener('progress', function (e) {
                        var percentage = parseInt(e.loaded / e.total * 100);
                        vm.uploadResult = percentage + "% :" + policy['key'];
                    }, false);
                }
                return xhr;
            },
            success: function (result) {
                vm.uploadResult = '文件上传成功:' + policy['key'];
                resolve(result);
            },
            error: function () { reject(); }
        });
    });
}

Chunked (slice) upload and merge

For large files, the front‑end can split the file into 5 MB chunks, calculate an MD5 for each chunk, request a presign policy for each slice, upload them individually, and finally call a merge endpoint on the server.

@GetMapping("/compose")
public void merge() {
    List
sources = new ArrayList<>();
    sources.add(ComposeSource.builder().bucket("slice").object("0寂寞的季节.mp4").build());
    sources.add(ComposeSource.builder().bucket("slice").object("1寂寞的季节.mp4").build());
    sources.add(ComposeSource.builder().bucket("slice").object("2寂寞的季节.mp4").build());
    ComposeObjectArgs args = ComposeObjectArgs.builder()
        .bucket("demo")
        .object("寂寞的季节.mp4")
        .sources(sources)
        .build();
    try {
        minioClient.composeObject(args);
    } catch (Exception e) { e.printStackTrace(); }
}

The front‑end code for slicing, MD5 calculation, and invoking the merge API is also provided in the original article.

Complete front‑end demo page

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Minio Test</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/vue/2.7.14/vue.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/spark-md5/3.0.2/spark-md5.min.js"></script>
</head>
<body>
    <div id="app">
        ... (Vue component code as in the article) ...
    </div>
<script>/* Vue instance code from the article */</script>
</body>
</html>

The article concludes with a call to action and promotional links, which are not part of the technical content.

JavaSpring BootFile UploadMinIOChunked UploadPresigned URL
Java Architect Essentials
Written by

Java Architect Essentials

Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.