Contents

Cloud Object Storage Spring Boot With Minio

Overview

/images/minio-logo.svg

MinIO is a High Performance Object Storage released under GNU Affero General Public License v3.0. It is API compatible with Amazon S3 cloud storage service. Use MinIO to build high performance infrastructure for machine learning, analytics and application data workloads.

Minio allows the upload and download of files for containerized applications, respecting the interfaces of Amazon S3 solution. The Minio API is requested as HTTP, which allows interoperability regardless of the framework or language used.

In the article, I would use the following terms, which are specific to Minio or S3

  • Bucket: Contains a set of files.
  • Prefix : Virtually, this is a set of directories in which the file is located. All the files are arranged at the root of the bucket, and have a prefix of kind my/prefix/file.pdf.

Minio is a self-hosted solution, you can install it by following instructions here. There is also a public instance to test on https://play.min.io/minio/. You can use the following credentials :

1
2
Access Key : minioadmin
Secret Key : minioadmin

Run Standalone MinIO on Docker

MinIO needs a persistent volume to store configuration and application data. However, for testing purposes, you can launch MinIO by simply passing a directory (/data in the example below). This directory gets created in the container filesystem at the time of container start. But all the data is lost after container exits.

1
2
3
4
5
6
docker run \
  -p 9000:9000 \
  -p 9001:9001 \
  -e "MINIO_ROOT_USER=<USERNAME>" \
  -e "MINIO_ROOT_PASSWORD=<PASSWORD>" \
  quay.io/minio/minio server /data --console-address ":9001"

Project Setup and Dependencies

I’m depending Spring Initializr for this as it is much easier.

Our example application will be a Spring Boot application. So we need to add some dependencies to our pom.xml.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.12</version>
    <scope>provided</scope>
</dependency>

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

<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>4.9.1</version>
</dependency>

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.11.0</version>
</dependency>

Change configuration application.properties file like following below. I will use play.min.io to this documentation which is open source and for demo the other projects.

1
2
3
4
5
6
7
8
server.port=8080
spring.servlet.multipart.max-file-size=2MB

# Minio
minio.bucket.name=minio-example-demo
minio.access.key=minioadmin
minio.access.secret=minioadmin
minio.url=https://play.min.io

Upload Some Image on Minio

I will create folder inside the minio-example-demo bucket is myfolder and upload some images.

/images/minio1.png

Implementation

Bean Configuration

Create bean configuration that can be used for dependency injection on com.piinalpin.minio.config.MinioConfiguration like following below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Configuration
public class MinioConfiguration {

    @Value("${minio.access.key}")
    private String accessKey;

    @Value("${minio.access.secret}")
    private String secretKey;

    @Value("${minio.url}")
    private String minioUrl;

    @Bean
    @Primary
    public MinioClient minioClient() {
        return new MinioClient.Builder()
                .credentials(accessKey, secretKey)
                .endpoint(minioUrl)
                .build();
    }

}

Data Transfer Object

Create a dto class to construct object even for request or response com.piinalpin.minio.http.dto.FileDto like following below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class FileDto implements Serializable {

    private static final long serialVersionUID = 232836038145089522L;

    private String title;

    private String description;

    @SuppressWarnings("java:S1948")
    private MultipartFile file;

    private String url;

    private Long size;

    private String filename;

}

Get List Objects from Bucket

Create service to get list objects inside bucket at om.piinalpin.minio.service.MinioService like following below. First we should inject MinioClient to use minio and define bucket name get from properties file.

1
2
3
4
5
@Autowired
private MinioClient minioClient;

@Value("${minio.bucket.name}")
    private String bucketName;

Create method to get list objects from bucket in minio like following below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@Slf4j
@Service
public class MinioService {

    @Autowired
    private MinioClient minioClient;

    @Value("${minio.bucket.name}")
    private String bucketName;

    public List<FileDto> getListObjects() {
        List<FileDto> objects = new ArrayList<>();
        try {
            Iterable<Result<Item>> result = minioClient.listObjects(ListObjectsArgs.builder()
                    .bucket(bucketName)
                    .recursive(true)
                    .build());
            for (Result<Item> item : result) {
                objects.add(FileDto.builder()
                        .filename(item.get().objectName())
                        .size(item.get().size())
                        .url(getPreSignedUrl(item.get().objectName()))
                        .build());
            }
            return objects;
        } catch (Exception e) {
            log.error("Happened error when get list objects from minio: ", e);
        }

        return objects;
    }

