@Singleton
public class ProfileService {
private static final Logger LOG = LoggerFactory.getLogger(ProfileService.class);
private final ObjectStorageOperations<?, ?, ?> objectStorage;
public ProfileService(ObjectStorageOperations<?, ?, ?> objectStorage) {
this.objectStorage = objectStorage;
}
}
Micronaut Object Storage
Micronaut Object Storage provides a uniform API to create, read and delete objects in the major cloud providers
Version: 2.5.0
1 Introduction
Micronaut Object Storage provides a uniform API to create, read and delete objects in the major cloud providers:
There is also a local storage implementation for testing purposes.
Using this API enables the creation of truly multi-cloud, portable applications.
2 Release History
For this project, you can find a list of releases (with release notes) here:
3 Quick Start
To get started, you need to declare a dependency for the actual cloud provider you are using. See the actual cloud provider documentation for more details:
Then, you can inject in your controllers/services/etc. a bean of type ObjectStorageOperations, the parent interface that allows you to use the API in a generic way for all cloud providers:
@Singleton
class ProfileService {
final ObjectStorageOperations<?, ?, ?> objectStorage
ProfileService(ObjectStorageOperations<?, ?, ?> objectStorage) {
this.objectStorage = objectStorage
}
}
@Singleton
open class ProfileService(private val objectStorage: ObjectStorageOperations<*, *, *>) {
}
If your application is not multi-cloud, and/or you need cloud-specific details, you can use a concrete implementation. For example, for AWS S3:
@Controller
public class UploadController {
private final AwsS3Operations objectStorage;
public UploadController(AwsS3Operations objectStorage) {
this.objectStorage = objectStorage;
}
}
@Controller
class UploadController {
final AwsS3Operations objectStorage
UploadController(AwsS3Operations objectStorage) {
this.objectStorage = objectStorage
}
}
@Controller
open class UploadController(private val objectStorage: AwsS3Operations) {
}
If you have multiple object storages configured, it is possible to select which one to work with via bean qualifiers.
For example, given the following configuration:
src/main/resources/application-ec2.yml
micronaut.object-storage.aws.pictures.bucket=pictures-bucket
micronaut.object-storage.aws.logos.bucket=logos-bucket
micronaut:
object-storage:
aws:
pictures:
bucket: pictures-bucket
logos:
bucket: logos-bucket
[micronaut]
[micronaut.object-storage]
[micronaut.object-storage.aws]
[micronaut.object-storage.aws.pictures]
bucket="pictures-bucket"
[micronaut.object-storage.aws.logos]
bucket="logos-bucket"
micronaut {
objectStorage {
aws {
pictures {
bucket = "pictures-bucket"
}
logos {
bucket = "logos-bucket"
}
}
}
}
{
micronaut {
object-storage {
aws {
pictures {
bucket = "pictures-bucket"
}
logos {
bucket = "logos-bucket"
}
}
}
}
}
{
"micronaut": {
"object-storage": {
"aws": {
"pictures": {
"bucket": "pictures-bucket"
},
"logos": {
"bucket": "logos-bucket"
}
}
}
}
}
You then need to use @Named("pictures")
or @Named("logos")
to specify which of the object storages you want to use.
Uploading files
public String saveProfilePicture(String userId, Path path) {
UploadRequest request = UploadRequest.fromPath(path, userId); // (1)
UploadResponse<?> response = objectStorage.upload(request); // (2)
return response.getKey(); // (3)
}
String saveProfilePicture(String userId, Path path) {
UploadRequest request = UploadRequest.fromPath(path, userId) // (1)
UploadResponse response = objectStorage.upload(request) // (2)
response.key // (3)
}
open fun saveProfilePicture(userId: String, path: Path): String? {
val request = UploadRequest.fromPath(path, userId) // (1)
val response = objectStorage.upload(request) // (2)
return response.key // (3)
}
1 | You can use any of the UploadRequest static methods to build an upload request. |
2 | The upload operation returns an UploadResponse, which wraps the cloud-specific SDK response object. |
3 | The response object contains some common properties for all cloud vendor, and a getNativeResponse() method that can
be used for accessing the vendor-specific response object. |
In case you want to have better control of the upload options used, you can use the method
upload(UploadRequest, Consumer)
of ObjectStorageOperations, which will give you access to the
cloud vendor-specific request class or builder.
For example, for AWS S3:
UploadResponse<PutObjectResponse> response = objectStorage.upload(objectStorageUpload, builder -> {
builder.acl(ObjectCannedACL.PUBLIC_READ);
});
UploadResponse<PutObjectResponse> response = objectStorage.upload(objectStorageUpload, { builder ->
builder.acl(ObjectCannedACL.PUBLIC_READ)
})
val response = objectStorage.upload(objectStorageUpload) { builder: PutObjectRequest.Builder ->
builder.acl(ObjectCannedACL.PUBLIC_READ)
}
Retrieving files
public Optional<Path> retrieveProfilePicture(String userId, String fileName) {
Path destination = null;
try {
String key = userId + "/" + fileName;
Optional<InputStream> stream = objectStorage.retrieve(key) // (1)
.map(ObjectStorageEntry::getInputStream);
if (stream.isPresent()) {
destination = File.createTempFile(userId, "temp").toPath();
Files.copy(stream.get(), destination, StandardCopyOption.REPLACE_EXISTING);
return Optional.of(destination);
} else {
return Optional.empty();
}
} catch (IOException e) {
LOG.error("Error while trying to save profile picture to the local file [{}]: {}", destination, e.getMessage());
return Optional.empty();
}
}
Optional<Path> retrieveProfilePicture(String userId, String fileName) {
String key = "${userId}/${fileName}"
Optional<InputStream> stream = objectStorage.retrieve(key) // (1)
.map(ObjectStorageEntry::getInputStream)
if (stream.isPresent()) {
Path destination = File.createTempFile(userId, "temp").toPath()
Files.copy(stream.get(), destination, StandardCopyOption.REPLACE_EXISTING)
return Optional.of(destination)
} else {
return Optional.empty()
}
}
open fun retrieveProfilePicture(userId: String, fileName: String): Path? {
val key = "$userId/$fileName"
val stream = objectStorage.retrieve<ObjectStorageEntry<*>>(key) // (1)
.map { obj: ObjectStorageEntry<*> -> obj.inputStream }
return if (stream.isPresent) {
val destination = File.createTempFile(userId, "temp").toPath()
Files.copy(stream.get(), destination, StandardCopyOption.REPLACE_EXISTING)
destination
} else {
null
}
}
1 | The retrieve operation returns an ObjectStorageEntry, from which you can get an InputStream .
There is also a getNativeEntry() method that gives you access to the cloud vendor-specific response object. |
Deleting files
public void deleteProfilePicture(String userId, String fileName) {
String key = userId + "/" + fileName;
objectStorage.delete(key); // (1)
}
void deleteProfilePicture(String userId, String fileName) {
String key = "${userId}/${fileName}"
objectStorage.delete(key) // (1)
}
open fun deleteProfilePicture(userId: String, fileName: String) {
val key = "$userId/$fileName"
objectStorage.delete(key) // (1)
}
1 | The delete operation returns the cloud vendor-specific delete response object in case you need it. |
4 Amazon S3
To use Amazon S3, you need the following dependency:
implementation("io.micronaut.objectstorage:micronaut-object-storage-aws")
<dependency>
<groupId>io.micronaut.objectstorage</groupId>
<artifactId>micronaut-object-storage-aws</artifactId>
</dependency>
Refer to the Micronaut AWS documentation for more information about credentials and region configuration.
The object storage specific configuration options available are:
Property | Type | Description |
---|---|---|
|
boolean |
Whether to enable or disable this object storage. |
|
java.lang.String |
The name of the AWS S3 bucket. |
For example:
src/main/resources/application-ec2.yml
micronaut.object-storage.aws.default.bucket=profile-pictures-bucket
micronaut:
object-storage:
aws:
default:
bucket: profile-pictures-bucket
[micronaut]
[micronaut.object-storage]
[micronaut.object-storage.aws]
[micronaut.object-storage.aws.default]
bucket="profile-pictures-bucket"
micronaut {
objectStorage {
aws {
'default' {
bucket = "profile-pictures-bucket"
}
}
}
}
{
micronaut {
object-storage {
aws {
default {
bucket = "profile-pictures-bucket"
}
}
}
}
}
{
"micronaut": {
"object-storage": {
"aws": {
"default": {
"bucket": "profile-pictures-bucket"
}
}
}
}
}
The concrete implementation of ObjectStorageOperations
is AwsS3Operations.
Advanced configuration
For configuration properties other than the specified above, you can add bean to your application that implements
BeanCreatedEventListener
. For example:
@Singleton
public class S3ClientBuilderCustomizer implements BeanCreatedEventListener<S3ClientBuilder> {
@Override
public S3ClientBuilder onCreated(@NonNull BeanCreatedEvent<S3ClientBuilder> event) {
return event.getBean()
.overrideConfiguration(c -> c.apiCallTimeout(Duration.of(60, ChronoUnit.SECONDS)));
}
}
See the guide for Use the Micronaut Object Storage API to Store Files in Amazon S3 to learn more. |
5 Azure Blob Storage
To use Azure Blob Storage, you need the following dependency:
implementation("io.micronaut.objectstorage:micronaut-object-storage-azure")
<dependency>
<groupId>io.micronaut.objectstorage</groupId>
<artifactId>micronaut-object-storage-azure</artifactId>
</dependency>
Refer to the Micronaut Azure documentation for more information about authentication options.
The object storage specific configuration options available are:
Property | Type | Description |
---|---|---|
|
boolean |
Whether to enable or disable this object storage. |
|
java.lang.String |
The blob container name. |
|
java.lang.String |
The blob service endpoint, in the format of https://{accountName}.blob.core.windows.net. |
For example:
src/main/resources/application-azure.yml
azure.credential.client-secret.client-id=<client-id>
azure.credential.client-secret.tenant-id=<tenant-id>
azure.credential.client-secret.secret=<secret>
micronaut.object-storage.azure.default.container=profile-pictures-container
micronaut.object-storage.azure.default.endpoint=https://my-account.blob.core.windows.net
azure:
credential:
client-secret:
client-id: <client-id>
tenant-id: <tenant-id>
secret: <secret>
micronaut:
object-storage:
azure:
default:
container: profile-pictures-container
endpoint: https://my-account.blob.core.windows.net
[azure]
[azure.credential]
[azure.credential.client-secret]
client-id="<client-id>"
tenant-id="<tenant-id>"
secret="<secret>"
[micronaut]
[micronaut.object-storage]
[micronaut.object-storage.azure]
[micronaut.object-storage.azure.default]
container="profile-pictures-container"
endpoint="https://my-account.blob.core.windows.net"
azure {
credential {
clientSecret {
clientId = "<client-id>"
tenantId = "<tenant-id>"
secret = "<secret>"
}
}
}
micronaut {
objectStorage {
azure {
'default' {
container = "profile-pictures-container"
endpoint = "https://my-account.blob.core.windows.net"
}
}
}
}
{
azure {
credential {
client-secret {
client-id = "<client-id>"
tenant-id = "<tenant-id>"
secret = "<secret>"
}
}
}
micronaut {
object-storage {
azure {
default {
container = "profile-pictures-container"
endpoint = "https://my-account.blob.core.windows.net"
}
}
}
}
}
{
"azure": {
"credential": {
"client-secret": {
"client-id": "<client-id>",
"tenant-id": "<tenant-id>",
"secret": "<secret>"
}
}
},
"micronaut": {
"object-storage": {
"azure": {
"default": {
"container": "profile-pictures-container",
"endpoint": "https://my-account.blob.core.windows.net"
}
}
}
}
}
The concrete implementation of ObjectStorageOperations
is AzureBlobStorageOperations.
Advanced configuration
For configuration properties other than the specified above, you can add bean to your application that implements
BeanCreatedEventListener
. For example:
@Singleton
public class BlobServiceClientBuilderCustomizer implements BeanCreatedEventListener<BlobServiceClientBuilder> {
@Override
public BlobServiceClientBuilder onCreated(@NonNull BeanCreatedEvent<BlobServiceClientBuilder> event) {
HttpPipelinePolicy noOp = (context, next) -> next.process();
return event.getBean().addPolicy(noOp);
}
}
6 Google Cloud Storage
To use Google Cloud Storage, you need the following dependency:
implementation("io.micronaut.objectstorage:micronaut-object-storage-gcp")
<dependency>
<groupId>io.micronaut.objectstorage</groupId>
<artifactId>micronaut-object-storage-gcp</artifactId>
</dependency>
Refer to the Micronaut GCP documentation for more information about configuring your GCP project.
The object storage specific configuration options available are:
Property | Type | Description |
---|---|---|
|
boolean |
Whether to enable or disable this object storage. |
|
java.lang.String |
The name of the Google Cloud Storage bucket. |
For example:
src/main/resources/application-gcp.yml
gcp.project-id=my-gcp-project
micronaut.object-storage.gcp.default.bucket=profile-pictures-bucket
gcp:
project-id: my-gcp-project
micronaut:
object-storage:
gcp:
default:
bucket: profile-pictures-bucket
[gcp]
project-id="my-gcp-project"
[micronaut]
[micronaut.object-storage]
[micronaut.object-storage.gcp]
[micronaut.object-storage.gcp.default]
bucket="profile-pictures-bucket"
gcp {
projectId = "my-gcp-project"
}
micronaut {
objectStorage {
gcp {
'default' {
bucket = "profile-pictures-bucket"
}
}
}
}
{
gcp {
project-id = "my-gcp-project"
}
micronaut {
object-storage {
gcp {
default {
bucket = "profile-pictures-bucket"
}
}
}
}
}
{
"gcp": {
"project-id": "my-gcp-project"
},
"micronaut": {
"object-storage": {
"gcp": {
"default": {
"bucket": "profile-pictures-bucket"
}
}
}
}
}
The concrete implementation of ObjectStorageOperations
is GoogleCloudStorageOperations.
Advanced configuration
For configuration properties other than the specified above, you can add bean to your application that implements
BeanCreatedEventListener
. For example:
@Singleton
public class StorageOptionsBuilderCustomizer implements BeanCreatedEventListener<StorageOptions.Builder> {
@Override
public StorageOptions.Builder onCreated(@NonNull BeanCreatedEvent<StorageOptions.Builder> event) {
return event.getBean()
.setTransportOptions(HttpTransportOptions.newBuilder().setConnectTimeout(60_000).build());
}
}
See the guide for Use the Micronaut Object Storage API to Store Files in Google Cloud Storage to learn more. |
7 Oracle Cloud Infrastructure (OCI) Object Storage
To use Oracle Cloud Infrastructure (OCI) Object Storage, you need the following dependency:
implementation("io.micronaut.objectstorage:micronaut-object-storage-oracle-cloud")
<dependency>
<groupId>io.micronaut.objectstorage</groupId>
<artifactId>micronaut-object-storage-oracle-cloud</artifactId>
</dependency>
Refer to the Micronaut Oracle Cloud documentation for more information about authentication options.
The object storage specific configuration options available are:
Property | Type | Description |
---|---|---|
|
boolean |
Whether to enable or disable this object storage. |
|
java.lang.String |
The name of the OCI Object Storage bucket. |
|
java.lang.String |
The OCI Object Storage namespace used. |
For example:
src/main/resources/application-oraclecloud.yml
oci.config.profile=DEFAULT
micronaut.object-storage.oracle-cloud.default.bucket=profile-pictures-bucket
micronaut.object-storage.oracle-cloud.default.namespace=MyNamespace
oci:
config:
profile: DEFAULT
micronaut:
object-storage:
oracle-cloud:
default:
bucket: profile-pictures-bucket
namespace: MyNamespace
[oci]
[oci.config]
profile="DEFAULT"
[micronaut]
[micronaut.object-storage]
[micronaut.object-storage.oracle-cloud]
[micronaut.object-storage.oracle-cloud.default]
bucket="profile-pictures-bucket"
namespace="MyNamespace"
oci {
config {
profile = "DEFAULT"
}
}
micronaut {
objectStorage {
oracleCloud {
'default' {
bucket = "profile-pictures-bucket"
namespace = "MyNamespace"
}
}
}
}
{
oci {
config {
profile = "DEFAULT"
}
}
micronaut {
object-storage {
oracle-cloud {
default {
bucket = "profile-pictures-bucket"
namespace = "MyNamespace"
}
}
}
}
}
{
"oci": {
"config": {
"profile": "DEFAULT"
}
},
"micronaut": {
"object-storage": {
"oracle-cloud": {
"default": {
"bucket": "profile-pictures-bucket",
"namespace": "MyNamespace"
}
}
}
}
}
The concrete implementation of ObjectStorageOperations
is OracleCloudStorageOperations
Advanced configuration
For configuration properties other than the specified above, you can add bean to your application that implements
BeanCreatedEventListener
. For example:
//See https://github.com/oracle/oci-java-sdk/blob/master/bmc-examples/src/main/java/ClientConfigurationTimeoutExample.java
@Singleton
public class ObjectStorageClientBuilderCustomizer implements BeanCreatedEventListener<ObjectStorageClient.Builder> {
public static final int CONNECTION_TIMEOUT_IN_MILLISECONDS = 25000;
public static final int READ_TIMEOUT_IN_MILLISECONDS = 35000;
@Override
public ObjectStorageClient.Builder onCreated(@NonNull BeanCreatedEvent<ObjectStorageClient.Builder> event) {
ClientConfiguration clientConfiguration =
ClientConfiguration.builder()
.connectionTimeoutMillis(CONNECTION_TIMEOUT_IN_MILLISECONDS)
.readTimeoutMillis(READ_TIMEOUT_IN_MILLISECONDS)
.build();
return event.getBean()
.configuration(clientConfiguration);
}
}
See the guide for Use the Micronaut Object Storage API to Store Files in Oracle Cloud Infrastructure (OCI) Object Storage to learn more. |
8 Local Storage
To use the local storage implementation (useful for tests), you need the following dependency:
testImplementation("io.micronaut.objectstorage:micronaut-object-storage-local")
<dependency>
<groupId>io.micronaut.objectstorage</groupId>
<artifactId>micronaut-object-storage-local</artifactId>
<scope>test</scope>
</dependency>
Then, simply define a local storage:
micronaut.object-storage.local.default.enabled=true
micronaut:
object-storage:
local:
default:
enabled: true
[micronaut]
[micronaut.object-storage]
[micronaut.object-storage.local]
[micronaut.object-storage.local.default]
enabled=true
micronaut {
objectStorage {
local {
'default' {
enabled = true
}
}
}
}
{
micronaut {
object-storage {
local {
default {
enabled = true
}
}
}
}
}
{
"micronaut": {
"object-storage": {
"local": {
"default": {
"enabled": true
}
}
}
}
}
When added to the classpath, LocalStorageOperations becomes the primary implementation of ObjectStorageOperations. |
By default, it will create a temporary folder to store the files, but you can configure it to use a specific folder:
Property | Type | Description |
---|---|---|
|
boolean |
Whether to enable or disable this object storage. |
|
java.nio.file.Path |
The path of the local storage. |
For example:
src/main/resources/application-test.yml
micronaut.object-storage.local.default.path=/tmp/my-object-storage
micronaut:
object-storage:
local:
default:
path: /tmp/my-object-storage
[micronaut]
[micronaut.object-storage]
[micronaut.object-storage.local]
[micronaut.object-storage.local.default]
path="/tmp/my-object-storage"
micronaut {
objectStorage {
local {
'default' {
path = "/tmp/my-object-storage"
}
}
}
}
{
micronaut {
object-storage {
local {
default {
path = "/tmp/my-object-storage"
}
}
}
}
}
{
"micronaut": {
"object-storage": {
"local": {
"default": {
"path": "/tmp/my-object-storage"
}
}
}
}
}
The concrete implementation of ObjectStorageOperations
is LocalStorageOperations.
9 Guides
See the following list of guides to learn more about working with Object Storage in the Micronaut Framework:
10 Repository
You can find the source code of this project in this repository: