Integrating MinIO Object Storage with Spring Boot Applications
MinIO Object Storage Integration Guide
Installing MinIO
Docker Installation
# Create directory for MinIO data storage
mkdir -p /opt/docker/minio/data
# Launch MinIO with specified ports
docker run \
-p 9000:9000 \
-p 5001:5001 \
--name minio \
-v /opt/docker/minio/data:/data \
-e "MINIO_ROOT_USER=minioadmin" \
-e "MINIO_ROOT_PASSWORD=minioadmin" \
-d minio/minio server /data --console-address ":5001"
# Configure to start automatically with Docker
docker update --restart=always
Official Binary Download
The official website provides multiple versions for different platofrms: https://www.minio.org.cn/download.shtml#/windows
Adding Required Dependencies
<!-- https://mvnrepository.com/artifact/io.minio/minio -->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.4.0</version>
</dependency>
Application Configuration
storage:
minio:
endpoint: http://192.168.218.131:9000 # MinIO server address
bucket: file-storage # Bucket name
access-key: minioadmin # Access key
secret-key: minioadmin # Secret key
MinIO Configuration Class
@Data
@Configuration
@ConfigurationProperties(prefix = "storage.minio")
public class StorageConfiguration {
private String endpoint;
private String accessKey;
private String secretKey;
private String bucket;
@Bean
public MinioClient storageClient() {
return MinioClient.builder()
.endpoint(endpoint)
.credentials(accessKey, secretKey)
.build();
}
}
Storage Service Implementation
@Component
@Slf4j
public class FileStorageService {
@Autowired
private StorageConfiguration config;
@Resource
private MinioClient storageClient;
/**
* Check if bucket exists
* @param bucketName bucket name to check
* @return true if bucket exists
*/
public boolean verifyBucket(String bucketName) {
try {
return storageClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
} catch (Exception e) {
log.error("Error checking bucket existence", e);
return false;
}
}
/**
* Create a new bucket
* @param bucketName name of the bucket to create
* @return true if creation was successful
*/
public boolean createBucket(String bucketName) {
try {
storageClient.makeBucket(MakeBucketArgs.builder()
.bucket(bucketName)
.build());
return true;
} catch (Exception e) {
log.error("Error creating bucket", e);
return false;
}
}
/**
* Remove a bucket
* @param bucketName name of the bucket to remove
* @return true if removal was successful
*/
public boolean deleteBucket(String bucketName) {
try {
storageClient.removeBucket(RemoveBucketArgs.builder()
.bucket(bucketName)
.build());
return true;
} catch (Exception e) {
log.error("Error deleting bucket", e);
return false;
}
}
/**
* Get all available buckets
* @return list of buckets
*/
public List<Bucket> fetchAllBuckets() {
try {
return storageClient.listBuckets();
} catch (Exception e) {
log.error("Error listing buckets", e);
return Collections.emptyList();
}
}
/**
* Upload a file to storage
* @param file the file to upload
* @return the generated object name or null if failed
*/
public String storeFile(MultipartFile file) {
String originalName = file.getOriginalFilename();
if (StringUtils.isBlank(originalName)) {
throw new IllegalArgumentException("File name cannot be empty");
}
String fileExtension = originalName.substring(originalName.lastIndexOf("."));
String uniqueFileName = generateUniqueIdentifier() + fileExtension;
String objectPath = buildDatePath() + "/" + uniqueFileName;
try {
PutObjectArgs uploadArgs = PutObjectArgs.builder()
.bucket(config.getBucket())
.object(objectPath)
.stream(file.getInputStream(), file.getSize(), -1)
.contentType(file.getContentType())
.build();
storageClient.putObject(uploadArgs);
return objectPath;
} catch (Exception e) {
log.error("Error uploading file", e);
return null;
}
}
/**
* Generate a preview URL for an image
* @param objectName the object name
* @return preview URL or null if failed
*/
public String getPreviewUrl(String objectName) {
try {
GetPresignedObjectUrlArgs previewArgs = GetPresignedObjectUrlArgs.builder()
.bucket(config.getBucket())
.object(objectName)
.method(Method.GET)
.build();
return storageClient.getPresignedObjectUrl(previewArgs);
} catch (Exception e) {
log.error("Error generating preview URL", e);
return null;
}
}
/**
* Download a file
* @param objectName the object name
* @param response the HTTP response
*/
public void retrieveFile(String objectName, HttpServletResponse response) {
try (GetObjectResponse fileResponse = storageClient.getObject(
GetObjectArgs.builder()
.bucket(config.getBucket())
.object(objectName)
.build())) {
byte[] buffer = new byte[1024];
int bytesRead;
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
while ((bytesRead = fileResponse.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
outputStream.flush();
byte[] fileData = outputStream.toByteArray();
response.setCharacterEncoding("utf-8");
response.addHeader("Content-Disposition", "attachment;filename=" + objectName);
try (ServletOutputStream out = response.getOutputStream()) {
out.write(fileData);
out.flush();
}
} catch (Exception e) {
log.error("Error downloading file", e);
}
}
/**
* List all objects in the bucket
* @return list of objects
*/
public List<Item> listAllObjects() {
try {
Iterable<Result<Item>> results = storageClient.listObjects(
ListObjectsArgs.builder()
.bucket(config.getBucket())
.build());
List<Item> items = new ArrayList<>();
for (Result<Item> result : results) {
items.add(result.get());
}
return items;
} catch (Exception e) {
log.error("Error listing objects", e);
return Collections.emptyList();
}
}
/**
* Delete a file
* @param objectName the object name to delete
* @return true if deletion was successful
*/
public boolean deleteFile(String objectName) {
try {
storageClient.removeObject(
RemoveObjectArgs.builder()
.bucket(config.getBucket())
.object(objectName)
.build());
return true;
} catch (Exception e) {
log.error("Error deleting file", e);
return false;
}
}
private String generateUniqueIdentifier() {
return UUID.randomUUID().toString();
}
private String buildDatePath() {
return LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM/dd"));
}
}
File Management Controller
@Api(tags = "File Management API")
@Slf4j
@RestController
@RequestMapping("/api/files")
public class FileManagementController {
@Autowired
private FileStorageService fileStorageService;
@Autowired
private StorageConfiguration storageConfig;
@ApiOperation(value = "Verify bucket existence")
@GetMapping("/verify-bucket")
public ResponseEntity<?> verifyBucket(@RequestParam("bucketName") String bucketName) {
boolean exists = fileStorageService.verifyBucket(bucketName);
return ResponseEntity.ok(Map.of("exists", exists));
}
@ApiOperation(value = "Create new bucket")
@PostMapping("/create-bucket")
public ResponseEntity<?> createBucket(@RequestParam("bucketName") String bucketName) {
boolean created = fileStorageService.createBucket(bucketName);
return ResponseEntity.ok(Map.of("created", created));
}
@ApiOperation(value = "Delete bucket")
@DeleteMapping("/delete-bucket")
public ResponseEntity<?> deleteBucket(@RequestParam("bucketName") String bucketName) {
boolean deleted = fileStorageService.deleteBucket(bucketName);
return ResponseEntity.ok(Map.of("deleted", deleted));
}
@ApiOperation(value = "List all buckets")
@GetMapping("/buckets")
public ResponseEntity<?> getAllBuckets() {
List<Bucket> buckets = fileStorageService.fetchAllBuckets();
return ResponseEntity.ok(Map.of("buckets", buckets));
}
@ApiOperation(value = "Upload file")
@PostMapping("/upload")
public ResponseEntity<?> uploadFile(@RequestParam("file") MultipartFile file) {
String objectPath = fileStorageService.storeFile(file);
if (objectPath != null) {
String fileUrl = storageConfig.getEndpoint() + "/" + storageConfig.getBucket() + "/" + objectPath;
return ResponseEntity.ok(Map.of("url", fileUrl, "path", objectPath));
}
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("File upload failed");
}
@ApiOperation(value = "Get file preview URL")
@GetMapping("/preview")
public ResponseEntity<?> getPreviewUrl(@RequestParam("path") String objectPath) {
String previewUrl = fileStorageService.getPreviewUrl(objectPath);
if (previewUrl != null) {
return ResponseEntity.ok(Map.of("previewUrl", previewUrl));
}
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Preview URL generation failed");
}
@ApiOperation(value = "Download file")
@GetMapping("/download")
public void downloadFile(@RequestParam("path") String objectPath, HttpServletResponse response) {
fileStorageService.retrieveFile(objectPath, response);
}
@ApiOperation(value = "Delete file")
@DeleteMapping("/delete")
public ResponseEntity<?> deleteFile(@RequestParam("url") String fileUrl) {
String objectPath = extractObjectPathFromUrl(fileUrl);
boolean deleted = fileStorageService.deleteFile(objectPath);
return ResponseEntity.ok(Map.of("deleted", deleted, "path", objectPath));
}
private String extractObjectPathFromUrl(String fileUrl) {
return fileUrl.substring(fileUrl.lastIndexOf(storageConfig.getBucket() + "/") +
storageConfig.getBucket().length() + 1);
}
}