    private String getPreSignedUrl(String filename) {
        return "http://localhost:8080/file/".concat(filename);
    }

}

Create a controller to interacted with user to get list objects of bucket com.piinalpin.minio.http.controller.FileController like following below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@Slf4j
@RestController
@RequestMapping(value = "/file")
public class FileController {

    @Autowired
    private MinioService minioService;

    @GetMapping
    public ResponseEntity<Object> getFiles() {
        return ResponseEntity.ok(minioService.getListObjects());
    }

}

Let’s try to running spring boot application by typing command on your terminal like following below.

1
mvn spring-boot:run 

And open your postman or thunder client or anything else to get list objects of bucket by accessing http://localhost:8080/file. Then we should get an output.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
[
  {
    "url": "http://localhost:8080/file/myfolder/microservices-portable-networkjpg.jpg",
    "size": 25816,
    "filename": "myfolder/microservices-portable-networkjpg.jpg"
  },
  {
    "url": "http://localhost:8080/file/surrounding.png",
    "size": 37600,
    "filename": "surrounding.png"
  }
]

Upload File into Minio

Create method in MinioService to upload file like following below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public FileDto uploadFile(FileDto request) {
    try {
        minioClient.putObject(PutObjectArgs.builder()
                .bucket(bucketName)
                .object(request.getFile().getOriginalFilename())
                .stream(request.getFile().getInputStream(), request.getFile().getSize(), -1)
                .build());
    } catch (Exception e) {
        log.error("Happened error when upload file: ", e);
    }
    return FileDto.builder()
            .title(request.getTitle())
            .description(request.getDescription())
            .size(request.getFile().getSize())
            .url(getPreSignedUrl(request.getFile().getOriginalFilename()))
            .filename(request.getFile().getOriginalFilename())
            .build();
}

Create another controller to handle upload file like following below.

1
2
3
4
@PostMapping(value = "/upload")
public ResponseEntity<Object> upload(@ModelAttribute FileDto request) {
    return ResponseEntity.ok().body(minioService.uploadFile(request));
}

Let’s try to running spring boot application by typing command on your terminal like following below.

1
mvn spring-boot:run 

And open your postman or thunder client or anything else to get list objects of bucket by accessing http://localhost:8080/file/upload with method POST. The request should like below.

/images/minio2.png

And we should get an output.

1
2
3
4
5
6
7
{
  "title": "Maverick",
  "description": "Maverick description",
  "url": "http://localhost:8080/file/logo_rrq.jpg",
  "size": 87624,
  "filename": "logo_rrq.jpg"
}

Download File from Minio

Create method in MinioService to get object from minio and transfer it to response application/octet-stream like following below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public InputStream getObject(String filename) {
    InputStream stream;
    try {
        stream = minioClient.getObject(GetObjectArgs.builder()
                .bucket(bucketName)
                .object(filename)
                .build());
    } catch (Exception e) {
        log.error("Happened error when get list objects from minio: ", e);
        return null;
    }

    return stream;
}

Create another controller to download file with dynamic path variables like following below.

1
2
3
4
5
6
7
8
@GetMapping(value = "/**")
public ResponseEntity<Object> getFile(HttpServletRequest request) throws IOException {
    String pattern = (String) request.getAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE);
    String filename = new AntPathMatcher().extractPathWithinPattern(pattern, request.getServletPath());
    return ResponseEntity.ok()
            .contentType(MediaType.APPLICATION_OCTET_STREAM)
            .body(IOUtils.toByteArray(minioService.getObject(filename)));
}

Let’s try to running spring boot application by typing command on your terminal like following below.

1
mvn spring-boot:run 

And open your browser to get list objects of bucket by accessing http://localhost:8080/file/{filename}. Change {filename} to your filename or object name on minio bucket. For example I will use inside myfolder, so the url shoul be like this http://localhost:8080/file/myfolder/microservices-portable-networkjpg.jpg. Then the browser should be auto download the file.

Clone or Download

You can clone or download this project at

1
https://github.com/piinalpin/minio-example.git

Reference