How to Download Large Files from Amazon S3 Bucket in Spring Boot
Follow these steps to download large files from an Amazon S3 bucket in Spring Boot:
- Create a Spring Boot Project:
- Add Amazon SDK Dependencies:
- Add Configurations:
- Create an AmazonS3Client:
- Configure Asynchronous Processing:
- Create a Service:
- Create a Service Implementation:
- Create a Web Controller:
We assume you have a Spring Boot project set up and ready. If not, you can create one using Spring Initializr or your preferred approach.
Add the AWS Java SDK For Amazon S3 dependency to your Spring Boot project:
<dependency>
<groupid>com.amazonaws</groupid>
<artifactid>aws-java-sdk-s3</artifactid>
<version>1.12.556</version>
</dependency>
implementation group: 'com.amazonaws', name: 'aws-java-sdk-s3', version: '1.12.556'
To access S3 bucket from a Spring Boot project, you'll need to configure your project with AWS credentials. You can do this by providing your AWS access key and secret key, which can be set in your application.properties or application.yml file or loaded from environment variables. For example, in your application.properties file:
server.port=8080
aws.access-key = your-access-key
aws.access-secret-key = your-access-secret-key
aws.region = us-east-1
Create a bean for the AmazonS3 client in your Spring Boot application configuration class. You can use the @Configuration annotation to create a configuration class:
package com.example.app.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
@Configuration
public class AmazonS3Config {
private String awsAccessKey;
private String awsAccessSecretKey;
private String awsRegion;
public AmazonS3Config(@Value(value = "${aws.access-key}") String awsAccessKey,
@Value(value = "${aws.access-secret-key}") String awsAccessSecretKey,
@Value(value = "${aws.region}") String awsRegion) {
this.awsAccessKey = awsAccessKey;
this.awsAccessSecretKey = awsAccessSecretKey;
this.awsRegion = awsRegion;
}
public AWSStaticCredentialsProvider getAwsCredentialsProvider() {
BasicAWSCredentials awsCred =
new BasicAWSCredentials(this.awsAccessKey, this.awsAccessSecretKey);
return new AWSStaticCredentialsProvider(awsCred);
}
@Bean
public AmazonS3 getAmazonS3Client() {
return AmazonS3ClientBuilder.standard().withRegion(this.awsRegion)
.withCredentials(getAwsCredentialsProvider()).build();
}
}
It is recommended that you explicitly configure the TaskExecutor if the file to be downloaded is large and will take more than a minute to download. Here is the complete code for configuring the TaskExecutor with a request timeout of 3600000 milliseconds (60 minutes):
package com.example.app.config;
import java.util.concurrent.Callable;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.async.CallableProcessingInterceptor;
import org.springframework.web.context.request.async.TimeoutCallableProcessingInterceptor;
import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@EnableAsync
@EnableScheduling
public class AsyncConfig implements AsyncConfigurer {
@Override
@Bean(name = "taskExecutor")
public AsyncTaskExecutor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(15);
executor.setQueueCapacity(50);
return executor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new SimpleAsyncUncaughtExceptionHandler();
}
@Bean
public WebMvcConfigurer webMvcConfigurerConfigurer(AsyncTaskExecutor taskExecutor,
CallableProcessingInterceptor callableProcessingInterceptor) {
return new WebMvcConfigurer() {
@Override
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
configurer.setDefaultTimeout(3600000).setTaskExecutor(taskExecutor);
configurer.registerCallableInterceptors(callableProcessingInterceptor);
WebMvcConfigurer.super.configureAsyncSupport(configurer);
}
};
}
@Bean
public CallableProcessingInterceptor callableProcessingInterceptor() {
return new TimeoutCallableProcessingInterceptor() {
@Override
public <T> Object handleTimeout(NativeWebRequest request, Callable<T> task) throws Exception {
return super.handleTimeout(request, task);
}
};
}
}
This configuration class is used to enable and configure asynchronous processing in a Spring Boot application. It defines an AsyncTaskExecutor, sets up default timeouts, and provides exception handling for asynchronous tasks. Additionally, it configures Spring MVC to support asynchronous requests and handles timeouts for callable tasks.
Create a service interface named S3FileDownloadService with a method for downloading files from your S3 bucket:
package com.example.app.service;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import jakarta.servlet.http.HttpServletResponse;
public interface S3FileDownloadService {
StreamingResponseBody downloadFileFromS3(HttpServletResponse response, String fileId);
}
Create an implementation class that implements the S3FileDownloadService interface and handles the business logic. Let's create a class called S3FileDownloadServiceImpl:
package com.example.app.service.impl;
import java.io.InputStream;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.GetObjectRequest;
import com.amazonaws.services.s3.model.S3Object;
import com.example.app.service.S3FileDownloadService;
import jakarta.servlet.http.HttpServletResponse;
@Service
public class S3FileDownloadServiceImpl implements S3FileDownloadService {
private String bucketName = "my-test-bucket";
private String s3FolderName = "/myfolder/images/";
@Autowired
private AmazonS3 s3Client;
@Override
public StreamingResponseBody downloadFileFromS3(HttpServletResponse response, String fileId) {
// get filename from database by fileId
String filename = "admission.pdf";
// file location in S3
String fileLocationKey = s3FolderName + filename;
return outputStream -> {
S3Object s3Object = null;
InputStream inputStream = null;
/* Retrieve file as object from S3 */
s3Object = s3Client.getObject(new GetObjectRequest(bucketName, fileLocationKey));
inputStream = s3Object.getObjectContent();
long fileLength = s3Object.getObjectMetadata().getContentLength();
response.setContentLength((int) fileLength);
response.setHeader("Content-Disposition", "attachment; filename=" + filename);
int BUFFER_SIZE = 1024;
int bytesRead;
byte[] buffer = new byte[BUFFER_SIZE];
// Writing to output stream
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
// Closing all streams
if (inputStream != null) {
inputStream.close();
}
if (s3Object != null) {
s3Object.close();
}
if (response != null) {
response.getOutputStream().close();
}
};
}
}
Create a controller class named S3FileDownloadController. It will handle HTTP requests and interact with the S3FileDownloadService to download files from your S3 bucket:
package com.example.app.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import com.example.app.service.S3FileDownloadService;
import jakarta.servlet.http.HttpServletResponse;
@RestController
@RequestMapping(path = "/files")
public class S3FileDownloadController {
@Autowired
private S3FileDownloadService s3FileDownloadService;
@GetMapping(value = "/{fileId}/download")
public ResponseEntity<StreamingResponseBody> downloadFile(HttpServletResponse response,
@PathVariable(name = "fileId", required = true) String fileId) {
return ResponseEntity.ok(s3FileDownloadService.downloadFileFromS3(response, fileId));
}
}