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);
}
}