<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>S3Client Archives - Codersee blog- Kotlin on the backend</title>
	<atom:link href="https://blog.codersee.com/tag/s3client/feed/" rel="self" type="application/rss+xml" />
	<link></link>
	<description>Kotlin &#38; Backend Tutorials - Learn Through Practice.</description>
	<lastBuildDate>Wed, 16 Apr 2025 04:49:36 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	

<image>
	<url>https://blog.codersee.com/wp-content/uploads/2025/04/cropped-codersee_logo_circle_2-32x32.png</url>
	<title>S3Client Archives - Codersee blog- Kotlin on the backend</title>
	<link></link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Spring Boot with AWS S3, S3Client and Kotlin</title>
		<link>https://blog.codersee.com/spring-boot-aws-s3-s3client-kotlin/</link>
					<comments>https://blog.codersee.com/spring-boot-aws-s3-s3client-kotlin/#comments</comments>
		
		<dc:creator><![CDATA[Piotr]]></dc:creator>
		<pubDate>Tue, 10 Sep 2024 06:00:00 +0000</pubDate>
				<category><![CDATA[Spring]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[S3 Object Storage]]></category>
		<category><![CDATA[S3Client]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=12009125</guid>

					<description><![CDATA[<p>A step-by-step guide on how to configure a Spring Boot Kotlin app to work with AWS S3 Object Storage using the S3Client. </p>
<p>The post <a href="https://blog.codersee.com/spring-boot-aws-s3-s3client-kotlin/">Spring Boot with AWS S3, S3Client and Kotlin</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Hello and welcome to the first article in a series dedicated to integrating a Spring Boot Kotlin app with <strong>AWS S3 Object Storage</strong>, in which I will show you how to <strong>properly set up the connection</strong> and <strong>make use of the S3Client</strong>. </p>



<p class="wp-block-paragraph">What can you expect today? </p>



<p class="wp-block-paragraph">Well, at the end of this tutorial, you will know precisely how to connect to the S3 service, resolve the most common issues related to that, as well as how to perform basic operations on <strong>buckets </strong>and <strong>objects </strong>using the <strong>S3Client approach</strong>. </p>



<p class="wp-block-paragraph">In the future content, we will work a bit with an asynchronous client, learn when to use S3Template instead, and learn how to write tests properly, so do not hesitate to subscribe to my <a href="https://codersee.com/newsletter/">newsletter</a> 😉</p>



<h2 class="wp-block-heading" id="h-video-tutorial">Video Tutorial</h2>



<p class="wp-block-paragraph">If you prefer <strong>video content</strong>, then check out my video that covers all three articles:</p>



<div style="text-align: center; width: 90%; margin-left: 5%;">
<a href="https://blog.codersee.com/spring-boot-aws-s3-s3client-kotlin/"><img decoding="async" src="https://blog.codersee.com/wp-content/plugins/wp-youtube-lyte/lyteCache.php?origThumbUrl=%2F%2Fi.ytimg.com%2Fvi%2FuTV9w1JehHM%2Fhqdefault.jpg" alt="YouTube Video"></a><br /><br /></p></div>



<p class="wp-block-paragraph">If you find this content useful,<strong> please leave a subscription</strong> 🙂</p>



<h2 class="wp-block-heading" id="h-project-setup">Project Setup </h2>



<p class="wp-block-paragraph">If you already have your Spring Boot project prepared, then feel free to skip this step. </p>



<p class="wp-block-paragraph">But if that is not the case, then let&#8217;s navigate together to the <a href="https://start.spring.io/" target="_blank" rel="noreferrer noopener">Spring Initializr</a> page and select the following settings: </p>



<figure class="wp-block-image aligncenter size-large"><img fetchpriority="high" decoding="async" width="1024" height="534" src="http://blog.codersee.com/wp-content/uploads/2024/09/spring_initializr_page-1024x534.png" alt="" class="wp-image-12009136" srcset="https://blog.codersee.com/wp-content/uploads/2024/09/spring_initializr_page-1024x534.png 1024w, https://blog.codersee.com/wp-content/uploads/2024/09/spring_initializr_page-300x156.png 300w, https://blog.codersee.com/wp-content/uploads/2024/09/spring_initializr_page-768x400.png 768w, https://blog.codersee.com/wp-content/uploads/2024/09/spring_initializr_page.png 1510w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">As we can see, nothing related to the AWS S3 yet, but we imported the <code>Spring Web</code> dependency so that we could expose REST endpoints to test. </p>



<p class="wp-block-paragraph">As always, please generate the project, extract it on your local, and import it to your favorite IDE. </p>



<h2 class="wp-block-heading" id="h-aws-s3-dependencies">AWS S3 Dependencies</h2>



<p class="wp-block-paragraph">It is worth mentioning that the <strong>S3Client</strong> is not a Spring Boot concept, but a class that comes from the AWS SDK. </p>



<p class="wp-block-paragraph"><strong>However</strong>, to make our lives easier when working with Spring, we can make use of the Spring Cloud AWS, which simplifies using AWS-managed services in a Spring and Spring Boot applications.</p>



<p class="wp-block-paragraph">To do so, let&#8217;s navigate to the <code>build.gradle.kts</code> and add the following: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">implementation("io.awspring.cloud:spring-cloud-aws-starter-s3:3.1.1")</pre>



<p class="wp-block-paragraph">Why do I pick it over the AWS Java SDK? </p>



<p class="wp-block-paragraph">Because it auto-configures various S3 integration-related components out of the box. Additionally, we can quickly configure a bunch of things using the <code>application.yaml</code>. And lastly, we are all Spring boyzz here, we don&#8217;t do things manually 🙂</p>



<h2 class="wp-block-heading" id="h-create-test-s3-bucket">Create Test S3 Bucket</h2>



<p class="wp-block-paragraph">Following, let&#8217;s navigate to the <a href="https://console.aws.amazon.com/" target="_blank" rel="noreferrer noopener">AWS Console</a> to prepare a test bucket. </p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Again, feel free to skip it if you are looking for Spring Boot details.</p>
</blockquote>



<p class="wp-block-paragraph">Then, let&#8217;s find the S3 (aka &#8220;Simple Storage Service&#8221;) in the search bar: </p>



<figure class="wp-block-image aligncenter size-full"><img decoding="async" width="1008" height="538" src="http://blog.codersee.com/wp-content/uploads/2024/09/1.png" alt="" class="wp-image-12009138" srcset="https://blog.codersee.com/wp-content/uploads/2024/09/1.png 1008w, https://blog.codersee.com/wp-content/uploads/2024/09/1-300x160.png 300w, https://blog.codersee.com/wp-content/uploads/2024/09/1-768x410.png 768w" sizes="(max-width: 1008px) 100vw, 1008px" /></figure>



<p class="wp-block-paragraph">Nextly, let&#8217;s hit the <code>Create Bucket</code> , provide a name for it, and leave the rest as is: </p>



<figure class="wp-block-image aligncenter size-full"><img decoding="async" width="831" height="692" src="http://blog.codersee.com/wp-content/uploads/2024/09/2.png" alt="" class="wp-image-12009139" srcset="https://blog.codersee.com/wp-content/uploads/2024/09/2.png 831w, https://blog.codersee.com/wp-content/uploads/2024/09/2-300x250.png 300w, https://blog.codersee.com/wp-content/uploads/2024/09/2-768x640.png 768w" sizes="(max-width: 831px) 100vw, 831px" /></figure>



<p class="wp-block-paragraph">If everything succeeded, then we should see our bucket on the list: </p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="435" src="http://blog.codersee.com/wp-content/uploads/2024/09/3-1024x435.png" alt="" class="wp-image-12009140" srcset="https://blog.codersee.com/wp-content/uploads/2024/09/3-1024x435.png 1024w, https://blog.codersee.com/wp-content/uploads/2024/09/3-300x127.png 300w, https://blog.codersee.com/wp-content/uploads/2024/09/3-768x326.png 768w, https://blog.codersee.com/wp-content/uploads/2024/09/3.png 1231w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">Excellent, at this point we can get back to our Spring Boot project :). </p>



<h2 class="wp-block-heading" id="h-test-s3client-connection">Test S3Client Connection</h2>



<p class="wp-block-paragraph">With all of that done, let&#8217;s figure out whether we can connect our local app with AWS using the <em>S3Client</em>. </p>



<p class="wp-block-paragraph">To do so, let&#8217;s create the <code>BucketController</code> class and introduce the GET endpoint: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@RestController
@RequestMapping("/buckets")
class BucketController(
  private val s3Client: S3Client
) {

  @GetMapping
  fun listBuckets(): List&lt;String> {
    val response = s3Client.listBuckets()

    return response.buckets()
      .mapIndexed { index, bucket ->
        "Bucket #${index + 1}: ${bucket.name()}"
      }
  }
}</pre>



<p class="wp-block-paragraph">As we can see, the above logic will be responsible for exposing the <code>GET /buckets</code> endpoint and returning a list of bucket names as &#8220;Bucket #N: some-name&#8221;. </p>



<p class="wp-block-paragraph">Moreover, the starter we are using <strong>automatically configures and registers an S3Client bean in the Spring Boot context</strong>. So, we simply inject that without any previous configuration. </p>



<p class="wp-block-paragraph">Anyway, less talkie-talkie, and let&#8217;s test the endpoint: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">curl --location --request GET 'http://localhost:8080/test' </pre>



<p class="wp-block-paragraph">And depending on our local environment, we <strong>get the 200 OK with a bucket name: </strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">[
    "Bucket #1: your-awesome-name"
]</pre>



<p class="wp-block-paragraph">Or the <strong>error related to <em>AwsCredentialsProviderChain</em>:</strong> </p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">software.amazon.awssdk.core.exception.SdkClientException: Unable to load credentials from any of the providers in the chain AwsCredentialsProviderChain(credentialsProviders=[SystemPropertyCredentialsProvider(), EnvironmentVariableCredentialsProvider(), WebIdentityTokenCredentialsProvider(), ProfileCredentialsProvider(profileName=default, profileFile=ProfileFile(sections=[])), ContainerCredentialsProvider(), InstanceProfileCredentialsProvider()]) : [SystemPropertyCredentialsProvider(): Unable to load credentials from system settings. Access key must be specified either via environment variable (AWS_ACCESS_KEY_ID) or system property (aws.accessKeyId)., EnvironmentVariableCredentialsProvider(): Unable to load credentials from system settings. Access key must be specified either via environment variable (AWS_ACCESS_KEY_ID) or system property (aws.accessKeyId)., WebIdentityTokenCredentialsProvider(): Either the environment variable AWS_WEB_IDENTITY_TOKEN_FILE or the javaproperty aws.webIdentityTokenFile must be set., ProfileCredentialsProvider(profileName=default, profileFile=ProfileFile(sections=[])): Profile file contained no credentials for profile &#8216;default&#8217;: ProfileFile(sections=[]), ContainerCredentialsProvider(): Cannot fetch credentials from container &#8211; neither AWS_CONTAINER_CREDENTIALS_FULL_URI or AWS_CONTAINER_CREDENTIALS_RELATIVE_URI environment variables are set., InstanceProfileCredentialsProvider(): Failed to load credentials from IMDS.]</p>
</blockquote>



<p class="wp-block-paragraph">Or even: </p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [software.amazon.awssdk.services.s3.S3ClientBuilder]: Factory method &#8216;s3ClientBuilder&#8217; threw exception with message: Unable to load region from any of the providers in the chain software.amazon.awssdk.regions.providers.DefaultAwsRegionProviderChain@15f35bc3: [software.amazon.awssdk.regions.providers.SystemSettingsRegionProvider@2bfb583b: Unable to load region from system settings. Region must be specified either via environment variable (AWS_REGION) or system property (aws.region)., software.amazon.awssdk.regions.providers.AwsProfileRegionProvider@7301eebe: No region provided in profile: default, software.amazon.awssdk.regions.providers.InstanceProfileRegionProvider@76a805b7: Unable to contact EC2 metadata service.]</p>
</blockquote>



<p class="wp-block-paragraph">So, let&#8217;s learn why it worked (or not🙂).</p>



<h2 class="wp-block-heading" id="h-configuring-aws-credentials">Configuring AWS Credentials </h2>



<p class="wp-block-paragraph">Long story short, the Spring Cloud AWS starter configures the <code>DefaultCredentialsProvider</code> that looks for credentials in the following order:</p>



<ol class="wp-block-list">
<li>Java System Properties &#8211; <code>aws.accessKeyId</code> and <code>aws.secretAccessKey</code></li>



<li>Environment Variables &#8211; <code>AWS_ACCESS_KEY_ID</code> and <code>AWS_SECRET_ACCESS_KEY</code></li>



<li>Web Identity Token credentials from system properties or environment variables</li>



<li>Credential profiles file at the default location (<code>~/.aws/ credentials</code>) shared by all AWS SDKs and the AWS CLI</li>



<li>Credentials delivered through the Amazon EC2 container service if <code>AWS_CONTAINER_CREDENTIALS_RELATIVE_URI</code> environment variable is set and the security manager has permission to access the variable,</li>



<li>Instance profile credentials delivered through the Amazon EC2 metadata service</li>
</ol>



<p class="wp-block-paragraph">And <strong>if you got 200 OK</strong>, but you don&#8217;t remember specifying anything on your local machine, then I am pretty sure that the <strong>number 4</strong>&nbsp; is the answer here 😉 </p>



<p class="wp-block-paragraph">The <code>.aws</code> folder inside the home directory is a default location for credentials, and you could have populated that unconsciously, for example <strong>when configuring AWS CLI with <code>aws configure</code>.</strong> And the <code>DefaultCredentialsProvider</code> was smart enough to use it without your knowledge. </p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">The autoconfiguration is a wonderful thing, but as we can see, it can backfire sometimes 🙂 </p>
</blockquote>



<p class="wp-block-paragraph">On the other hand, if none of these 6 satisfies you, then <strong>Spring Cloud AWS allows us to use the access keys, too.</strong>  </p>



<p class="wp-block-paragraph">To do so, the only thing we need to do is add the following to the <code>application.yaml</code>: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="yaml" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">spring:
  cloud:
    aws:
      credentials:
        access-key: your-access-key
        secret-key: your-secret-key</pre>



<h2 class="wp-block-heading" id="h-configure-region">Configure Region </h2>



<p class="wp-block-paragraph">OK, so at this point the problem related to the credentials should be gone. However, if we got the second error related to the region, then let&#8217;s see how it works internally, too. </p>



<p class="wp-block-paragraph">Well, when we take a look at the <code>DefaultAwsRegionProviderChain</code> docs, we will see that it looks for the region in this order:</p>



<ol class="wp-block-list">
<li>Check the <code>aws.region</code> system property for the region.</li>



<li>Check the <code>AWS_REGION</code> environment variable for the region.</li>



<li>Check the {user. home}/.aws/ credentials and {user. home}/.aws/config files for the region.</li>



<li>If running in EC2, check the EC2 metadata service for the region.</li>
</ol>



<p class="wp-block-paragraph">So, again, if we had the <code>.aws</code> credentials set, then this value came from there😉</p>



<p class="wp-block-paragraph">And as you might already have guessed, yes, we can update that in the properties, too:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="yaml" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">spring:
  cloud:
    aws:
      s3:
        region: us-east-1 #default is us-west-1</pre>



<p class="wp-block-paragraph">And at this point, when we rerun our application, then everything should be working, as expected. </p>



<h2 class="wp-block-heading" id="h-aws-s3client-operations">AWS S3Client Operations</h2>



<p class="wp-block-paragraph">So, with all of that done, we can finally take a look at a few AWS S3Client capabilities. Of course, we will not cover all possible scenarios, so if you have a bit more specific use case, then I recommend checking the Spring Cloud AWS / AWS SDK documentation. </p>



<h3 class="wp-block-heading" id="h-list-all-buckets">List All Buckets </h3>



<p class="wp-block-paragraph">Firstly, let&#8217;s get back to the <code>listBuckets</code> usage:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@GetMapping
fun listBuckets(): List&lt;String> {
  val response = s3Client.listBuckets()

  return response.buckets()
    .mapIndexed { index, bucket ->
      "Bucket #${index + 1}: ${bucket.name()}"
    }
}</pre>



<p class="wp-block-paragraph">Long story short, this function returns <code>ListBucketsResponse</code> that contains a list of all buckets owned by us. It is worth mentioning that we must have the <code>s3:ListAllMyBucket</code> permission. </p>



<p class="wp-block-paragraph">Nevertheless, <strong>I wanted to emphasize one, important thing</strong>. </p>



<p class="wp-block-paragraph">AWS SDK methods throw exceptions quite heavily. For example, the above may result in <code>SdkException</code>, <code>S3Exception</code>, or <code>SdkClientException</code> to be thrown. In a production-ready code, we must keep that in mind, handle them according to our needs and (if necessary) translate to appropriate HTTP status codes.</p>



<h3 class="wp-block-heading" id="h-create-new-s3-bucket">Create New S3 Bucket</h3>



<p class="wp-block-paragraph">Following, let&#8217;s expose a <code>POST /buckets</code> endpoint that will be used to create new buckets: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@PostMapping
fun createBucket(@RequestBody request: BucketRequest) {
  val createBucketRequest = CreateBucketRequest.builder()
    .bucket(request.bucketName)
    .build()

  s3Client.createBucket(createBucketRequest)
}

data class BucketRequest(val bucketName: String)</pre>



<p class="wp-block-paragraph">This time our function looks quite different- we must prepare the <code>CreateBucketRequest</code> that we pass to the <code>createBucket</code> function. </p>



<p class="wp-block-paragraph">And that is quite a common thing when dealing with AWS S3Client in Spring Boot. The SDK methods expect us to provide different objects of classes extending the <code>S3Request</code>, like <code>CreateBucketRequest</code>, <code>DeleteObjectRequest</code>, etc. </p>



<p class="wp-block-paragraph">What the <code>createBucket</code> does is pretty obvious, but we must be cautious about the bucket name, because the function may throw <code>BucketAlreadyExistsException</code>, or  <code>BucketAlreadyOwnedByYouException</code>. </p>



<p class="wp-block-paragraph">Of course, to test that, the only thing we need to do is to hit the endpoint: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">curl --location --request POST 'http://localhost:8080/buckets' \
--header 'Content-Type: application/json' \
--data-raw '{
    "bucketName": "your-awesome-name"
}'</pre>



