Spring Boot Storage Integration with MinIO SDK Versions 7 and 8

Maven Dependency Configuration

For projects using the legacy MinIO SDK version 7, the following dependency structure is required. Note that when using Spring Boot 2.7 or newer, you must explicitly declare the OkHttp dependency before the MinIO dependency to insure compatibility.

<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>4.9.0</version>
</dependency>
<dependency>
    <groupId>io.minio</groupId>
    <artifactId>minio</artifactId>
    <version>7.0.2</version>
</dependency>

For modern implementations using MinIO SDK version 8, use the following configuration which updates the OkHttp library and manages transitive dependencies.

<dependency>
    <groupId>io.minio</groupId>
    <artifactId>minio</artifactId>
    <version>8.4.6</version>
    <exclusions>
        <exclusion>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>4.11.0</version>
</dependency>

Application Properties

Define the connection parameters and default bucket in your application.yml or application.properties file:

storage.minio.endpoint=http://localhost:9000
storage.minio.access-key=minioadmin
storage.minio.secret-key=minioadmin
storage.minio.bucket-name=assets

Implementation for MinIO SDK 7

In version 7, the MinioClient is instantiated directly using the constructor, and methods typically accept direct string arguments.

@Component
public class LegacyMinioProvider implements InitializingBean {
    @Value("${storage.minio.endpoint}")
    private String host;
    @Value("${storage.minio.access-key}")
    private String userKey;
    @Value("${storage.minio.secret-key}")
    private String passKey;
    @Value("${storage.minio.bucket-name}")
    private String defaultBucket;

    private MinioClient client;

    @Override
    public void afterPropertiesSet() throws Exception {
        this.client = new MinioClient(host, userKey, passKey);
    }

    @SneakyThrows
    public void uploadStream(String bucket, String name, InputStream input, String mimeType) {
        String targetBucket = bucket.isEmpty() ? defaultBucket : bucket;
        if (!client.bucketExists(targetBucket)) {
            client.makeBucket(targetBucket);
        }
        PutObjectOptions options = new PutObjectOptions(input.available(), -1);
        options.setContentType(mimeType);
        client.putObject(targetBucket, name, input, options);
    }

    @SneakyThrows
    public void deleteFile(String bucket, String name) {
        client.removeObject(bucket.isEmpty() ? defaultBucket : bucket, name);
    }

    @SneakyThrows
    public String getPresignedUrl(String name, int seconds) {
        return client.presignedGetObject(defaultBucket, name, seconds);
    }
}

Implementation for MinIO SDK 8

Version 8 introduces the Builder pattern for client creation and uses specific Args objects for all API interactions.

@Component
@Slf4j
public class ModernMinioProvider implements InitializingBean {
    @Value("${storage.minio.endpoint}")
    private String host;
    @Value("${storage.minio.access-key}")
    private String userKey;
    @Value("${storage.minio.secret-key}")
    private String passKey;
    @Value("${storage.minio.bucket-name}")
    private String defaultBucket;

    private MinioClient client;

    @Override
    public void afterPropertiesSet() {
        this.client = MinioClient.builder()
                .endpoint(host)
                .credentials(userKey, passKey)
                .build();
    }

    @SneakyThrows
    public void uploadFile(String name, InputStream stream, String contentType) {
        if (!client.bucketExists(BucketExistsArgs.builder().bucket(defaultBucket).build())) {
            client.makeBucket(MakeBucketArgs.builder().bucket(defaultBucket).build());
        }
        client.putObject(PutObjectArgs.builder()
                .bucket(defaultBucket)
                .object(name)
                .stream(stream, stream.available(), -1)
                .contentType(contentType)
                .build());
    }

    @SneakyThrows
    public String generatePreviewUrl(String objectName, int expiryInSeconds) {
        return client.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
                .method(Method.GET)
                .bucket(defaultBucket)
                .object(objectName)
                .expiry(expiryInSeconds)
                .build());
    }

    public void removeFiles(List<String> names) {
        List<DeleteObject> targets = names.stream().map(DeleteObject::new).collect(Collectors.toList());
        Iterable<Result<DeleteError>> results = client.removeObjects(
                RemoveObjectsArgs.builder().bucket(defaultBucket).objects(targets).build());
        for (Result<DeleteError> result : results) {
            try {
                DeleteError error = result.get();
                log.error("Deletion failed for object: {}", error.objectName());
            } catch (Exception e) {
                log.error("Error parsing delete result", e);
            }
        }
    }
}

REST Controller Integration

This controller exposes endpoints to handle file lifecycle operations via the storage provider.

@RestController
@RequestMapping("/api/storage")
public class StorageController {
    @Autowired
    private ModernMinioProvider storageService;

    @PostMapping("/upload")
    public ResponseEntity<String> handleUpload(@RequestParam("file") MultipartFile file) throws Exception {
        if (file.isEmpty()) return ResponseEntity.badRequest().body("Empty file");
        
        String originalName = file.getOriginalFilename();
        String extension = originalName.substring(originalName.lastIndexOf(".") + 1).toLowerCase();
        
        storageService.uploadFile(originalName, file.getInputStream(), getMimeType(extension));
        return ResponseEntity.ok("Upload successful");
    }

    @GetMapping("/view")
    public String getUrl(@RequestParam String filename) {
        return storageService.generatePreviewUrl(filename, 3600);
    }

    private String getMimeType(String ext) {
        switch (ext) {
            case "png": return "image/png";
            case "jpg":
            case "jpeg": return "image/jpeg";
            case "pdf": return "application/pdf";
            default: return "application/octet-stream";
        }
    }
}

File Download Implementation

To allow clients to download files directly as a stream, use the following logic in your service or controller:

public void streamDownload(HttpServletResponse response, String fileName) {
    try (InputStream is = storageService.getObjectStream(fileName)) {
        StatObjectResponse metadata = storageService.getObjectMetadata(fileName);
        response.setContentType(metadata.contentType());
        response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
        org.apache.commons.io.IOUtils.copy(is, response.getOutputStream());
        response.flushBuffer();
    } catch (IOException e) {
        log.error("Download error", e);
    }
}

Tags: Spring Boot MinIO java Object Storage

Posted on Sun, 02 Aug 2026 16:45:41 +0000 by koopmaster