How I’m I going to store the files (aka objects) in my project? Well, there’s no right answer. Either the file could be stored in the database itself as a BLOB or file can be copied to a folder inside the server and reference can be stored in the database. But with the rise of cloud computing services like object storage have taken a lead which can be used to achieve the same goal in a better way.
Welcome to another Spring Boot tutorial. For this tutorial I’ll be assuming that you have a basic understanding of Spring Boot and AWS. If you’re totally new to Spring Boot check out my starter tutorial Setting up your first Spring Boot app on Medium. Amazon S3 being one of the widely used object storage service, let’s see how we can integrate it with our Spring Boot project.

Setting up your first Spring Boot app | by Afrar Malakooth | Medium
Afrar Malakooth ・ ・
mmafrar.Medium
When your web or mobile application grows you’ll need to work with images, documents etc. apart from just plain text. And you need resilient and highly available mechanism to store and retrieve those files. That’s where Amazon S3 comes into play. In order to get started with this tutorial, we’ll head onto AWS Management Console and create an IAM user and a S3 bucket initially.
Login as root user or IAM user and navigate to IAM dashboard. On the left pane click on Users and select Add user. I have given my-test-user as the username and chosen only programmatic access. Press next and select attach existing policies directly. Search for AmazonS3FullAccess and attach it to the user. At the time of writing this article you’ll be presented with a review window as shown in the above. Proceed with creating the user and copy the access key id and secret.
Then navigate to S3 dashboard and click on create bucket. I have given my-test-s3-bucket-123456 as the bucket name and us-east-1 as the region. For this tutorial I think you can leave the rest of the settings with default values and click on create bucket. We’re done with AWS, let’s switch to Spring Boot.
# File storage configuration
access.key.id=<your_aws_access_key_id>
access.key.secret=<your_aws_access_key_secret>
s3.region.name=us-east-1
s3.bucket.name=my-test-s3-bucket-123456
Head on to your Spring Boot project and add the above properties to your application.properties file. Then add the below dependencies to your build.gradle file.
implementation platform('com.amazonaws:aws-java-sdk-bom:1.11.837')
implementation 'com.amazonaws:aws-java-sdk-s3'
Then we need to add a configuration class to setup Amazon S3 client. Refer the below Gist and create your file accordingly.
package com.example.demo; | |
import com.amazonaws.auth.AWSStaticCredentialsProvider; | |
import com.amazonaws.auth.BasicAWSCredentials; | |
import com.amazonaws.services.s3.AmazonS3; | |
import com.amazonaws.services.s3.AmazonS3ClientBuilder; | |
import org.springframework.beans.factory.annotation.Value; | |
import org.springframework.context.annotation.Bean; | |
import org.springframework.context.annotation.Configuration; | |
@Configuration | |
public class FileConfiguration { | |
@Value("${access.key.id}") | |
private String accessKeyId; | |
@Value("${access.key.secret}") | |
private String accessKeySecret; | |
@Value("${s3.region.name}") | |
private String s3RegionName; | |
@Bean | |
public AmazonS3 getAmazonS3Client() { | |
final BasicAWSCredentials basicAWSCredentials = new BasicAWSCredentials(accessKeyId, accessKeySecret); | |
// Get Amazon S3 client and return the S3 client object | |
return AmazonS3ClientBuilder | |
.standard() | |
.withCredentials(new AWSStaticCredentialsProvider(basicAWSCredentials)) | |
.withRegion(s3RegionName) | |
.build(); | |
} | |
} |
After you’re done with the configuration class, we need to create a service class where the logic to interact with Amazon S3 resides.
package com.example.demo; | |
import com.amazonaws.AmazonServiceException; | |
import com.amazonaws.services.s3.AmazonS3; | |
import com.amazonaws.services.s3.model.PutObjectRequest; | |
import com.amazonaws.services.s3.model.S3ObjectInputStream; | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
import org.springframework.beans.factory.annotation.Autowired; | |
import org.springframework.beans.factory.annotation.Value; | |
import org.springframework.scheduling.annotation.Async; | |
import org.springframework.stereotype.Service; | |
import org.springframework.web.multipart.MultipartFile; | |
import java.io.File; | |
import java.io.FileOutputStream; | |
import java.io.IOException; | |
import java.nio.file.Files; | |
import java.time.LocalDateTime; | |
@Service | |
public class FileService { | |
private static final Logger LOG = LoggerFactory.getLogger(FileService.class); | |
@Autowired | |
private AmazonS3 amazonS3; | |
@Value("${s3.bucket.name}") | |
private String s3BucketName; | |
private File convertMultiPartFileToFile(final MultipartFile multipartFile) { | |
final File file = new File(multipartFile.getOriginalFilename()); | |
try (final FileOutputStream outputStream = new FileOutputStream(file)) { | |
outputStream.write(multipartFile.getBytes()); | |
} catch (IOException e) { | |
LOG.error("Error {} occurred while converting the multipart file", e.getLocalizedMessage()); | |
} | |
return file; | |
} | |
// @Async annotation ensures that the method is executed in a different thread | |
@Async | |
public S3ObjectInputStream findByName(String fileName) { | |
LOG.info("Downloading file with name {}", fileName); | |
return amazonS3.getObject(s3BucketName, fileName).getObjectContent(); | |
} | |
@Async | |
public void save(final MultipartFile multipartFile) { | |
try { | |
final File file = convertMultiPartFileToFile(multipartFile); | |
final String fileName = LocalDateTime.now() + "_" + file.getName(); | |
LOG.info("Uploading file with name {}", fileName); | |
final PutObjectRequest putObjectRequest = new PutObjectRequest(s3BucketName, fileName, file); | |
amazonS3.putObject(putObjectRequest); | |
Files.delete(file.toPath()); // Remove the file locally created in the project folder | |
} catch (AmazonServiceException e) { | |
LOG.error("Error {} occurred while uploading file", e.getLocalizedMessage()); | |
} catch (IOException ex) { | |
LOG.error("Error {} occurred while deleting temporary file", ex.getLocalizedMessage()); | |
} | |
} | |
} |
Once the service class is ready we need to have a controller class which contains the APIs to be exposed to the frontend.
package com.example.demo; | |
import com.example.demo.FileService; | |
import org.springframework.beans.factory.annotation.Autowired; | |
import org.springframework.core.io.InputStreamResource; | |
import org.springframework.http.CacheControl; | |
import org.springframework.http.HttpStatus; | |
import org.springframework.http.ResponseEntity; | |
import org.springframework.web.bind.annotation.*; | |
import org.springframework.web.multipart.MultipartFile; | |
import java.util.Map; | |
@RestController | |
@RequestMapping("/api/v1/files") | |
@CrossOrigin(origins = "*", maxAge = 3600) | |
public class FileController { | |
private static final String MESSAGE_1 = "Uploaded the file successfully"; | |
private static final String FILE_NAME = "fileName"; | |
@Autowired | |
FileService fileService; | |
@GetMapping | |
public ResponseEntity<Object> findByName(@RequestBody(required = false) Map<String, String> params) { | |
return ResponseEntity | |
.ok() | |
.cacheControl(CacheControl.noCache()) | |
.header("Content-type", "application/octet-stream") | |
.header("Content-disposition", "attachment; filename=\"" + params.get(FILE_NAME) + "\"") | |
.body(new InputStreamResource(fileService.findByName(params.get(FILE_NAME)))); | |
} | |
@PostMapping | |
public ResponseEntity<Object> save(@RequestParam("file") MultipartFile multipartFile) { | |
fileService.save(multipartFile); | |
return new ResponseEntity<>(MESSAGE_1, HttpStatus.OK); | |
} | |
} |
We’re all set now, Happy Coding! Below is a DEV Community video I have recently published and if you’re interested check out my previous Medium story on Configuring multiple data sources with Spring Boot 2 and Spring Data JPA.
Top comments (0)