<p class="wp-block-paragraph">And if everything is fine, a new bucket should be created. </p>



<h3 class="wp-block-heading" id="h-create-object-in-the-bucket">Create Object In The Bucket</h3>



<p class="wp-block-paragraph">So at this point, we know how to create buckets in AWS. Nevertheless, we use them to <strong>organise uploaded files</strong>. </p>



<p class="wp-block-paragraph">And it is the right time to learn how we can upload a file: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// we must add the typealias to avoid name clash for the @RequestBody annotation :) 
typealias PutObjectRequestBody = software.amazon.awssdk.core.sync.RequestBody

@RestController
@RequestMapping("/buckets")
class BucketController(
  private val s3Client: S3Client
) {

  @PostMapping("/{bucketName}/objects")
  fun createObject(@PathVariable bucketName: String, @RequestBody request: ObjectRequest) {
    val createObjectRequest = PutObjectRequest.builder()
      .bucket(bucketName)
      .key(request.objectName)
      .build()

    val fileContent = PutObjectRequestBody.fromString(request.content)

    s3Client.putObject(createObjectRequest, fileContent)
  }

  data class ObjectRequest(val objectName: String, val content: String)
}</pre>



<p class="wp-block-paragraph">As we can see, this time, we make use of the <code>putObject</code> and the <code>PutObjectRequest</code> (you see the pattern now 😉 ). </p>



