Harnessing Cloud Storage with MinIO in Spring Boot: A Student's Guide
Introduction
When building modern applications, efficient data storage and retrieval are critical components to consider. Today, we embark on a journey into the realm of MinIO, an open-source cloud storage solution, to enhance our Spring applications. Let's delve into the basics of MinIO and its seamless integration with the Spring framework.
What's MinIO, Anyway?
MinIO is an open-source object storage solution that provides an Amazon Web Services S3-compatible API and supports all core S3 features. MinIO is built to deploy anywhere and it's not only easy to use but also works super well with Spring – the Java framework we all know and love.
Installation
First, you'll need to install the MinIO server. Installing instructions tailored to your operating system are on the official MinIO documentation website.
After you can explore the available features using the public instance at https://play.min.io/minio/. You can use the following credentials :
Access Key : Q3AM3UQ867SPQQA43P2F
Secret Key : zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG
Integrating MinIO with Spring Boot
Now that we have our MinIO set up, we'll add it to our Spring project. First, add the minio
dependency to your pom.xml
file.
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.5.7</version>
</dependency>
Configuration
Although minio
has its default configuration keys, we can change them for our personal use cases in the applications.properties
file. Let's configure our application to connect to Minio public instance.
# Minio Host
spring.minio.url=https://play.min.io
# Minio Bucket name for your application
spring.minio.bucket=Test
# Minio access key (login)
spring.minio.access-key=Q3AM3UQ867SPQQA43P2F
# Minio secret key (password)
spring.minio.secret-key=zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG
# Minio default folder
spring.minio.default-folder=/
minio
library only manages one bucket for your application and it must already exist when the application starts.Next, we make a configuration class that uses the above keys to create an return an instance of the minio
client which we will use to upload and download our files.
//insert your package
import io.minio.MinioClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MinioConfig{
@Value("${spring.minio.access-key}")
String accessKey;
@Value("${spring.minio.secret-key}")
String secretKey;
@Value("${spring.minio.url}")
String minioUrl;
@Bean
public MinioClient generateMinioClient(){
try{
return MinioClient.builder()
.endpoint(minioUrl)
.credentials(accessKey,secretKey)
.build();
}catch (Exception e){
throw new RuntimeException(e.getMessage());
}
}
}
Uploading Files
Now, we can implement our file upload logic. To do this, we create a MinioAdapter
class where we will store all our logic.
// your package name
import io.minio.MinioClient;
import io.minio.UploadObjectArgs;
import io.minio.messages.Bucket;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.io.*;
import java.util.List;
@Service
public class MinioAdapter {
@Autowired
MinioClient minioClient;
@Value("${spring.minio.bucket}")
String defaultBucketName;
@Value("${spring.minio.default-folder}")
String defaultBaseFolder;
public void uploadFile(String name) {
File file = new File("src/main/resources/" + name);
try (FileInputStream iofs = new FileInputStream(file);){
minioClient.uploadObject(UploadObjectArgs.builder()
.bucket(defaultBucketName)
.object(name)
.filename("src/main/resources/" + name)
.build());
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
}
@PostConstruct
public void init() {
}
}
The above code allows us to store a file in our resources folder in the default bucket we defined in our application.properties
file.
Next, we implement our controller and create an endpoint to upload the files.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
@RestController
@RequestMapping("/files")
public class MinioStorageController{
@Autowired
MinioAdapter minioAdapter;
@PostMapping(path = "/upload", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
public @ResponseBody ResponseEntity<String> uploadFile(@RequestPart(value = "file", required = false) MultipartFile files) throws IOException {
minioAdapter.uploadFile(files.getOriginalFilename());
return new ResponseEntity<>("File uploaded successfully!", HttpStatus.OK);
}
}
The code begins by injecting an instance of MinioAdapter
, the dedicated service we created to handle our logic. A POST endpoint, reachable at "/files/upload
" is established to handle incoming file uploads. The endpoint is designed to consume data in the form of multipart form data, a common method for transmitting files over HTTP.
The next step involves invoking the uploadFile
method of the injected MinioAdapter
. The filename of the uploaded file is extracted using files.getOriginalFilename()
and passed to the MinioAdapter
for handling.
Upon the successful execution of the file upload logic, a response entity is constructed. This entity encapsulates a success message — "File uploaded successfully!" — and an HTTP status of OK (200). This response is then dispatched back to the client, acknowledging the completion of the file upload process.
Downloading Files
Similar to how we upload files, we write our logic to download files from the bucket in the MinioAdapter class
public byte[] getFile(String key) {
try {
InputStream obj = minioClient.getObject(
GetObjectArgs.builder()
.bucket(defaultBucketName)
.object(defaultBaseFolder + "/" + key)
.build());
byte[] content = IOUtils.toByteArray(obj);
obj.close();
return content;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
Next, we create an endpoint to get the file in our Controller.
@GetMapping(path = "/download")
public ResponseEntity<ByteArrayResource> getFile(@RequestParam(value = "file") String file) throws IOException {
byte[] data = minioAdapter.getFile(file);
ByteArrayResource resource = new ByteArrayResource(data);
return ResponseEntity
.ok()
.contentLength(data.length)
.contentType(MediaType.APPLICATION_OCTET_STREAM) // Set the Content-Type
.header("Content-Disposition", "attachment; filename=\"" + file + "\"; filename*=UTF-8''" + URLEncoder.encode(file, StandardCharsets.UTF_8.toString()))
.body(resource);
}
A GET endpoint, reachable at "/files/download
" is established to handle file downloads. The next step involves invoking the getFile
method of the injected MinioAdapter
. Subsequently, a ByteArrayResource
named resource
is instantiated, wrapping the byte array obtained from the MinIO storage.
The core of the method lies in the construction of the response entity using ResponseEntity.ok()
. This signifies a successful HTTP response with an HTTP status of OK (200). We then configure various aspects of the response:
.contentLength(data.length)
: Sets the content length of the response to the size of the byte array, ensuring accurate transmission..contentType(MediaType.APPLICATION_OCTET_STREAM)
: Specifies the content type as application/octet-stream, indicating that the response contains binary data..header("Content-Disposition", ...)
: Defines the Content-Disposition header, instructing the client's browser to treat the response as an attachment. The filename is included, facilitating a seamless download experience. Additionally, a UTF-8 encoded filename parameter is appended to support international characters in the filename.
Now, you can use Postman or any API testing tool to check the endpoint and verify if the files are being uploaded and downloaded.
Conclusion
In this article, we've explored how to use upload and download files from MinIO in your Spring applications. Now go, explore, and build amazing applications with the power of scalable and performant storage.
The code implementations are available on GitHub. If you have any questions or improvements, please let me know in the comments below.