How to Download Multiple Files from Amazon S3 Bucket in Spring Boot

In this example, we will show you how to download multiple files from an Amazon S3 bucket and zip them for download using a REST API in Spring Boot.

Follow the steps below to complete this example:

Adding Dependency

To upload files to S3, you will need to add the AWS Java SDK For Amazon S3 dependency to your application. Here is the Maven repository for Amazon S3 SDK for Java.

Gradle Dependency

Add the following dependency to the build.gradle file:


implementation group: 'com.amazonaws', name: 'aws-java-sdk-s3', version: '1.12.158'
Maven Dependency

Add the following dependency to the pom.xml file:



Adding Configurations

First, add the following credentials to your resources/application.properties configuration file:


server.port=8080

aws.access-key = your aws access key here
aws.access-secret-key = your aws secret key here
aws.region = us-east-1

Creating Configuration Classes

Create a configuration Java class for the AmazonS3 Client:

spring-boot-s3-sample/src/main/java/com/s3/sample/demo/config/AwsConfig.java

package com.s3.sample.demo.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 AwsConfig {

	private String awsAccessKey;
	private String awsAccessSecretKey;
	private String awsRegion;

	public AwsConfig(@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();
	}
}

Next, create AsyncConfig class and configure the TaskExecutor. 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):

spring-boot-s3-sample/src/main/java/com/s3/sample/demo/config/AsyncConfig.java

package com.s3.sample.demo.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);
			}
		};
	}
}

Creating Service

Create a service class with a method to download multiple files from an Amazon S3 bucket:

spring-boot-s3-sample/src/main/java/com/s3/sample/demo/service/S3MultipleDownloadServiceExample.java

package com.s3.sample.demo.service;

import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import javax.servlet.http.HttpServletResponse;
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;

@Service
public class S3MultipleDownloadServiceExample {
	
	private String bucketName = "my-test-bucket";

	private String s3FolderName = "/myfolder/images/";

	@Autowired
	private AmazonS3 s3Client;

	public StreamingResponseBody downloadMultipleFilesFromS3(HttpServletResponse response, List<String> fileIds) {
		// get file names by fileIds from your database
		List<String> filenameList = Arrays.asList("file1.pdf", "file2.png", "file3.pdf", "file4.mp4");

		int BUFFER_SIZE = 1024;

		StreamingResponseBody streamResponseBody = out -> {

			final ZipOutputStream zipOutputStream = new ZipOutputStream(response.getOutputStream());
			ZipEntry zipEntry = null;
			InputStream inputStream = null;
			S3Object s3Object = null;
			
			try {
				for (String filename : filenameList) {
					// file location in S3
					String fileLocationKey = s3FolderName + filename;
					
					/* Retrieve file as object from S3 */
					s3Object = s3Client.getObject(new GetObjectRequest(bucketName, fileLocationKey));
										
					zipEntry = new ZipEntry(filename);

					inputStream = s3Object.getObjectContent();

					zipOutputStream.putNextEntry(zipEntry);
					byte[] bytes = new byte[BUFFER_SIZE];
					int length;
					while ((length = inputStream.read(bytes)) >= 0) {
						zipOutputStream.write(bytes, 0, length);
					}

				}
				// set zip size in response
				response.setContentLength((int) (zipEntry != null ? zipEntry.getSize() : 0));
			} catch (IOException e) {
				e.printStackTrace();
			} finally {
				if (inputStream != null) {
					inputStream.close();
				}
				if (zipOutputStream != null) {
					zipOutputStream.close();
				}
			}

		};

		response.setContentType("application/zip");
		response.setHeader("Content-Disposition", "attachment; filename=example.zip");
		response.addHeader("Pragma", "no-cache");
		response.addHeader("Expires", "0");
		
		return streamResponseBody;
	}

}

Creating Web Controller

Create a controller with a REST API endpoint that downloads multiple files from S3 and allows us to download as a zipped file:

spring-boot-s3-sample/src/main/java/com/s3/sample/demo/controller/AmazonS3MultipleDownloadExampleController.java

package com.s3.sample.demo.controller;

import java.util.List;
import javax.servlet.http.HttpServletResponse;
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.s3.sample.demo.service.S3MultipleDownloadServiceExample;

@RestController
@RequestMapping(value = "/api/files")
public class AmazonS3MultipleDownloadExampleController {

	@Autowired
	private S3MultipleDownloadServiceExample s3MultipleDownloadServiceExample;

	@GetMapping(value = "/{fileIds}/download")
	public ResponseEntity<StreamingResponseBody> downloadMultipleFilesAsZip(HttpServletResponse response,
			@PathVariable(name = "fileIds", required = true) List<String> fileIds) {

		return ResponseEntity.ok(s3MultipleDownloadServiceExample.downloadMultipleFilesFromS3(response, fileIds));
	}
}

The code is complete. You can run and test it now.