<p class="wp-block-paragraph">Moreover, when preparing the request we must specify both the bucket name and our object key. </p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">curl --location --request POST 'http://localhost:8080/buckets/your-awesome-name/objects' \
--header 'Content-Type: application/json' \
--data-raw '{
    "objectName": "file-example.txt",
    "content": "My file content"
}'</pre>



<p class="wp-block-paragraph">As a result, a new text file named &#8220;file-example&#8221; with &#8220;My file content&#8221; in it should be created in the &#8220;your-awesome-name&#8221; bucket. </p>



<p class="wp-block-paragraph">Of course, this is not the only method of S3Client that allows us to upload files, and sometimes the multipart upload might be a better choice for our use case. </p>



<h3 class="wp-block-heading" id="h-list-objects-from-the-bucket">List Objects From The Bucket</h3>



<p class="wp-block-paragraph">Nextly, let&#8217;s take a look what is the content of our bucket:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@GetMapping("/{bucketName}/objects")
fun listObjects(@PathVariable bucketName: String): List&lt;String> {
  val listObjectsRequest = ListObjectsRequest.builder()
    .bucket(bucketName)
    .build()

  val response = s3Client.listObjects(listObjectsRequest)

  return response.contents()
    .map { s3Object -> s3Object.key() }
}</pre>



<p class="wp-block-paragraph">Similarly, we build the <code>ListObjectsRequest</code> instance, we perform the request using the <code>listObjects</code> and return an array with item names.</p>



<p class="wp-block-paragraph">And this time, when we check with the following query:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">curl --location --request GET 'http://localhost:8080/buckets/your-awesome-name/objects'</pre>



<p class="wp-block-paragraph">We should get the 200 OK with the array: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">[
    "file-example.txt"
]</pre>



<h3 class="wp-block-heading" id="h-fetch-the-object-from-s3-bucket">Fetch The Object From S3 Bucket</h3>



<p class="wp-block-paragraph">And although listing objects might be sometimes useful, I am pretty sure you would be more often interested in getting the actual object with a key: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@GetMapping("/{bucketName}/objects/{objectName}")
fun getObject(@PathVariable bucketName: String, @PathVariable objectName: String): String {
  val getObjectRequest = GetObjectRequest.builder()
    .bucket(bucketName)
    .key(objectName)
    .build()

  val response = s3Client.getObjectAsBytes(getObjectRequest)

  return response.asString(UTF_8)
}</pre>



<p class="wp-block-paragraph">Just like in the previous examples, we prepare the request with bucket name and object key, invoke the <code>getObjectAsBytes</code> and this time we print out the content to the output: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">curl --location --request GET 'http://localhost:8080/buckets/your-awesome-name/objects/file-example.txt'

# Response: 
"My file content"</pre>



<p class="wp-block-paragraph">Of course, a friendly reminder that the AWS SDK throws the exceptions, and if the file does not exist, we will get the <code>NoSuchKeyException</code>. </p>



<h3 class="wp-block-heading" id="h-delete-s3-bucket">Delete S3 Bucket</h3>



<p class="wp-block-paragraph">As the last step, let&#8217;s take a look at the logic necessary to delete a bucket: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@DeleteMapping("/{bucketName}")
fun deleteBucket(@PathVariable bucketName: String) {
  val listObjectsRequest = ListObjectsRequest.builder()
    .bucket(bucketName)
    .build()

  val listObjectsResponse = s3Client.listObjects(listObjectsRequest)

  val allObjectsIdentifiers = listObjectsResponse.contents()
    .map { s3Object ->
      ObjectIdentifier.builder()
        .key(s3Object.key())
        .build()
    }

  val del = Delete.builder()
    .objects(allObjectsIdentifiers)
    .build()

  val deleteObjectsRequest = DeleteObjectsRequest.builder()
    .bucket(bucketName)
    .delete(del)
    .build()

  s3Client.deleteObjects(deleteObjectsRequest)


  val deleteBucketRequest = DeleteBucketRequest.builder()
    .bucket(bucketName)
    .build()

  s3Client.deleteBucket(deleteBucketRequest)
}</pre>



<p class="wp-block-paragraph">As we can see, this time, we must perform our actions in a few steps.</p>



<p class="wp-block-paragraph">Why? </p>



<p class="wp-block-paragraph">The reason is simple: </p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: software.amazon.awssdk.services.s3.model.S3Exception: The bucket you tried to delete is not empty (Service: S3, Status Code: 409&#8230;</p>
</blockquote>



<p class="wp-block-paragraph">As we can see, the message above is pretty descriptive. Simply said- <strong>we cannot delete a bucket that is not empty.</strong> </p>



<p class="wp-block-paragraph">So, our function utilizes the <code>listObjects</code>, so that we can get keys to delete, the <code>deleteObjects</code> to actually delete them, and <code>deleteBucket</code> to get rid of the bucket. </p>



<h2 class="wp-block-heading" id="h-summary">Summary </h2>



<p class="wp-block-paragraph">And that&#8217;s all for this first tutorial on how to integrate your Spring Boot application with AWS and <strong>make use of the S3Client to work with AWS S3</strong>. In the upcoming articles in the series, we will expand our knowledge to work with S3 even more efficiently. </p>



<p class="wp-block-paragraph">Of course, I highly encourage you to take a look at the remaining articles in this series: </p>



<ul class="wp-block-list">
<li><a href="https://blog.codersee.com/test-spring-boot-aws-s3-with-localstack-and-testcontainers/">#3 Test Spring Boot AWS S3 with Localstack and Testcontainers</a></li>
</ul>



<p class="wp-block-paragraph">For the source code, as always, please refer to <a href="https://github.com/codersee-blog/spring-boot-3-kotlin-s3Client" target="_blank" rel="noreferrer noopener">this GitHub repository</a>. </p>
<p>The post <a href="https://blog.codersee.com/spring-boot-aws-s3-s3client-kotlin/">Spring Boot with AWS S3, S3Client and Kotlin</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.codersee.com/spring-boot-aws-s3-s3client-kotlin/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
	</channel>
</rss>

<!--
Performance optimized by W3 Total Cache. Learn more: https://www.boldgrid.com/w3-total-cache/?utm_source=w3tc&utm_medium=footer_comment&utm_campaign=free_plugin

Page Caching using Disk: Enhanced 

Served from: blog.codersee.com @ 2026-05-29 16:09:24 by W3 Total Cache
-->