<?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>Spring Archives - Codersee blog- Kotlin on the backend</title>
	<atom:link href="https://blog.codersee.com/category/spring/feed/" rel="self" type="application/rss+xml" />
	<link>https://blog.codersee.com/category/spring/</link>
	<description>Kotlin &#38; Backend Tutorials - Learn Through Practice.</description>
	<lastBuildDate>Thu, 17 Apr 2025 09:17:14 +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>Spring Archives - Codersee blog- Kotlin on the backend</title>
	<link>https://blog.codersee.com/category/spring/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Spring for GraphQL with Kotlin Coroutines</title>
		<link>https://blog.codersee.com/spring-graphql-kotlin-coroutines/</link>
					<comments>https://blog.codersee.com/spring-graphql-kotlin-coroutines/#respond</comments>
		
		<dc:creator><![CDATA[Piotr]]></dc:creator>
		<pubDate>Sat, 12 Apr 2025 12:10:34 +0000</pubDate>
				<category><![CDATA[Spring]]></category>
		<category><![CDATA[GraphQL]]></category>
		<category><![CDATA[Kotlin coroutines]]></category>
		<category><![CDATA[Spring Boot]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=19012915</guid>

					<description><![CDATA[<p>In this tutorial, we will learn how to expose GraphQL APIs using Spring for GraphQL and Kotlin coroutines.</p>
<p>The post <a href="https://blog.codersee.com/spring-graphql-kotlin-coroutines/">Spring for GraphQL with Kotlin Coroutines</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>At the end of this article, you will know precisely how to create a <strong>Spring Boot GraphQL</strong> application that exposes <strong>queries</strong> and <strong>mutations</strong> with the help of Kotlin <strong>suspended functions</strong> and <strong>Flows</strong>. </p>



<p>Long story short, we will see: </p>



<ul class="wp-block-list">
<li>how to prepare a GraphQL schema,</li>



<li>what imports are necessary to utilize suspended functions,</li>



<li>how to expose queries, mutations, and map schema with annotated controllers,</li>



<li>exception handling with <code>GraphQlExceptionHandler</code> </li>
</ul>



<h2 class="wp-block-heading" id="h-create-project">Create Project </h2>



<p>As the first step, let&#8217;s prepare a brand new project. </p>



<p>To do so, let&#8217;s navigate to the <a href="https://start.spring.io/" target="_blank" rel="noreferrer noopener">Spring Initializr</a> page and select the following imports: </p>



<figure class="wp-block-image size-large"><img fetchpriority="high" decoding="async" width="1024" height="537" src="http://blog.codersee.com/wp-content/uploads/2025/04/spring_for_graphql_webflux_spring_initializr_page_screenshot-1024x537.png" alt="Image is a screenshot from Spring Initializr page and shows necessary spring configs for the tutorial, among others, Spring Reactive Web and Spring for GraphQL" class="wp-image-19012919" srcset="https://blog.codersee.com/wp-content/uploads/2025/04/spring_for_graphql_webflux_spring_initializr_page_screenshot-1024x537.png 1024w, https://blog.codersee.com/wp-content/uploads/2025/04/spring_for_graphql_webflux_spring_initializr_page_screenshot-300x157.png 300w, https://blog.codersee.com/wp-content/uploads/2025/04/spring_for_graphql_webflux_spring_initializr_page_screenshot-768x403.png 768w, https://blog.codersee.com/wp-content/uploads/2025/04/spring_for_graphql_webflux_spring_initializr_page_screenshot.png 1530w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>The project <strong>metadata</strong> are totally up to you, but when it comes to the <strong>dependencies</strong>, we must select the <em>Spring Reactive Web</em> and <em>Spring for GraphQL</em>.</p>



<p>Thanks to the first one, Spring Boot configures a <strong>reactive web stack</strong> based on <strong>Project Reactor</strong> and <strong>Netty</strong> instead of Tomcat. And given we want to work with GraphQL <strong>and </strong>Kotlin coroutines, then this is a must-have for us. </p>



<p>The second provides support for Spring applications built on GraphQL Java. Moreover, it is the <strong>successor</strong> of the GraphQL Java Spring project from the GraphQL Java team.</p>



<p>Lastly, let&#8217;s download the zip package, extract and import it to our IDE. </p>



<h2 class="wp-block-heading" id="h-define-graphql-schema">Define GraphQL Schema</h2>



<h3 class="wp-block-heading" id="h-what-is-graphql-schema">What is GraphQL Schema?</h3>



<p>If you have ever worked with GraphQL before, then you know that compared to the REST, everything starts with <strong>schema definition</strong>.</p>



<p>If you haven&#8217;t, then <strong>GraphQL schema</strong> is a file typically written with <strong>Schema Definition Language (SDL)</strong> that describes how our API clients can interact with our server. To be more specific, inside it, we define the structure of returned data, as well as how consumers can fetch or send us the payloads. </p>



<p>And even though it may sound like a GraphQL substitute for OpenAPI specs, it is something more. In REST, OpenAPI docs are a nice addition to our project. In GraphQL, our services continuously utilize the schema definition to validate and execute requests against it. </p>



<p>And if you would like to learn more about GraphQL schema, then check out the <a href="https://graphql.org/learn/schema/">official documentation</a> later. But for now, let&#8217;s focus on the Spring / GraphQL / Kotlin combination 😉 </p>



<h3 class="wp-block-heading" id="h-schema-graphql-in-spring">schema.graphql in Spring </h3>



<p>As the first step, let&#8217;s head to the <code>resources &gt; graphql</code> directory in our project, and let&#8217;s insert the <code>schema.graphql</code> file:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#24292e"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="type Query {
  article(id: ID!): Article
  articles: [Article!]!
}

type Mutation {
  createArticle(input: CreateArticleInput!): Article!
  addComment(articleId: ID!, input: AddCommentInput!): Comment
}

input CreateArticleInput {
  title: String!
  content: String!
  userId: ID!
}

input AddCommentInput {
  userId: String!
  content: String!
}

type User {
  id: ID!
  name: String!
}

type Article {
  id: ID!
  title: String!
  content: String!
  author: User!
  comments: [Comment!]!
  createdAt: String!
}

type Comment {
  id: ID!
  content: String!
  author: User
}" style="color:#e1e4e8;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki github-dark" style="background-color: #24292e" tabindex="0"><code><span class="line"><span style="color: #F97583">type</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">Query</span><span style="color: #E1E4E8"> {</span></span>
<span class="line"><span style="color: #E1E4E8">  article(id: ID!): Article</span></span>
<span class="line"><span style="color: #E1E4E8">  articles: [Article!]!</span></span>
<span class="line"><span style="color: #E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F97583">type</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">Mutation</span><span style="color: #E1E4E8"> {</span></span>
<span class="line"><span style="color: #E1E4E8">  createArticle(input: CreateArticleInput!): Article!</span></span>
<span class="line"><span style="color: #E1E4E8">  addComment(articleId: ID!, input: AddCommentInput!): Comment</span></span>
<span class="line"><span style="color: #E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #E1E4E8">input CreateArticleInput {</span></span>
<span class="line"><span style="color: #E1E4E8">  </span><span style="color: #B392F0">title</span><span style="color: #E1E4E8">: String</span><span style="color: #F97583">!</span></span>
<span class="line"><span style="color: #E1E4E8">  </span><span style="color: #B392F0">content</span><span style="color: #E1E4E8">: String</span><span style="color: #F97583">!</span></span>
<span class="line"><span style="color: #E1E4E8">  </span><span style="color: #B392F0">userId</span><span style="color: #E1E4E8">: </span><span style="color: #79B8FF">ID</span><span style="color: #F97583">!</span></span>
<span class="line"><span style="color: #E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #E1E4E8">input AddCommentInput {</span></span>
<span class="line"><span style="color: #E1E4E8">  </span><span style="color: #B392F0">userId</span><span style="color: #E1E4E8">: String</span><span style="color: #F97583">!</span></span>
<span class="line"><span style="color: #E1E4E8">  </span><span style="color: #B392F0">content</span><span style="color: #E1E4E8">: String</span><span style="color: #F97583">!</span></span>
<span class="line"><span style="color: #E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F97583">type</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">User</span><span style="color: #E1E4E8"> {</span></span>
<span class="line"><span style="color: #E1E4E8">  id: ID!</span></span>
<span class="line"><span style="color: #E1E4E8">  name: String!</span></span>
<span class="line"><span style="color: #E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F97583">type</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">Article</span><span style="color: #E1E4E8"> {</span></span>
<span class="line"><span style="color: #E1E4E8">  id: ID!</span></span>
<span class="line"><span style="color: #E1E4E8">  title: String!</span></span>
<span class="line"><span style="color: #E1E4E8">  content: String!</span></span>
<span class="line"><span style="color: #E1E4E8">  author: User!</span></span>
<span class="line"><span style="color: #E1E4E8">  comments: [Comment!]!</span></span>
<span class="line"><span style="color: #E1E4E8">  createdAt: String!</span></span>
<span class="line"><span style="color: #E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F97583">type</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">Comment</span><span style="color: #E1E4E8"> {</span></span>
<span class="line"><span style="color: #E1E4E8">  id: ID!</span></span>
<span class="line"><span style="color: #E1E4E8">  content: String!</span></span>
<span class="line"><span style="color: #E1E4E8">  author: User</span></span>
<span class="line"><span style="color: #E1E4E8">}</span></span></code></pre></div>



<p>In Spring Boot, this file is registered automatically as our schema definition. </p>



<p>And as we can see, we defined 5 things: </p>



<ul class="wp-block-list">
<li><strong>queries</strong>&#8211; how a client reads data. Every GraphQL schema <strong>must </strong>support them,</li>



<li><strong>mutations</strong>&#8211; how a client modifies data,     </li>



<li><strong>inputs</strong>&#8211; used to pass structured arguments to our mutations and queries,</li>



<li><strong>types</strong>&#8211; establishing the structure of data we return,</li>



<li><strong>scalars</strong>&#8211; like <code>String</code>, or <code>ID</code> (but also <code>Int</code>, <code>Float</code>, <code>Boolean</code>), describing returned fields.</li>
</ul>



<p>Moreover, we can see the exclamation mark- <code>!</code>. In contrast to Kotlin, in GraphQL, we must <strong>explicitly define non-nullable types.</strong> Meaning, that <code>[Comment]</code> describes a nullable array of nullable <code>Comment</code> type (<code>Array&lt;Comment?&gt;?</code>), whereas <code>[Comment!]!</code> is a not null array with not null items.</p>



<h3 class="wp-block-heading" id="h-spring-graphql-schema-inspection">Spring GraphQL Schema Inspection</h3>



<p>Following, let&#8217;s rerun our application. </p>



<p>As a result, we should get the following in logs: </p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="o.s.b.a.g.GraphQlAutoConfiguration: GraphQL schema inspection:
Unmapped fields: {Query=[article, articles], Mutation=[createArticle, addComment]}
Unmapped registrations: {}
Unmapped arguments: {}
Skipped types: []" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #d8dee9ff">o.s.b.a.g.GraphQlAutoConfiguration: GraphQL schema inspection:</span></span>
<span class="line"><span style="color: #d8dee9ff">Unmapped fields: {Query=[article, articles], Mutation=[createArticle, addComment]}</span></span>
<span class="line"><span style="color: #d8dee9ff">Unmapped registrations: {}</span></span>
<span class="line"><span style="color: #d8dee9ff">Unmapped arguments: {}</span></span>
<span class="line"><span style="color: #d8dee9ff">Skipped types: []</span></span></code></pre></div>



<p>As we can see, Spring Boot verifies the schema we defined every time we start the application. And the above message simply says that <strong>we do not have handler methods for our definition.</strong></p>



<p>Let&#8217;s fix that then 😉</p>



<h2 class="wp-block-heading" id="h-create-models">Create Models </h2>



<p>As the next step, let&#8217;s add Kotlin data classes to our Spring Boot project. They will be later used to serialize and deserialize data defined in our GraphQL schema.</p>



<p>To do so, let&#8217;s add the <code>Models.kt</code>:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#24292e"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="data class User(
    val id: String,
    val name: String,
)
data class Article(
    val id: String,
    val title: String,
    val content: String,
    val authorId: String,
    val createdAt: String,
)
data class Comment(
    val id: String,
    val content: String,
    val articleId: String,
    val userId: String,
)
data class CreateArticleInput(
    val title: String,
    val content: String,
    val userId: String,
)
data class AddCommentInput(
    val content: String,
    val userId: String,
)" style="color:#e1e4e8;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki github-dark" style="background-color: #24292e" tabindex="0"><code><span class="line"><span style="color: #F97583">data</span><span style="color: #E1E4E8"> </span><span style="color: #F97583">class</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">User</span><span style="color: #E1E4E8">(</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #F97583">val</span><span style="color: #E1E4E8"> id: </span><span style="color: #B392F0">String</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #F97583">val</span><span style="color: #E1E4E8"> name: </span><span style="color: #B392F0">String</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">)</span></span>
<span class="line"><span style="color: #F97583">data</span><span style="color: #E1E4E8"> </span><span style="color: #F97583">class</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">Article</span><span style="color: #E1E4E8">(</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #F97583">val</span><span style="color: #E1E4E8"> id: </span><span style="color: #B392F0">String</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #F97583">val</span><span style="color: #E1E4E8"> title: </span><span style="color: #B392F0">String</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #F97583">val</span><span style="color: #E1E4E8"> content: </span><span style="color: #B392F0">String</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #F97583">val</span><span style="color: #E1E4E8"> authorId: </span><span style="color: #B392F0">String</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #F97583">val</span><span style="color: #E1E4E8"> createdAt: </span><span style="color: #B392F0">String</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">)</span></span>
<span class="line"><span style="color: #F97583">data</span><span style="color: #E1E4E8"> </span><span style="color: #F97583">class</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">Comment</span><span style="color: #E1E4E8">(</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #F97583">val</span><span style="color: #E1E4E8"> id: </span><span style="color: #B392F0">String</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #F97583">val</span><span style="color: #E1E4E8"> content: </span><span style="color: #B392F0">String</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #F97583">val</span><span style="color: #E1E4E8"> articleId: </span><span style="color: #B392F0">String</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #F97583">val</span><span style="color: #E1E4E8"> userId: </span><span style="color: #B392F0">String</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">)</span></span>
<span class="line"><span style="color: #F97583">data</span><span style="color: #E1E4E8"> </span><span style="color: #F97583">class</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">CreateArticleInput</span><span style="color: #E1E4E8">(</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #F97583">val</span><span style="color: #E1E4E8"> title: </span><span style="color: #B392F0">String</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #F97583">val</span><span style="color: #E1E4E8"> content: </span><span style="color: #B392F0">String</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #F97583">val</span><span style="color: #E1E4E8"> userId: </span><span style="color: #B392F0">String</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">)</span></span>
<span class="line"><span style="color: #F97583">data</span><span style="color: #E1E4E8"> </span><span style="color: #F97583">class</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">AddCommentInput</span><span style="color: #E1E4E8">(</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #F97583">val</span><span style="color: #E1E4E8"> content: </span><span style="color: #B392F0">String</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #F97583">val</span><span style="color: #E1E4E8"> userId: </span><span style="color: #B392F0">String</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">)</span></span></code></pre></div>



<p>As we can see, nothing spectacular here. The only interesting fact is that the <code>ID</code> is serialized in the same way as a <code>String</code>.</p>



<h2 class="wp-block-heading" id="h-querymapping-as-suspend-fun">QueryMapping as suspend fun</h2>



<p>Following, let&#8217;s learn how the Spring for GraphQL works with Kotlin coroutines. </p>



<p>According to the documentation:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Kotlin coroutine and <code>Flow</code> are adapted to <code>Mono</code> and <code>Flux</code>.</p>
</blockquote>



<p>Which means that whenever we use the Spring WebFlux, <strong>Spring automatically converts our suspended functions <code>Mono</code> and functions that return <code>Flow</code> to <code>Flux</code></strong> .</p>



<h3 class="wp-block-heading" id="h-implement-article-service">Implement Article Service</h3>



<p>Before we head to the GraphQL part, let&#8217;s make some small preparations.</p>



<p>Let&#8217;s insert the <code>ArticleService</code>:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#24292e"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="@Component
object ArticleService {
    private val articles = mutableListOf(
        Article(
            id = &quot;article-id-1&quot;,
            title = &quot;article-title-1&quot;,
            content = &quot;article-content-1&quot;,
            authorId = &quot;user-id-2&quot;,
            createdAt = LocalDateTime.now().toString()
        ),
        Article(
            id = &quot;article-id-2&quot;,
            title = &quot;article-title-2&quot;,
            content = &quot;article-content-2&quot;,
            authorId = &quot;user-id-2&quot;,
            createdAt = LocalDateTime.now().toString()
        ),
        Article(
            id = &quot;article-id-3&quot;,
            title = &quot;article-title-3&quot;,
            content = &quot;article-content-3&quot;,
            authorId = &quot;user-id-3&quot;,
            createdAt = LocalDateTime.now().toString()
        ),
    )
    suspend fun findArticleById(id: String): Article? {
        delay(100)
        return articles.firstOrNull { it.id == id }
    }
}" style="color:#e1e4e8;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki github-dark" style="background-color: #24292e" tabindex="0"><code><span class="line"><span style="color: #B392F0">@Component</span></span>
<span class="line"><span style="color: #F97583">object</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">ArticleService</span><span style="color: #E1E4E8"> {</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #F97583">private</span><span style="color: #E1E4E8"> </span><span style="color: #F97583">val</span><span style="color: #E1E4E8"> articles </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">mutableListOf</span><span style="color: #E1E4E8">(</span></span>
<span class="line"><span style="color: #E1E4E8">        </span><span style="color: #B392F0">Article</span><span style="color: #E1E4E8">(</span></span>
<span class="line"><span style="color: #E1E4E8">            id </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #9ECBFF">&quot;article-id-1&quot;</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">            title </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #9ECBFF">&quot;article-title-1&quot;</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">            content </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #9ECBFF">&quot;article-content-1&quot;</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">            authorId </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #9ECBFF">&quot;user-id-2&quot;</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">            createdAt </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> LocalDateTime.</span><span style="color: #B392F0">now</span><span style="color: #E1E4E8">().</span><span style="color: #B392F0">toString</span><span style="color: #E1E4E8">()</span></span>
<span class="line"><span style="color: #E1E4E8">        ),</span></span>
<span class="line"><span style="color: #E1E4E8">        </span><span style="color: #B392F0">Article</span><span style="color: #E1E4E8">(</span></span>
<span class="line"><span style="color: #E1E4E8">            id </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #9ECBFF">&quot;article-id-2&quot;</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">            title </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #9ECBFF">&quot;article-title-2&quot;</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">            content </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #9ECBFF">&quot;article-content-2&quot;</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">            authorId </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #9ECBFF">&quot;user-id-2&quot;</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">            createdAt </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> LocalDateTime.</span><span style="color: #B392F0">now</span><span style="color: #E1E4E8">().</span><span style="color: #B392F0">toString</span><span style="color: #E1E4E8">()</span></span>
<span class="line"><span style="color: #E1E4E8">        ),</span></span>
<span class="line"><span style="color: #E1E4E8">        </span><span style="color: #B392F0">Article</span><span style="color: #E1E4E8">(</span></span>
<span class="line"><span style="color: #E1E4E8">            id </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #9ECBFF">&quot;article-id-3&quot;</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">            title </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #9ECBFF">&quot;article-title-3&quot;</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">            content </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #9ECBFF">&quot;article-content-3&quot;</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">            authorId </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #9ECBFF">&quot;user-id-3&quot;</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">            createdAt </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> LocalDateTime.</span><span style="color: #B392F0">now</span><span style="color: #E1E4E8">().</span><span style="color: #B392F0">toString</span><span style="color: #E1E4E8">()</span></span>
<span class="line"><span style="color: #E1E4E8">        ),</span></span>
<span class="line"><span style="color: #E1E4E8">    )</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #F97583">suspend</span><span style="color: #E1E4E8"> </span><span style="color: #F97583">fun</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">findArticleById</span><span style="color: #E1E4E8">(id: </span><span style="color: #B392F0">String</span><span style="color: #E1E4E8">): </span><span style="color: #B392F0">Article</span><span style="color: #E1E4E8">? {</span></span>
<span class="line"><span style="color: #E1E4E8">        </span><span style="color: #B392F0">delay</span><span style="color: #E1E4E8">(</span><span style="color: #79B8FF">100</span><span style="color: #E1E4E8">)</span></span>
<span class="line"><span style="color: #E1E4E8">        </span><span style="color: #F97583">return</span><span style="color: #E1E4E8"> articles.</span><span style="color: #B392F0">firstOrNull</span><span style="color: #E1E4E8"> { it.id </span><span style="color: #F97583">==</span><span style="color: #E1E4E8"> id }</span></span>
<span class="line"><span style="color: #E1E4E8">    }</span></span>
<span class="line"><span style="color: #E1E4E8">}</span></span></code></pre></div>



<p>As we can see, we use my favorite, in-memory database called <strong>mutable list</strong> (😉) and expose <code>findArticleById</code> &#8211; a suspended function.</p>



<p>This function will either find the desired article by ID or return a null value.</p>



<h3 class="wp-block-heading" id="h-add-articlecontroller">Add ArticleController</h3>



<p>With that done, let&#8217;s add the <code>ArticleController</code>:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#24292e"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="@Controller
class ArticleController(
    private val articleService: ArticleService,
) {
    @QueryMapping
    suspend fun article(@Argument id: String): Article? =
        articleService.findArticleById(id)
}" style="color:#e1e4e8;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki github-dark" style="background-color: #24292e" tabindex="0"><code><span class="line"><span style="color: #B392F0">@Controller</span></span>
<span class="line"><span style="color: #F97583">class</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">ArticleController</span><span style="color: #E1E4E8">(</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #F97583">private</span><span style="color: #E1E4E8"> </span><span style="color: #F97583">val</span><span style="color: #E1E4E8"> articleService: </span><span style="color: #B392F0">ArticleService</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">) {</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #B392F0">@QueryMapping</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #F97583">suspend</span><span style="color: #E1E4E8"> </span><span style="color: #F97583">fun</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">article</span><span style="color: #E1E4E8">(</span><span style="color: #B392F0">@Argument</span><span style="color: #E1E4E8"> id: </span><span style="color: #B392F0">String</span><span style="color: #E1E4E8">): </span><span style="color: #B392F0">Article</span><span style="color: #E1E4E8">? </span><span style="color: #F97583">=</span></span>
<span class="line"><span style="color: #E1E4E8">        articleService.</span><span style="color: #B392F0">findArticleById</span><span style="color: #E1E4E8">(id)</span></span>
<span class="line"><span style="color: #E1E4E8">}</span></span></code></pre></div>



<p>As we can see, thanks to the Spring GraphQL, we can leverage the annotation-based approach.</p>



<p>Firstly, we must annotate our class with the <strong>@Controller </strong>annotation. Then, all the methods inside it annotated with <strong>@SchemaMapping</strong> annotation will become handlers. </p>



<p>And <strong>@QueryMapping, @MutationMapping, </strong>or <strong>@SubscriptionMapping</strong> are nothing else than meta annotations (annotated with @SchemaMapping). This way, we achieve a more readable code. </p>



<p>Additionally, in Spring, we use the <code><strong>@Argument</strong></code> annotation to bind GraphQL input arguments to our Kotlin instances. </p>



<h3 class="wp-block-heading" id="h-enable-graphiql-amp-test">Enable GraphiQL &amp; Test</h3>



<p>Nextly, let&#8217;s turn on the <strong>GraphiQL</strong>&#8211; the IDE for testing GraphQL APIs (like Postman, Bruno, or Insomnia).</p>



<p>To do so, let&#8217;s navigate to the <code>resources</code> directory and change the <code>application.properties</code> into the <code>application.yaml</code> file:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#24292e"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="spring:
  application:
    name: graphsandbox
  graphql:
    graphiql:
      enabled: true" style="color:#e1e4e8;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki github-dark" style="background-color: #24292e" tabindex="0"><code><span class="line"><span style="color: #85E89D">spring</span><span style="color: #E1E4E8">:</span></span>
<span class="line"><span style="color: #E1E4E8">  </span><span style="color: #85E89D">application</span><span style="color: #E1E4E8">:</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #85E89D">name</span><span style="color: #E1E4E8">: </span><span style="color: #9ECBFF">graphsandbox</span></span>
<span class="line"><span style="color: #E1E4E8">  </span><span style="color: #85E89D">graphql</span><span style="color: #E1E4E8">:</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #85E89D">graphiql</span><span style="color: #E1E4E8">:</span></span>
<span class="line"><span style="color: #E1E4E8">      </span><span style="color: #85E89D">enabled</span><span style="color: #E1E4E8">: </span><span style="color: #79B8FF">true</span></span></code></pre></div>



<p>Then, let&#8217;s open up the browser, go to <code>http://localhost:8080/graphiql</code>, and put the following: </p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#24292e"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="query SomeRandomQuery {
  article(id: &quot;article-id-1&quot;) {
    id
    title
  }
}" style="color:#e1e4e8;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki github-dark" style="background-color: #24292e" tabindex="0"><code><span class="line"><span style="color: #F97583">query</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">SomeRandomQuery</span><span style="color: #E1E4E8"> {</span></span>
<span class="line"><span style="color: #E1E4E8">  </span><span style="color: #FFAB70">article</span><span style="color: #E1E4E8">(</span><span style="color: #FFAB70">id</span><span style="color: #E1E4E8">: </span><span style="color: #9ECBFF">&quot;article-id-1&quot;</span><span style="color: #E1E4E8">) {</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #FFAB70">id</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #FFAB70">title</span></span>
<span class="line"><span style="color: #E1E4E8">  }</span></span>
<span class="line"><span style="color: #E1E4E8">}</span></span></code></pre></div>



<p>As we can see, the <strong>query</strong> name is up to us, but inside it, we must define which of the &#8220;exposed&#8221; queries we would like to use. As the input parameter, we pass the String value- <code>article-id-1</code>&#8211; and lastly, we define what fields we would like to get in response (that&#8217;s what the whole GraphQL is a about, right?😉).</p>



<p>So, as the next step, let&#8217;s run the query and check out the result: </p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#24292e"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="{
  &quot;errors&quot;: [
    {
      &quot;message&quot;: &quot;INTERNAL_ERROR for f337f3f3-5&quot;,
      &quot;locations&quot;: [
        {
          &quot;line&quot;: 2,
          &quot;column&quot;: 3
        }
      ],
      &quot;path&quot;: [
        &quot;article&quot;
      ],
      &quot;extensions&quot;: {
        &quot;classification&quot;: &quot;INTERNAL_ERROR&quot;
      }
    }
  ],
  &quot;data&quot;: {
    &quot;article&quot;: null
  }
}" style="color:#e1e4e8;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki github-dark" style="background-color: #24292e" tabindex="0"><code><span class="line"><span style="color: #E1E4E8">{</span></span>
<span class="line"><span style="color: #E1E4E8">  </span><span style="color: #79B8FF">&quot;errors&quot;</span><span style="color: #E1E4E8">: [</span></span>
<span class="line"><span style="color: #E1E4E8">    {</span></span>
<span class="line"><span style="color: #E1E4E8">      </span><span style="color: #79B8FF">&quot;message&quot;</span><span style="color: #E1E4E8">: </span><span style="color: #9ECBFF">&quot;INTERNAL_ERROR for f337f3f3-5&quot;</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">      </span><span style="color: #79B8FF">&quot;locations&quot;</span><span style="color: #E1E4E8">: [</span></span>
<span class="line"><span style="color: #E1E4E8">        {</span></span>
<span class="line"><span style="color: #E1E4E8">          </span><span style="color: #79B8FF">&quot;line&quot;</span><span style="color: #E1E4E8">: </span><span style="color: #79B8FF">2</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">          </span><span style="color: #79B8FF">&quot;column&quot;</span><span style="color: #E1E4E8">: </span><span style="color: #79B8FF">3</span></span>
<span class="line"><span style="color: #E1E4E8">        }</span></span>
<span class="line"><span style="color: #E1E4E8">      ],</span></span>
<span class="line"><span style="color: #E1E4E8">      </span><span style="color: #79B8FF">&quot;path&quot;</span><span style="color: #E1E4E8">: [</span></span>
<span class="line"><span style="color: #E1E4E8">        </span><span style="color: #9ECBFF">&quot;article&quot;</span></span>
<span class="line"><span style="color: #E1E4E8">      ],</span></span>
<span class="line"><span style="color: #E1E4E8">      </span><span style="color: #79B8FF">&quot;extensions&quot;</span><span style="color: #E1E4E8">: {</span></span>
<span class="line"><span style="color: #E1E4E8">        </span><span style="color: #79B8FF">&quot;classification&quot;</span><span style="color: #E1E4E8">: </span><span style="color: #9ECBFF">&quot;INTERNAL_ERROR&quot;</span></span>
<span class="line"><span style="color: #E1E4E8">      }</span></span>
<span class="line"><span style="color: #E1E4E8">    }</span></span>
<span class="line"><span style="color: #E1E4E8">  ],</span></span>
<span class="line"><span style="color: #E1E4E8">  </span><span style="color: #79B8FF">&quot;data&quot;</span><span style="color: #E1E4E8">: {</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #79B8FF">&quot;article&quot;</span><span style="color: #E1E4E8">: </span><span style="color: #79B8FF">null</span></span>
<span class="line"><span style="color: #E1E4E8">  }</span></span>
<span class="line"><span style="color: #E1E4E8">}</span></span></code></pre></div>



<p>Unfortunately, that is not what we expected. </p>



<p>Moreover, when we check out logs, we should see this:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#24292e"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="s.g.e.ExceptionResolversExceptionHandler : Unresolved NoClassDefFoundError for executionId f337f3f3-5
java.lang.NoClassDefFoundError: org/springframework/data/util/KotlinReflectionUtils
	at org.springframework.graphql.data.method.InvocableHandlerMethodSupport.invokeSuspendingFunction(InvocableHandlerMethodSupport.java:141) ~[spring-graphql-1.3.4.jar:1.3.4]
	at org.springframework.graphql.data.method.InvocableHandlerMethodSupport.doInvoke(InvocableHandlerMethodSupport.java:108) ~[spring-graphql-1.3.4.jar:1.3.4]" style="color:#e1e4e8;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki github-dark" style="background-color: #24292e" tabindex="0"><code><span class="line"><span style="color: #e1e4e8">s.g.e.ExceptionResolversExceptionHandler : Unresolved NoClassDefFoundError for executionId f337f3f3-5</span></span>
<span class="line"><span style="color: #e1e4e8">java.lang.NoClassDefFoundError: org/springframework/data/util/KotlinReflectionUtils</span></span>
<span class="line"><span style="color: #e1e4e8">	at org.springframework.graphql.data.method.InvocableHandlerMethodSupport.invokeSuspendingFunction(InvocableHandlerMethodSupport.java:141) ~[spring-graphql-1.3.4.jar:1.3.4]</span></span>
<span class="line"><span style="color: #e1e4e8">	at org.springframework.graphql.data.method.InvocableHandlerMethodSupport.doInvoke(InvocableHandlerMethodSupport.java:108) ~[spring-graphql-1.3.4.jar:1.3.4]</span></span></code></pre></div>



<h3 class="wp-block-heading" id="h-fix-spring-graphql-coroutines-issue">Fix Spring GraphQL Coroutines Issue</h3>



<p>As we could see, the issue is caused by the missing dependency, and we can easily fix that.</p>



<p>To do so, let&#8217;s open up the <code>build.gradle.kts</code> and add the following dependency:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#24292e"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="implementation(&quot;org.springframework.data:spring-data-commons&quot;)" style="color:#e1e4e8;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki github-dark" style="background-color: #24292e" tabindex="0"><code><span class="line"><span style="color: #e1e4e8">implementation(&quot;org.springframework.data:spring-data-commons&quot;)</span></span></code></pre></div>



<p>Then, let&#8217;s sync the gradle project and rerun the application.</p>



<p>After we run the test again, we should see the following:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#24292e"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="{
  &quot;data&quot;: {
    &quot;article&quot;: {
      &quot;id&quot;: &quot;article-id-1&quot;,
      &quot;title&quot;: &quot;article-title-1&quot;
    }
  }
}" style="color:#e1e4e8;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki github-dark" style="background-color: #24292e" tabindex="0"><code><span class="line"><span style="color: #E1E4E8">{</span></span>
<span class="line"><span style="color: #E1E4E8">  </span><span style="color: #79B8FF">&quot;data&quot;</span><span style="color: #E1E4E8">: {</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #79B8FF">&quot;article&quot;</span><span style="color: #E1E4E8">: {</span></span>
<span class="line"><span style="color: #E1E4E8">      </span><span style="color: #79B8FF">&quot;id&quot;</span><span style="color: #E1E4E8">: </span><span style="color: #9ECBFF">&quot;article-id-1&quot;</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">      </span><span style="color: #79B8FF">&quot;title&quot;</span><span style="color: #E1E4E8">: </span><span style="color: #9ECBFF">&quot;article-title-1&quot;</span></span>
<span class="line"><span style="color: #E1E4E8">    }</span></span>
<span class="line"><span style="color: #E1E4E8">  }</span></span>
<span class="line"><span style="color: #E1E4E8">}</span></span></code></pre></div>



<p>Splendid! Everything works perfectly fine😉</p>



<h2 class="wp-block-heading" id="h-spring-graphql-with-kotlin-flow">Spring GraphQL with Kotlin Flow </h2>



<p>With that done, let&#8217;s learn how to work with Kotlin Flow and Spring GraphQL.</p>



<p>As the first step, let&#8217;s insert a new function to our <code>ArticleService</code>:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#24292e"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="fun findAllArticles(): Flow&lt;Article&gt; = articles.asFlow()" style="color:#e1e4e8;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki github-dark" style="background-color: #24292e" tabindex="0"><code><span class="line"><span style="color: #F97583">fun</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">findAllArticles</span><span style="color: #E1E4E8">(): </span><span style="color: #B392F0">Flow</span><span style="color: #E1E4E8">&lt;</span><span style="color: #B392F0">Article</span><span style="color: #E1E4E8">&gt; </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> articles.</span><span style="color: #B392F0">asFlow</span><span style="color: #E1E4E8">()</span></span></code></pre></div>



<p>This way, we produce a <strong>cold flow</strong> from our list of articles.</p>



<p>Then, let&#8217;s add a new handler:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#24292e"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="@QueryMapping
fun articles(): Flow&lt;Article&gt; =
    articleService.findAllArticles()" style="color:#e1e4e8;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki github-dark" style="background-color: #24292e" tabindex="0"><code><span class="line"><span style="color: #B392F0">@QueryMapping</span></span>
<span class="line"><span style="color: #F97583">fun</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">articles</span><span style="color: #E1E4E8">(): </span><span style="color: #B392F0">Flow</span><span style="color: #E1E4E8">&lt;</span><span style="color: #B392F0">Article</span><span style="color: #E1E4E8">&gt; </span><span style="color: #F97583">=</span></span>
<span class="line"><span style="color: #E1E4E8">    articleService.</span><span style="color: #B392F0">findAllArticles</span><span style="color: #E1E4E8">()</span></span></code></pre></div>



<p>Nothing new this time. Just like previously, we add a new function marked with <strong>@QueryMapping</strong>.</p>



<p>When we run the following query in GraphiQL: </p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#24292e"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="query AnotherOne {
  articles {
    id
    title
    createdAt
  }
}" style="color:#e1e4e8;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki github-dark" style="background-color: #24292e" tabindex="0"><code><span class="line"><span style="color: #F97583">query</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">AnotherOne</span><span style="color: #E1E4E8"> {</span></span>
<span class="line"><span style="color: #E1E4E8">  </span><span style="color: #FFAB70">articles</span><span style="color: #E1E4E8"> {</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #FFAB70">id</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #FFAB70">title</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #FFAB70">createdAt</span></span>
<span class="line"><span style="color: #E1E4E8">  }</span></span>
<span class="line"><span style="color: #E1E4E8">}</span></span></code></pre></div>



<p>We should see the exact result: </p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#24292e"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="{
  &quot;data&quot;: {
    &quot;articles&quot;: [
      {
        &quot;id&quot;: &quot;article-id-1&quot;,
        &quot;title&quot;: &quot;article-title-1&quot;,
        &quot;createdAt&quot;: &quot;2025-04-12T13:06:29.142469200&quot;
      },
      {
        &quot;id&quot;: &quot;article-id-2&quot;,
        &quot;title&quot;: &quot;article-title-2&quot;,
        &quot;createdAt&quot;: &quot;2025-04-12T13:06:29.142469200&quot;
      },
      {
        &quot;id&quot;: &quot;article-id-3&quot;,
        &quot;title&quot;: &quot;article-title-3&quot;,
        &quot;createdAt&quot;: &quot;2025-04-12T13:06:29.142469200&quot;
      }
    ]
  }
}" style="color:#e1e4e8;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki github-dark" style="background-color: #24292e" tabindex="0"><code><span class="line"><span style="color: #E1E4E8">{</span></span>
<span class="line"><span style="color: #E1E4E8">  </span><span style="color: #79B8FF">&quot;data&quot;</span><span style="color: #E1E4E8">: {</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #79B8FF">&quot;articles&quot;</span><span style="color: #E1E4E8">: [</span></span>
<span class="line"><span style="color: #E1E4E8">      {</span></span>
<span class="line"><span style="color: #E1E4E8">        </span><span style="color: #79B8FF">&quot;id&quot;</span><span style="color: #E1E4E8">: </span><span style="color: #9ECBFF">&quot;article-id-1&quot;</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">        </span><span style="color: #79B8FF">&quot;title&quot;</span><span style="color: #E1E4E8">: </span><span style="color: #9ECBFF">&quot;article-title-1&quot;</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">        </span><span style="color: #79B8FF">&quot;createdAt&quot;</span><span style="color: #E1E4E8">: </span><span style="color: #9ECBFF">&quot;2025-04-12T13:06:29.142469200&quot;</span></span>
<span class="line"><span style="color: #E1E4E8">      },</span></span>
<span class="line"><span style="color: #E1E4E8">      {</span></span>
<span class="line"><span style="color: #E1E4E8">        </span><span style="color: #79B8FF">&quot;id&quot;</span><span style="color: #E1E4E8">: </span><span style="color: #9ECBFF">&quot;article-id-2&quot;</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">        </span><span style="color: #79B8FF">&quot;title&quot;</span><span style="color: #E1E4E8">: </span><span style="color: #9ECBFF">&quot;article-title-2&quot;</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">        </span><span style="color: #79B8FF">&quot;createdAt&quot;</span><span style="color: #E1E4E8">: </span><span style="color: #9ECBFF">&quot;2025-04-12T13:06:29.142469200&quot;</span></span>
<span class="line"><span style="color: #E1E4E8">      },</span></span>
<span class="line"><span style="color: #E1E4E8">      {</span></span>
<span class="line"><span style="color: #E1E4E8">        </span><span style="color: #79B8FF">&quot;id&quot;</span><span style="color: #E1E4E8">: </span><span style="color: #9ECBFF">&quot;article-id-3&quot;</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">        </span><span style="color: #79B8FF">&quot;title&quot;</span><span style="color: #E1E4E8">: </span><span style="color: #9ECBFF">&quot;article-title-3&quot;</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">        </span><span style="color: #79B8FF">&quot;createdAt&quot;</span><span style="color: #E1E4E8">: </span><span style="color: #9ECBFF">&quot;2025-04-12T13:06:29.142469200&quot;</span></span>
<span class="line"><span style="color: #E1E4E8">      }</span></span>
<span class="line"><span style="color: #E1E4E8">    ]</span></span>
<span class="line"><span style="color: #E1E4E8">  }</span></span>
<span class="line"><span style="color: #E1E4E8">}</span></span></code></pre></div>



<h2 class="wp-block-heading" id="h-schemamapping">@SchemaMapping</h2>



<p>One of the greatest thing in GraphQL is the possibility to return the related data in one, single query. And if you remember our schema definition, our API should allow us to do that.</p>



<p>To be more specific, each article can have the <strong>author</strong>, multiple <strong>comments</strong>, and each comment also has its <strong>author</strong>.</p>



<p>But when we test that functionality right now:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#24292e"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="query AnotherOne {
  articles {
    id
    title
    createdAt
    author {
      id
      name
    }
    
    comments {
      id
      content
      author {
        name
      }
    }
  }
}" style="color:#e1e4e8;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki github-dark" style="background-color: #24292e" tabindex="0"><code><span class="line"><span style="color: #F97583">query</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">AnotherOne</span><span style="color: #E1E4E8"> {</span></span>
<span class="line"><span style="color: #E1E4E8">  </span><span style="color: #FFAB70">articles</span><span style="color: #E1E4E8"> {</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #FFAB70">id</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #FFAB70">title</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #FFAB70">createdAt</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #FFAB70">author</span><span style="color: #E1E4E8"> {</span></span>
<span class="line"><span style="color: #E1E4E8">      </span><span style="color: #FFAB70">id</span></span>
<span class="line"><span style="color: #E1E4E8">      </span><span style="color: #FFAB70">name</span></span>
<span class="line"><span style="color: #E1E4E8">    }</span></span>
<span class="line"><span style="color: #E1E4E8">    </span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #FFAB70">comments</span><span style="color: #E1E4E8"> {</span></span>
<span class="line"><span style="color: #E1E4E8">      </span><span style="color: #FFAB70">id</span></span>
<span class="line"><span style="color: #E1E4E8">      </span><span style="color: #FFAB70">content</span></span>
<span class="line"><span style="color: #E1E4E8">      </span><span style="color: #FFAB70">author</span><span style="color: #E1E4E8"> {</span></span>
<span class="line"><span style="color: #E1E4E8">        </span><span style="color: #FFAB70">name</span></span>
<span class="line"><span style="color: #E1E4E8">      }</span></span>
<span class="line"><span style="color: #E1E4E8">    }</span></span>
<span class="line"><span style="color: #E1E4E8">  }</span></span>
<span class="line"><span style="color: #E1E4E8">}</span></span></code></pre></div>



<p>The only thing we will get in response will be a looooong list of errors:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#24292e"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="{
  &quot;errors&quot;: [
    {
      &quot;message&quot;: &quot;The field at path '/articles[0]/author' was declared as a non null type, but the code involved in retrieving data has wrongly returned a null value.  The graphql specification requires that the parent field be set to null, or if that is non nullable that it bubble up null to its parent and so on. The non-nullable type is 'User' within parent type 'Article'&quot;,
      &quot;path&quot;: [
        &quot;articles&quot;,
        0,
        &quot;author&quot;
      ],
      &quot;extensions&quot;: {
        &quot;classification&quot;: &quot;NullValueInNonNullableField&quot;
      }
    }
... other errors" style="color:#e1e4e8;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki github-dark" style="background-color: #24292e" tabindex="0"><code><span class="line"><span style="color: #e1e4e8">{</span></span>
<span class="line"><span style="color: #e1e4e8">  &quot;errors&quot;: [</span></span>
<span class="line"><span style="color: #e1e4e8">    {</span></span>
<span class="line"><span style="color: #e1e4e8">      &quot;message&quot;: &quot;The field at path &#39;/articles[0]/author&#39; was declared as a non null type, but the code involved in retrieving data has wrongly returned a null value.  The graphql specification requires that the parent field be set to null, or if that is non nullable that it bubble up null to its parent and so on. The non-nullable type is &#39;User&#39; within parent type &#39;Article&#39;&quot;,</span></span>
<span class="line"><span style="color: #e1e4e8">      &quot;path&quot;: [</span></span>
<span class="line"><span style="color: #e1e4e8">        &quot;articles&quot;,</span></span>
<span class="line"><span style="color: #e1e4e8">        0,</span></span>
<span class="line"><span style="color: #e1e4e8">        &quot;author&quot;</span></span>
<span class="line"><span style="color: #e1e4e8">      ],</span></span>
<span class="line"><span style="color: #e1e4e8">      &quot;extensions&quot;: {</span></span>
<span class="line"><span style="color: #e1e4e8">        &quot;classification&quot;: &quot;NullValueInNonNullableField&quot;</span></span>
<span class="line"><span style="color: #e1e4e8">      }</span></span>
<span class="line"><span style="color: #e1e4e8">    }</span></span>
<span class="line"><span style="color: #e1e4e8">... other errors</span></span></code></pre></div>



<p>Of course, with Spring we can easily fix it, but we will need small preparation first.</p>



<h3 class="wp-block-heading" id="h-update-service">Update Service</h3>



<p>Firstly, let&#8217;s get back to our service and update it: </p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#24292e"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="@Component
object ArticleService {
    private val articles = mutableListOf(
        Article(
            id = &quot;article-id-1&quot;,
            title = &quot;article-title-1&quot;,
            content = &quot;article-content-1&quot;,
            authorId = &quot;user-id-2&quot;,
            createdAt = LocalDateTime.now().toString()
        ),
        Article(
            id = &quot;article-id-2&quot;,
            title = &quot;article-title-2&quot;,
            content = &quot;article-content-2&quot;,
            authorId = &quot;user-id-2&quot;,
            createdAt = LocalDateTime.now().toString()
        ),
        Article(
            id = &quot;article-id-3&quot;,
            title = &quot;article-title-3&quot;,
            content = &quot;article-content-3&quot;,
            authorId = &quot;user-id-3&quot;,
            createdAt = LocalDateTime.now().toString()
        ),
    )
    private val users = mutableListOf(
        User(id = &quot;user-id-1&quot;, name = &quot;user-name-1&quot;),
        User(id = &quot;user-id-2&quot;, name = &quot;user-name-2&quot;),
        User(id = &quot;user-id-3&quot;, name = &quot;user-name-3&quot;),
    )
    private val comments = mutableListOf(
        Comment(id = &quot;comment-id-1&quot;, content = &quot;comment-content-1&quot;, articleId = &quot;article-id-1&quot;, userId = &quot;user-id-3&quot;),
        Comment(id = &quot;comment-id-2&quot;, content = &quot;comment-content-2&quot;, articleId = &quot;article-id-2&quot;, userId = &quot;user-id-3&quot;),
        Comment(id = &quot;comment-id-3&quot;, content = &quot;comment-content-3&quot;, articleId = &quot;article-id-2&quot;, userId = &quot;user-id-2&quot;),
        Comment(id = &quot;comment-id-4&quot;, content = &quot;comment-content-4&quot;, articleId = &quot;article-id-3&quot;, userId = &quot;user-id-3&quot;),
    )
    
    suspend fun findArticleById(id: String): Article? {
        delay(100)
        return articles.firstOrNull { it.id == id }
    }
    fun findAllArticles(): Flow&lt;Article&gt; = articles.asFlow()
    suspend fun findUserById(id: String): User? {
        delay(100)
        return users.firstOrNull { it.id == id }
    }
    fun findCommentsByArticleId(id: String): Flow&lt;Comment&gt; =
        comments.filter { it.articleId == id }.asFlow()
}" style="color:#e1e4e8;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki github-dark" style="background-color: #24292e" tabindex="0"><code><span class="line"><span style="color: #B392F0">@Component</span></span>
<span class="line"><span style="color: #F97583">object</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">ArticleService</span><span style="color: #E1E4E8"> {</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #F97583">private</span><span style="color: #E1E4E8"> </span><span style="color: #F97583">val</span><span style="color: #E1E4E8"> articles </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">mutableListOf</span><span style="color: #E1E4E8">(</span></span>
<span class="line"><span style="color: #E1E4E8">        </span><span style="color: #B392F0">Article</span><span style="color: #E1E4E8">(</span></span>
<span class="line"><span style="color: #E1E4E8">            id </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #9ECBFF">&quot;article-id-1&quot;</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">            title </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #9ECBFF">&quot;article-title-1&quot;</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">            content </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #9ECBFF">&quot;article-content-1&quot;</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">            authorId </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #9ECBFF">&quot;user-id-2&quot;</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">            createdAt </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> LocalDateTime.</span><span style="color: #B392F0">now</span><span style="color: #E1E4E8">().</span><span style="color: #B392F0">toString</span><span style="color: #E1E4E8">()</span></span>
<span class="line"><span style="color: #E1E4E8">        ),</span></span>
<span class="line"><span style="color: #E1E4E8">        </span><span style="color: #B392F0">Article</span><span style="color: #E1E4E8">(</span></span>
<span class="line"><span style="color: #E1E4E8">            id </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #9ECBFF">&quot;article-id-2&quot;</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">            title </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #9ECBFF">&quot;article-title-2&quot;</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">            content </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #9ECBFF">&quot;article-content-2&quot;</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">            authorId </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #9ECBFF">&quot;user-id-2&quot;</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">            createdAt </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> LocalDateTime.</span><span style="color: #B392F0">now</span><span style="color: #E1E4E8">().</span><span style="color: #B392F0">toString</span><span style="color: #E1E4E8">()</span></span>
<span class="line"><span style="color: #E1E4E8">        ),</span></span>
<span class="line"><span style="color: #E1E4E8">        </span><span style="color: #B392F0">Article</span><span style="color: #E1E4E8">(</span></span>
<span class="line"><span style="color: #E1E4E8">            id </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #9ECBFF">&quot;article-id-3&quot;</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">            title </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #9ECBFF">&quot;article-title-3&quot;</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">            content </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #9ECBFF">&quot;article-content-3&quot;</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">            authorId </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #9ECBFF">&quot;user-id-3&quot;</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">            createdAt </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> LocalDateTime.</span><span style="color: #B392F0">now</span><span style="color: #E1E4E8">().</span><span style="color: #B392F0">toString</span><span style="color: #E1E4E8">()</span></span>
<span class="line"><span style="color: #E1E4E8">        ),</span></span>
<span class="line"><span style="color: #E1E4E8">    )</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #F97583">private</span><span style="color: #E1E4E8"> </span><span style="color: #F97583">val</span><span style="color: #E1E4E8"> users </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">mutableListOf</span><span style="color: #E1E4E8">(</span></span>
<span class="line"><span style="color: #E1E4E8">        </span><span style="color: #B392F0">User</span><span style="color: #E1E4E8">(id </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #9ECBFF">&quot;user-id-1&quot;</span><span style="color: #E1E4E8">, name </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #9ECBFF">&quot;user-name-1&quot;</span><span style="color: #E1E4E8">),</span></span>
<span class="line"><span style="color: #E1E4E8">        </span><span style="color: #B392F0">User</span><span style="color: #E1E4E8">(id </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #9ECBFF">&quot;user-id-2&quot;</span><span style="color: #E1E4E8">, name </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #9ECBFF">&quot;user-name-2&quot;</span><span style="color: #E1E4E8">),</span></span>
<span class="line"><span style="color: #E1E4E8">        </span><span style="color: #B392F0">User</span><span style="color: #E1E4E8">(id </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #9ECBFF">&quot;user-id-3&quot;</span><span style="color: #E1E4E8">, name </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #9ECBFF">&quot;user-name-3&quot;</span><span style="color: #E1E4E8">),</span></span>
<span class="line"><span style="color: #E1E4E8">    )</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #F97583">private</span><span style="color: #E1E4E8"> </span><span style="color: #F97583">val</span><span style="color: #E1E4E8"> comments </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">mutableListOf</span><span style="color: #E1E4E8">(</span></span>
<span class="line"><span style="color: #E1E4E8">        </span><span style="color: #B392F0">Comment</span><span style="color: #E1E4E8">(id </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #9ECBFF">&quot;comment-id-1&quot;</span><span style="color: #E1E4E8">, content </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #9ECBFF">&quot;comment-content-1&quot;</span><span style="color: #E1E4E8">, articleId </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #9ECBFF">&quot;article-id-1&quot;</span><span style="color: #E1E4E8">, userId </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #9ECBFF">&quot;user-id-3&quot;</span><span style="color: #E1E4E8">),</span></span>
<span class="line"><span style="color: #E1E4E8">        </span><span style="color: #B392F0">Comment</span><span style="color: #E1E4E8">(id </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #9ECBFF">&quot;comment-id-2&quot;</span><span style="color: #E1E4E8">, content </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #9ECBFF">&quot;comment-content-2&quot;</span><span style="color: #E1E4E8">, articleId </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #9ECBFF">&quot;article-id-2&quot;</span><span style="color: #E1E4E8">, userId </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #9ECBFF">&quot;user-id-3&quot;</span><span style="color: #E1E4E8">),</span></span>
<span class="line"><span style="color: #E1E4E8">        </span><span style="color: #B392F0">Comment</span><span style="color: #E1E4E8">(id </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #9ECBFF">&quot;comment-id-3&quot;</span><span style="color: #E1E4E8">, content </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #9ECBFF">&quot;comment-content-3&quot;</span><span style="color: #E1E4E8">, articleId </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #9ECBFF">&quot;article-id-2&quot;</span><span style="color: #E1E4E8">, userId </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #9ECBFF">&quot;user-id-2&quot;</span><span style="color: #E1E4E8">),</span></span>
<span class="line"><span style="color: #E1E4E8">        </span><span style="color: #B392F0">Comment</span><span style="color: #E1E4E8">(id </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #9ECBFF">&quot;comment-id-4&quot;</span><span style="color: #E1E4E8">, content </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #9ECBFF">&quot;comment-content-4&quot;</span><span style="color: #E1E4E8">, articleId </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #9ECBFF">&quot;article-id-3&quot;</span><span style="color: #E1E4E8">, userId </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> </span><span style="color: #9ECBFF">&quot;user-id-3&quot;</span><span style="color: #E1E4E8">),</span></span>
<span class="line"><span style="color: #E1E4E8">    )</span></span>
<span class="line"><span style="color: #E1E4E8">    </span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #F97583">suspend</span><span style="color: #E1E4E8"> </span><span style="color: #F97583">fun</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">findArticleById</span><span style="color: #E1E4E8">(id: </span><span style="color: #B392F0">String</span><span style="color: #E1E4E8">): </span><span style="color: #B392F0">Article</span><span style="color: #E1E4E8">? {</span></span>
<span class="line"><span style="color: #E1E4E8">        </span><span style="color: #B392F0">delay</span><span style="color: #E1E4E8">(</span><span style="color: #79B8FF">100</span><span style="color: #E1E4E8">)</span></span>
<span class="line"><span style="color: #E1E4E8">        </span><span style="color: #F97583">return</span><span style="color: #E1E4E8"> articles.</span><span style="color: #B392F0">firstOrNull</span><span style="color: #E1E4E8"> { it.id </span><span style="color: #F97583">==</span><span style="color: #E1E4E8"> id }</span></span>
<span class="line"><span style="color: #E1E4E8">    }</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #F97583">fun</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">findAllArticles</span><span style="color: #E1E4E8">(): </span><span style="color: #B392F0">Flow</span><span style="color: #E1E4E8">&lt;</span><span style="color: #B392F0">Article</span><span style="color: #E1E4E8">&gt; </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> articles.</span><span style="color: #B392F0">asFlow</span><span style="color: #E1E4E8">()</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #F97583">suspend</span><span style="color: #E1E4E8"> </span><span style="color: #F97583">fun</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">findUserById</span><span style="color: #E1E4E8">(id: </span><span style="color: #B392F0">String</span><span style="color: #E1E4E8">): </span><span style="color: #B392F0">User</span><span style="color: #E1E4E8">? {</span></span>
<span class="line"><span style="color: #E1E4E8">        </span><span style="color: #B392F0">delay</span><span style="color: #E1E4E8">(</span><span style="color: #79B8FF">100</span><span style="color: #E1E4E8">)</span></span>
<span class="line"><span style="color: #E1E4E8">        </span><span style="color: #F97583">return</span><span style="color: #E1E4E8"> users.</span><span style="color: #B392F0">firstOrNull</span><span style="color: #E1E4E8"> { it.id </span><span style="color: #F97583">==</span><span style="color: #E1E4E8"> id }</span></span>
<span class="line"><span style="color: #E1E4E8">    }</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #F97583">fun</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">findCommentsByArticleId</span><span style="color: #E1E4E8">(id: </span><span style="color: #B392F0">String</span><span style="color: #E1E4E8">): </span><span style="color: #B392F0">Flow</span><span style="color: #E1E4E8">&lt;</span><span style="color: #B392F0">Comment</span><span style="color: #E1E4E8">&gt; </span><span style="color: #F97583">=</span></span>
<span class="line"><span style="color: #E1E4E8">        comments.</span><span style="color: #B392F0">filter</span><span style="color: #E1E4E8"> { it.articleId </span><span style="color: #F97583">==</span><span style="color: #E1E4E8"> id }.</span><span style="color: #B392F0">asFlow</span><span style="color: #E1E4E8">()</span></span>
<span class="line"><span style="color: #E1E4E8">}</span></span></code></pre></div>



<p>As we can see, we added two more &#8220;tables&#8221; along with simple functions to obtain data from them. </p>



<h3 class="wp-block-heading" id="h-update-controller">Update @Controller</h3>



<p>Then, let&#8217;s update our controller class:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#24292e"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="@SchemaMapping
suspend fun author(article: Article): User =
    articleService.findUserById(article.authorId)!!
@SchemaMapping
suspend fun author(comment: Comment): User =
    articleService.findUserById(comment.userId)!!
@SchemaMapping
fun comments(article: Article): Flow&lt;Comment&gt; =
    articleService.findCommentsByArticleId(article.id)" style="color:#e1e4e8;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki github-dark" style="background-color: #24292e" tabindex="0"><code><span class="line"><span style="color: #B392F0">@SchemaMapping</span></span>
<span class="line"><span style="color: #F97583">suspend</span><span style="color: #E1E4E8"> </span><span style="color: #F97583">fun</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">author</span><span style="color: #E1E4E8">(article: </span><span style="color: #B392F0">Article</span><span style="color: #E1E4E8">): </span><span style="color: #B392F0">User</span><span style="color: #E1E4E8"> </span><span style="color: #F97583">=</span></span>
<span class="line"><span style="color: #E1E4E8">    articleService.</span><span style="color: #B392F0">findUserById</span><span style="color: #E1E4E8">(article.authorId)</span><span style="color: #F97583">!!</span></span>
<span class="line"><span style="color: #B392F0">@SchemaMapping</span></span>
<span class="line"><span style="color: #F97583">suspend</span><span style="color: #E1E4E8"> </span><span style="color: #F97583">fun</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">author</span><span style="color: #E1E4E8">(comment: </span><span style="color: #B392F0">Comment</span><span style="color: #E1E4E8">): </span><span style="color: #B392F0">User</span><span style="color: #E1E4E8"> </span><span style="color: #F97583">=</span></span>
<span class="line"><span style="color: #E1E4E8">    articleService.</span><span style="color: #B392F0">findUserById</span><span style="color: #E1E4E8">(comment.userId)</span><span style="color: #F97583">!!</span></span>
<span class="line"><span style="color: #B392F0">@SchemaMapping</span></span>
<span class="line"><span style="color: #F97583">fun</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">comments</span><span style="color: #E1E4E8">(article: </span><span style="color: #B392F0">Article</span><span style="color: #E1E4E8">): </span><span style="color: #B392F0">Flow</span><span style="color: #E1E4E8">&lt;</span><span style="color: #B392F0">Comment</span><span style="color: #E1E4E8">&gt; </span><span style="color: #F97583">=</span></span>
<span class="line"><span style="color: #E1E4E8">    articleService.</span><span style="color: #B392F0">findCommentsByArticleId</span><span style="color: #E1E4E8">(article.id)</span></span></code></pre></div>



<p>As we can see, this time we leverage the <strong>@SchemaMapping</strong> annotation for our handlers. Pretty similar to what we did already.</p>



<p>The interesting part here is the <strong>parameters</strong>. Every handler takes the <strong>parent type as an argument</strong>. Meaning, that for the <code>article -&gt; author</code> parent-child relationship, we define the function that takes the article instance as an argument. And that&#8217;s it!</p>



<p>Of course, please the double exclamation (<code>!!</code>) should not land in the real, production-ready code😉</p>



<p>Anyway, when we rerun our test now, we will see the following: </p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#24292e"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="{
  &quot;data&quot;: {
    &quot;articles&quot;: [
      {
        &quot;id&quot;: &quot;article-id-1&quot;,
        &quot;title&quot;: &quot;article-title-1&quot;,
        &quot;createdAt&quot;: &quot;2025-04-12T13:21:17.584507600&quot;,
        &quot;author&quot;: {
          &quot;id&quot;: &quot;user-id-2&quot;,
          &quot;name&quot;: &quot;user-name-2&quot;
        },
        &quot;comments&quot;: [
          {
            &quot;id&quot;: &quot;comment-id-1&quot;,
            &quot;content&quot;: &quot;comment-content-1&quot;,
            &quot;author&quot;: {
              &quot;name&quot;: &quot;user-name-3&quot;
            }
          }
        ]
      }
... more thingies " style="color:#e1e4e8;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki github-dark" style="background-color: #24292e" tabindex="0"><code><span class="line"><span style="color: #E1E4E8">{</span></span>
<span class="line"><span style="color: #E1E4E8">  </span><span style="color: #79B8FF">&quot;data&quot;</span><span style="color: #E1E4E8">: {</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #79B8FF">&quot;articles&quot;</span><span style="color: #E1E4E8">: [</span></span>
<span class="line"><span style="color: #E1E4E8">      {</span></span>
<span class="line"><span style="color: #E1E4E8">        </span><span style="color: #79B8FF">&quot;id&quot;</span><span style="color: #E1E4E8">: </span><span style="color: #9ECBFF">&quot;article-id-1&quot;</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">        </span><span style="color: #79B8FF">&quot;title&quot;</span><span style="color: #E1E4E8">: </span><span style="color: #9ECBFF">&quot;article-title-1&quot;</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">        </span><span style="color: #79B8FF">&quot;createdAt&quot;</span><span style="color: #E1E4E8">: </span><span style="color: #9ECBFF">&quot;2025-04-12T13:21:17.584507600&quot;</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">        </span><span style="color: #79B8FF">&quot;author&quot;</span><span style="color: #E1E4E8">: {</span></span>
<span class="line"><span style="color: #E1E4E8">          </span><span style="color: #79B8FF">&quot;id&quot;</span><span style="color: #E1E4E8">: </span><span style="color: #9ECBFF">&quot;user-id-2&quot;</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">          </span><span style="color: #79B8FF">&quot;name&quot;</span><span style="color: #E1E4E8">: </span><span style="color: #9ECBFF">&quot;user-name-2&quot;</span></span>
<span class="line"><span style="color: #E1E4E8">        },</span></span>
<span class="line"><span style="color: #E1E4E8">        </span><span style="color: #79B8FF">&quot;comments&quot;</span><span style="color: #E1E4E8">: [</span></span>
<span class="line"><span style="color: #E1E4E8">          {</span></span>
<span class="line"><span style="color: #E1E4E8">            </span><span style="color: #79B8FF">&quot;id&quot;</span><span style="color: #E1E4E8">: </span><span style="color: #9ECBFF">&quot;comment-id-1&quot;</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">            </span><span style="color: #79B8FF">&quot;content&quot;</span><span style="color: #E1E4E8">: </span><span style="color: #9ECBFF">&quot;comment-content-1&quot;</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">            </span><span style="color: #79B8FF">&quot;author&quot;</span><span style="color: #E1E4E8">: {</span></span>
<span class="line"><span style="color: #E1E4E8">              </span><span style="color: #79B8FF">&quot;name&quot;</span><span style="color: #E1E4E8">: </span><span style="color: #9ECBFF">&quot;user-name-3&quot;</span></span>
<span class="line"><span style="color: #E1E4E8">            }</span></span>
<span class="line"><span style="color: #E1E4E8">          }</span></span>
<span class="line"><span style="color: #E1E4E8">        ]</span></span>
<span class="line"><span style="color: #E1E4E8">      }</span></span>
<span class="line"><span style="color: #FDAEB7; font-style: italic">...</span><span style="color: #E1E4E8"> </span><span style="color: #FDAEB7; font-style: italic">more</span><span style="color: #E1E4E8"> </span><span style="color: #FDAEB7; font-style: italic">thingies</span><span style="color: #E1E4E8"> </span></span></code></pre></div>



<p>And that&#8217;s what we expected. Awesome!</p>



<h2 class="wp-block-heading" id="h-graphql-mutations">GraphQL Mutations </h2>



<p>When we run the application right now, we see that we still miss two things that we defined- <strong>mutations</strong>.</p>



<p>So, before we add a new handler, let&#8217;s implement the following function in our service:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#24292e"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="suspend fun createArticle(input: CreateArticleInput): Article {
    delay(100)
    return Article(
        id = UUID.randomUUID().toString(),
        title = input.title,
        content = input.content,
        authorId = input.userId,
        createdAt = LocalDateTime.now().toString(),
    ).also { articles.add(it) }
}" style="color:#e1e4e8;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki github-dark" style="background-color: #24292e" tabindex="0"><code><span class="line"><span style="color: #F97583">suspend</span><span style="color: #E1E4E8"> </span><span style="color: #F97583">fun</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">createArticle</span><span style="color: #E1E4E8">(input: </span><span style="color: #B392F0">CreateArticleInput</span><span style="color: #E1E4E8">): </span><span style="color: #B392F0">Article</span><span style="color: #E1E4E8"> {</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #B392F0">delay</span><span style="color: #E1E4E8">(</span><span style="color: #79B8FF">100</span><span style="color: #E1E4E8">)</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #F97583">return</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">Article</span><span style="color: #E1E4E8">(</span></span>
<span class="line"><span style="color: #E1E4E8">        id </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> UUID.</span><span style="color: #B392F0">randomUUID</span><span style="color: #E1E4E8">().</span><span style="color: #B392F0">toString</span><span style="color: #E1E4E8">(),</span></span>
<span class="line"><span style="color: #E1E4E8">        title </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> input.title,</span></span>
<span class="line"><span style="color: #E1E4E8">        content </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> input.content,</span></span>
<span class="line"><span style="color: #E1E4E8">        authorId </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> input.userId,</span></span>
<span class="line"><span style="color: #E1E4E8">        createdAt </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> LocalDateTime.</span><span style="color: #B392F0">now</span><span style="color: #E1E4E8">().</span><span style="color: #B392F0">toString</span><span style="color: #E1E4E8">(),</span></span>
<span class="line"><span style="color: #E1E4E8">    ).</span><span style="color: #B392F0">also</span><span style="color: #E1E4E8"> { articles.</span><span style="color: #B392F0">add</span><span style="color: #E1E4E8">(it) }</span></span>
<span class="line"><span style="color: #E1E4E8">}</span></span></code></pre></div>



<p>As can be seen, we take the input, create a new <code>Article</code> instance, and insert that to the list.</p>



<p>With Kotlin <code>also</code> ,we can return at the same time the created instance and achieve a slightly cleaner code. To be even fancier, we could use a reference here: <code>.also(articles::add)</code> 😉 </p>



<p>Then, let&#8217;s add the appropriate handler to our controller:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#24292e"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="@MutationMapping
suspend fun createArticle(@Argument input: CreateArticleInput): Article =
    articleService.createArticle(input)" style="color:#e1e4e8;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki github-dark" style="background-color: #24292e" tabindex="0"><code><span class="line"><span style="color: #B392F0">@MutationMapping</span></span>
<span class="line"><span style="color: #F97583">suspend</span><span style="color: #E1E4E8"> </span><span style="color: #F97583">fun</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">createArticle</span><span style="color: #E1E4E8">(</span><span style="color: #B392F0">@Argument</span><span style="color: #E1E4E8"> input: </span><span style="color: #B392F0">CreateArticleInput</span><span style="color: #E1E4E8">): </span><span style="color: #B392F0">Article</span><span style="color: #E1E4E8"> </span><span style="color: #F97583">=</span></span>
<span class="line"><span style="color: #E1E4E8">    articleService.</span><span style="color: #B392F0">createArticle</span><span style="color: #E1E4E8">(input)</span></span></code></pre></div>



<p>As we can see, just like with @QueryMapping, this time, we mark the handler with <strong>@MutationMapping</strong> and our argument with <strong>@Argument</strong>. Nothing else is necessary to make our mutation work. </p>



<p>So, given that, let&#8217;s rerun the app and test our functionality:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#24292e"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="mutation someMutation {
  createArticle(
    input: {
      title: &quot;Awesome codersee Kotlin article&quot;, 
      content: &quot;Some content&quot;, 
      userId: &quot;user-id-3&quot;
    }
  ) {
    id
    title
    content
    author {
      id
      name
    }
  }
}" style="color:#e1e4e8;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki github-dark" style="background-color: #24292e" tabindex="0"><code><span class="line"><span style="color: #F97583">mutation</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">someMutation</span><span style="color: #E1E4E8"> {</span></span>
<span class="line"><span style="color: #E1E4E8">  </span><span style="color: #FFAB70">createArticle</span><span style="color: #E1E4E8">(</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #FFAB70">input</span><span style="color: #E1E4E8">: {</span></span>
<span class="line"><span style="color: #E1E4E8">      </span><span style="color: #9ECBFF">title</span><span style="color: #E1E4E8">: </span><span style="color: #9ECBFF">&quot;Awesome codersee Kotlin article&quot;</span><span style="color: #E1E4E8">, </span></span>
<span class="line"><span style="color: #E1E4E8">      </span><span style="color: #9ECBFF">content</span><span style="color: #E1E4E8">: </span><span style="color: #9ECBFF">&quot;Some content&quot;</span><span style="color: #E1E4E8">, </span></span>
<span class="line"><span style="color: #E1E4E8">      </span><span style="color: #9ECBFF">userId</span><span style="color: #E1E4E8">: </span><span style="color: #9ECBFF">&quot;user-id-3&quot;</span></span>
<span class="line"><span style="color: #E1E4E8">    }</span></span>
<span class="line"><span style="color: #E1E4E8">  ) {</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #FFAB70">id</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #FFAB70">title</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #FFAB70">content</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #FFAB70">author</span><span style="color: #E1E4E8"> {</span></span>
<span class="line"><span style="color: #E1E4E8">      </span><span style="color: #FFAB70">id</span></span>
<span class="line"><span style="color: #E1E4E8">      </span><span style="color: #FFAB70">name</span></span>
<span class="line"><span style="color: #E1E4E8">    }</span></span>
<span class="line"><span style="color: #E1E4E8">  }</span></span>
<span class="line"><span style="color: #E1E4E8">}</span></span></code></pre></div>



<p>As a result, we should get the following:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#24292e"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="{
  &quot;data&quot;: {
    &quot;createArticle&quot;: {
      &quot;id&quot;: &quot;0e51a902-835b-4e55-9b4a-f2dccd242ea7&quot;,
      &quot;title&quot;: &quot;Awesome codersee Kotlin article&quot;,
      &quot;content&quot;: &quot;Some content&quot;,
      &quot;author&quot;: {
        &quot;id&quot;: &quot;user-id-3&quot;,
        &quot;name&quot;: &quot;user-name-3&quot;
      }
    }
  }
}" style="color:#e1e4e8;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki github-dark" style="background-color: #24292e" tabindex="0"><code><span class="line"><span style="color: #E1E4E8">{</span></span>
<span class="line"><span style="color: #E1E4E8">  </span><span style="color: #79B8FF">&quot;data&quot;</span><span style="color: #E1E4E8">: {</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #79B8FF">&quot;createArticle&quot;</span><span style="color: #E1E4E8">: {</span></span>
<span class="line"><span style="color: #E1E4E8">      </span><span style="color: #79B8FF">&quot;id&quot;</span><span style="color: #E1E4E8">: </span><span style="color: #9ECBFF">&quot;0e51a902-835b-4e55-9b4a-f2dccd242ea7&quot;</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">      </span><span style="color: #79B8FF">&quot;title&quot;</span><span style="color: #E1E4E8">: </span><span style="color: #9ECBFF">&quot;Awesome codersee Kotlin article&quot;</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">      </span><span style="color: #79B8FF">&quot;content&quot;</span><span style="color: #E1E4E8">: </span><span style="color: #9ECBFF">&quot;Some content&quot;</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">      </span><span style="color: #79B8FF">&quot;author&quot;</span><span style="color: #E1E4E8">: {</span></span>
<span class="line"><span style="color: #E1E4E8">        </span><span style="color: #79B8FF">&quot;id&quot;</span><span style="color: #E1E4E8">: </span><span style="color: #9ECBFF">&quot;user-id-3&quot;</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">        </span><span style="color: #79B8FF">&quot;name&quot;</span><span style="color: #E1E4E8">: </span><span style="color: #9ECBFF">&quot;user-name-3&quot;</span></span>
<span class="line"><span style="color: #E1E4E8">      }</span></span>
<span class="line"><span style="color: #E1E4E8">    }</span></span>
<span class="line"><span style="color: #E1E4E8">  }</span></span>
<span class="line"><span style="color: #E1E4E8">}</span></span></code></pre></div>



<p>Wonderful! We can see that with GraphQL mutations, we can not only create/modify things on our server. With this one query, we can also retrieve the data and the dependent objects😮</p>



<h2 class="wp-block-heading" id="h-error-handling-in-spring-graphql">Error Handling in Spring GraphQL</h2>



<p>As the last step, let&#8217;s add one more mutation to learn more about <strong>error handling</strong>.</p>



<p>First, let&#8217;s add the new custom exception ot our codebase in <code>Models.kt</code>:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#24292e"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="data class GenericNotFound(val msg: String) : RuntimeException(msg)" style="color:#e1e4e8;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki github-dark" style="background-color: #24292e" tabindex="0"><code><span class="line"><span style="color: #F97583">data</span><span style="color: #E1E4E8"> </span><span style="color: #F97583">class</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">GenericNotFound</span><span style="color: #E1E4E8">(</span><span style="color: #F97583">val</span><span style="color: #E1E4E8"> msg: </span><span style="color: #B392F0">String</span><span style="color: #E1E4E8">) : </span><span style="color: #B392F0">RuntimeException</span><span style="color: #E1E4E8">(</span><span style="color: #B392F0">msg</span><span style="color: #E1E4E8">)</span></span></code></pre></div>



<p>Then, let&#8217;s implement the following function in <code>ArticleService</code>:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#24292e"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="suspend fun addComment(id: String, input: AddCommentInput): Comment {
        delay(100)
        users.firstOrNull { it.id == input.userId }
            ?: throw GenericNotFound(&quot;User not found&quot;)
        return articles.firstOrNull { it.id == id }
            ?.let {
                Comment(
                    id = UUID.randomUUID().toString(),
                    articleId = id,
                    content = input.content,
                    userId = input.userId,
                ).also { comments.add(it) }
            }
            ?: throw GenericNotFound(&quot;Article not found&quot;)
    }" style="color:#e1e4e8;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki github-dark" style="background-color: #24292e" tabindex="0"><code><span class="line"><span style="color: #F97583">suspend</span><span style="color: #E1E4E8"> </span><span style="color: #F97583">fun</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">addComment</span><span style="color: #E1E4E8">(id: </span><span style="color: #B392F0">String</span><span style="color: #E1E4E8">, input: </span><span style="color: #B392F0">AddCommentInput</span><span style="color: #E1E4E8">): </span><span style="color: #B392F0">Comment</span><span style="color: #E1E4E8"> {</span></span>
<span class="line"><span style="color: #E1E4E8">        </span><span style="color: #B392F0">delay</span><span style="color: #E1E4E8">(</span><span style="color: #79B8FF">100</span><span style="color: #E1E4E8">)</span></span>
<span class="line"><span style="color: #E1E4E8">        users.</span><span style="color: #B392F0">firstOrNull</span><span style="color: #E1E4E8"> { it.id </span><span style="color: #F97583">==</span><span style="color: #E1E4E8"> input.userId }</span></span>
<span class="line"><span style="color: #E1E4E8">            ?: </span><span style="color: #F97583">throw</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">GenericNotFound</span><span style="color: #E1E4E8">(</span><span style="color: #9ECBFF">&quot;User not found&quot;</span><span style="color: #E1E4E8">)</span></span>
<span class="line"><span style="color: #E1E4E8">        </span><span style="color: #F97583">return</span><span style="color: #E1E4E8"> articles.</span><span style="color: #B392F0">firstOrNull</span><span style="color: #E1E4E8"> { it.id </span><span style="color: #F97583">==</span><span style="color: #E1E4E8"> id }</span></span>
<span class="line"><span style="color: #E1E4E8">            ?.</span><span style="color: #B392F0">let</span><span style="color: #E1E4E8"> {</span></span>
<span class="line"><span style="color: #E1E4E8">                </span><span style="color: #B392F0">Comment</span><span style="color: #E1E4E8">(</span></span>
<span class="line"><span style="color: #E1E4E8">                    id </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> UUID.</span><span style="color: #B392F0">randomUUID</span><span style="color: #E1E4E8">().</span><span style="color: #B392F0">toString</span><span style="color: #E1E4E8">(),</span></span>
<span class="line"><span style="color: #E1E4E8">                    articleId </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> id,</span></span>
<span class="line"><span style="color: #E1E4E8">                    content </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> input.content,</span></span>
<span class="line"><span style="color: #E1E4E8">                    userId </span><span style="color: #F97583">=</span><span style="color: #E1E4E8"> input.userId,</span></span>
<span class="line"><span style="color: #E1E4E8">                ).</span><span style="color: #B392F0">also</span><span style="color: #E1E4E8"> { comments.</span><span style="color: #B392F0">add</span><span style="color: #E1E4E8">(it) }</span></span>
<span class="line"><span style="color: #E1E4E8">            }</span></span>
<span class="line"><span style="color: #E1E4E8">            ?: </span><span style="color: #F97583">throw</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">GenericNotFound</span><span style="color: #E1E4E8">(</span><span style="color: #9ECBFF">&quot;Article not found&quot;</span><span style="color: #E1E4E8">)</span></span>
<span class="line"><span style="color: #E1E4E8">    }</span></span></code></pre></div>



<p>This time, we can see a more production-ready code.</p>



<p>If we want to add a comment to the article, we must first check if both the desired author and article exist, right?</p>



<p>If that is not the case, then we throw our custom exception.</p>



<p>Following, let&#8217;s add a new mutation handler: </p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#24292e"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="@MutationMapping
suspend fun addComment(
    @Argument id: String,
    @Argument input: AddCommentInput,
): Comment =
    articleService.addComment(id, input)" style="color:#e1e4e8;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki github-dark" style="background-color: #24292e" tabindex="0"><code><span class="line"><span style="color: #B392F0">@MutationMapping</span></span>
<span class="line"><span style="color: #F97583">suspend</span><span style="color: #E1E4E8"> </span><span style="color: #F97583">fun</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">addComment</span><span style="color: #E1E4E8">(</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #B392F0">@Argument</span><span style="color: #E1E4E8"> id: </span><span style="color: #B392F0">String</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #B392F0">@Argument</span><span style="color: #E1E4E8"> input: </span><span style="color: #B392F0">AddCommentInput</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">): </span><span style="color: #B392F0">Comment</span><span style="color: #E1E4E8"> </span><span style="color: #F97583">=</span></span>
<span class="line"><span style="color: #E1E4E8">    articleService.</span><span style="color: #B392F0">addComment</span><span style="color: #E1E4E8">(id, input)</span></span></code></pre></div>



<p>Now, when we rerun the app and execute the following test: </p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#24292e"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="mutation anotherMutation {
  addComment(
    articleId: &quot;non-existing&quot;
    input: {userId: &quot;user-id-1&quot;, content: &quot;My comment&quot;}
  ) {
    id
    content
  }
}" style="color:#e1e4e8;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki github-dark" style="background-color: #24292e" tabindex="0"><code><span class="line"><span style="color: #F97583">mutation</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">anotherMutation</span><span style="color: #E1E4E8"> {</span></span>
<span class="line"><span style="color: #E1E4E8">  </span><span style="color: #FFAB70">addComment</span><span style="color: #E1E4E8">(</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #FFAB70">articleId</span><span style="color: #E1E4E8">: </span><span style="color: #9ECBFF">&quot;non-existing&quot;</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #FFAB70">input</span><span style="color: #E1E4E8">: {</span><span style="color: #9ECBFF">userId</span><span style="color: #E1E4E8">: </span><span style="color: #9ECBFF">&quot;user-id-1&quot;</span><span style="color: #E1E4E8">, </span><span style="color: #9ECBFF">content</span><span style="color: #E1E4E8">: </span><span style="color: #9ECBFF">&quot;My comment&quot;</span><span style="color: #E1E4E8">}</span></span>
<span class="line"><span style="color: #E1E4E8">  ) {</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #FFAB70">id</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #FFAB70">content</span></span>
<span class="line"><span style="color: #E1E4E8">  }</span></span>
<span class="line"><span style="color: #E1E4E8">}</span></span></code></pre></div>



<p>We will see the following error: </p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#24292e"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="{
  &quot;errors&quot;: [
    {
      &quot;message&quot;: &quot;INTERNAL_ERROR for f62a4c7e-1&quot;,
      &quot;locations&quot;: [
        {
          &quot;line&quot;: 52,
          &quot;column&quot;: 3
        }
      ],
      &quot;path&quot;: [
        &quot;addComment&quot;
      ],
      &quot;extensions&quot;: {
        &quot;classification&quot;: &quot;INTERNAL_ERROR&quot;
      }
    }
  ],
  &quot;data&quot;: {
    &quot;addComment&quot;: null
  }
}" style="color:#e1e4e8;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki github-dark" style="background-color: #24292e" tabindex="0"><code><span class="line"><span style="color: #E1E4E8">{</span></span>
<span class="line"><span style="color: #E1E4E8">  </span><span style="color: #79B8FF">&quot;errors&quot;</span><span style="color: #E1E4E8">: [</span></span>
<span class="line"><span style="color: #E1E4E8">    {</span></span>
<span class="line"><span style="color: #E1E4E8">      </span><span style="color: #79B8FF">&quot;message&quot;</span><span style="color: #E1E4E8">: </span><span style="color: #9ECBFF">&quot;INTERNAL_ERROR for f62a4c7e-1&quot;</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">      </span><span style="color: #79B8FF">&quot;locations&quot;</span><span style="color: #E1E4E8">: [</span></span>
<span class="line"><span style="color: #E1E4E8">        {</span></span>
<span class="line"><span style="color: #E1E4E8">          </span><span style="color: #79B8FF">&quot;line&quot;</span><span style="color: #E1E4E8">: </span><span style="color: #79B8FF">52</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">          </span><span style="color: #79B8FF">&quot;column&quot;</span><span style="color: #E1E4E8">: </span><span style="color: #79B8FF">3</span></span>
<span class="line"><span style="color: #E1E4E8">        }</span></span>
<span class="line"><span style="color: #E1E4E8">      ],</span></span>
<span class="line"><span style="color: #E1E4E8">      </span><span style="color: #79B8FF">&quot;path&quot;</span><span style="color: #E1E4E8">: [</span></span>
<span class="line"><span style="color: #E1E4E8">        </span><span style="color: #9ECBFF">&quot;addComment&quot;</span></span>
<span class="line"><span style="color: #E1E4E8">      ],</span></span>
<span class="line"><span style="color: #E1E4E8">      </span><span style="color: #79B8FF">&quot;extensions&quot;</span><span style="color: #E1E4E8">: {</span></span>
<span class="line"><span style="color: #E1E4E8">        </span><span style="color: #79B8FF">&quot;classification&quot;</span><span style="color: #E1E4E8">: </span><span style="color: #9ECBFF">&quot;INTERNAL_ERROR&quot;</span></span>
<span class="line"><span style="color: #E1E4E8">      }</span></span>
<span class="line"><span style="color: #E1E4E8">    }</span></span>
<span class="line"><span style="color: #E1E4E8">  ],</span></span>
<span class="line"><span style="color: #E1E4E8">  </span><span style="color: #79B8FF">&quot;data&quot;</span><span style="color: #E1E4E8">: {</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #79B8FF">&quot;addComment&quot;</span><span style="color: #E1E4E8">: </span><span style="color: #79B8FF">null</span></span>
<span class="line"><span style="color: #E1E4E8">  }</span></span>
<span class="line"><span style="color: #E1E4E8">}</span></span></code></pre></div>



<p>And this is not what we wanted, right? </p>



<h3 class="wp-block-heading" id="h-graphqlexceptionhandler-and-controlleradvice">GraphQlExceptionHandler and ControllerAdvice</h3>



<p>One of the solutions we can have in such a situation is a combination of ControllerAdvice and GraphQlExceptionHandler.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>If you would like to learn more about ControllerAdvice and RestControllerAdvice, then check out my <a href="https://blog.codersee.com/controlleradvice-vs-restcontrolleradvice/">other article</a>.</p>
</blockquote>



<p>So, as the next step, let&#8217;s add the <code>GlobalExceptionHandler</code> class to the project:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#24292e"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="@ControllerAdvice
class GlobalExceptionHandler {
    @GraphQlExceptionHandler
    fun handleGenericNotFound(ex: GenericNotFound): GraphQLError =
        GraphQLError.newError()
            .errorType(ErrorType.DataFetchingException)
            .message(ex.msg)
            .build()
}" style="color:#e1e4e8;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki github-dark" style="background-color: #24292e" tabindex="0"><code><span class="line"><span style="color: #B392F0">@ControllerAdvice</span></span>
<span class="line"><span style="color: #F97583">class</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">GlobalExceptionHandler</span><span style="color: #E1E4E8"> {</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #B392F0">@GraphQlExceptionHandler</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #F97583">fun</span><span style="color: #E1E4E8"> </span><span style="color: #B392F0">handleGenericNotFound</span><span style="color: #E1E4E8">(ex: </span><span style="color: #B392F0">GenericNotFound</span><span style="color: #E1E4E8">): </span><span style="color: #B392F0">GraphQLError</span><span style="color: #E1E4E8"> </span><span style="color: #F97583">=</span></span>
<span class="line"><span style="color: #E1E4E8">        GraphQLError.</span><span style="color: #B392F0">newError</span><span style="color: #E1E4E8">()</span></span>
<span class="line"><span style="color: #E1E4E8">            .</span><span style="color: #B392F0">errorType</span><span style="color: #E1E4E8">(ErrorType.DataFetchingException)</span></span>
<span class="line"><span style="color: #E1E4E8">            .</span><span style="color: #B392F0">message</span><span style="color: #E1E4E8">(ex.msg)</span></span>
<span class="line"><span style="color: #E1E4E8">            .</span><span style="color: #B392F0">build</span><span style="color: #E1E4E8">()</span></span>
<span class="line"><span style="color: #E1E4E8">}</span></span></code></pre></div>



<p>As we can see, inside it, we implement a function that takes the <strong>GenericNotFound</strong> as an argument. This way, whenever our custom exception is thrown, Spring will return the <strong>GraphQLError</strong> instance with our config instead.</p>



<p>As a result, when we retest the application, we should get the following JSON:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#24292e"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="{
  &quot;errors&quot;: [
    {
      &quot;message&quot;: &quot;Article not found&quot;,
      &quot;locations&quot;: [],
      &quot;extensions&quot;: {
        &quot;classification&quot;: &quot;DataFetchingException&quot;
      }
    }
  ],
  &quot;data&quot;: {
    &quot;addComment&quot;: null
  }
}" style="color:#e1e4e8;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki github-dark" style="background-color: #24292e" tabindex="0"><code><span class="line"><span style="color: #E1E4E8">{</span></span>
<span class="line"><span style="color: #E1E4E8">  </span><span style="color: #79B8FF">&quot;errors&quot;</span><span style="color: #E1E4E8">: [</span></span>
<span class="line"><span style="color: #E1E4E8">    {</span></span>
<span class="line"><span style="color: #E1E4E8">      </span><span style="color: #79B8FF">&quot;message&quot;</span><span style="color: #E1E4E8">: </span><span style="color: #9ECBFF">&quot;Article not found&quot;</span><span style="color: #E1E4E8">,</span></span>
<span class="line"><span style="color: #E1E4E8">      </span><span style="color: #79B8FF">&quot;locations&quot;</span><span style="color: #E1E4E8">: [],</span></span>
<span class="line"><span style="color: #E1E4E8">      </span><span style="color: #79B8FF">&quot;extensions&quot;</span><span style="color: #E1E4E8">: {</span></span>
<span class="line"><span style="color: #E1E4E8">        </span><span style="color: #79B8FF">&quot;classification&quot;</span><span style="color: #E1E4E8">: </span><span style="color: #9ECBFF">&quot;DataFetchingException&quot;</span></span>
<span class="line"><span style="color: #E1E4E8">      }</span></span>
<span class="line"><span style="color: #E1E4E8">    }</span></span>
<span class="line"><span style="color: #E1E4E8">  ],</span></span>
<span class="line"><span style="color: #E1E4E8">  </span><span style="color: #79B8FF">&quot;data&quot;</span><span style="color: #E1E4E8">: {</span></span>
<span class="line"><span style="color: #E1E4E8">    </span><span style="color: #79B8FF">&quot;addComment&quot;</span><span style="color: #E1E4E8">: </span><span style="color: #79B8FF">null</span></span>
<span class="line"><span style="color: #E1E4E8">  }</span></span>
<span class="line"><span style="color: #E1E4E8">}</span></span></code></pre></div>



<h2 class="wp-block-heading" id="h-spring-graphql-with-kotlin-coroutines-summary">Spring GraphQL with Kotlin Coroutines Summary</h2>



<p>And that&#8217;s all for this article on how to work with Kotlin coroutines and Flows in the Spring Boot GraphQL project.</p>



<p>As always, you can find the source code in my <a href="https://github.com/codersee-blog/spring-boot-3-graphql-kotlin-coroutines" target="_blank" rel="noreferrer noopener">GitHub repostory</a>.</p>
<p>The post <a href="https://blog.codersee.com/spring-graphql-kotlin-coroutines/">Spring for GraphQL with Kotlin Coroutines</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-graphql-kotlin-coroutines/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Test Spring Boot AWS S3 with Localstack and Testcontainers</title>
		<link>https://blog.codersee.com/test-spring-boot-aws-s3-with-localstack-and-testcontainers/</link>
					<comments>https://blog.codersee.com/test-spring-boot-aws-s3-with-localstack-and-testcontainers/#respond</comments>
		
		<dc:creator><![CDATA[Piotr]]></dc:creator>
		<pubDate>Tue, 24 Sep 2024 05:00:00 +0000</pubDate>
				<category><![CDATA[Spring]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[LocalStack]]></category>
		<category><![CDATA[S3 Object Storage]]></category>
		<category><![CDATA[Testcontainers]]></category>
		<category><![CDATA[Testing]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=12009164</guid>

					<description><![CDATA[<p>The last article in a series dedicated to Spring Boot AWS S3 integration focused on testing with LocalStack and Testcontainers.</p>
<p>The post <a href="https://blog.codersee.com/test-spring-boot-aws-s3-with-localstack-and-testcontainers/">Test Spring Boot AWS S3 with Localstack and Testcontainers</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Welcome to the <strong>last article</strong> in a series dedicated to integrating a Spring Boot Kotlin app with <strong>AWS S3</strong> Object Storage, in which we will focus on <strong>integration testing with LocalStack and Testcontainers</strong>. And although we will focus on Object Storage, the approach we will use can be easily replicated with other AWS services.</p>



<p>I can guarantee that you will benefit from this tutorial regardless of whether you saw previous articles about S3Client or S3Template, or not. But, I definitely encourage you to take a look at them, too: </p>



<ul class="wp-block-list">
<li><a href="https://blog.codersee.com/spring-boot-aws-s3-s3client-kotlin/">#1 Spring Boot with AWS S3, S3Client, and Kotlin</a></li>



<li><a href="https://blog.codersee.com/spring-boot-with-kotlin-aws-s3-and-s3template/">#2 Spring Boot with Kotlin, AWS S3, and S3Template</a></li>



<li>#3 (This article)</li>
</ul>



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



<p>If you prefer <strong>video content</strong>, then check out my video that covers all three articles:</p>



<div>
<a href="https://blog.codersee.com/test-spring-boot-aws-s3-with-localstack-and-testcontainers/"><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>If you find this content useful,<strong> please leave a subscription</strong> 🙂</p>



<h2 class="wp-block-heading" id="h-prerequisites">Prerequisites</h2>



<p>Before heading to the guide, I just wanted to emphasize that today we will be working with <strong>Testcontainers</strong>. And this means that we must have a <strong>supported Docker environment</strong>.</p>



<p>So, if you do not have Docker configured on your local and want to follow this article, please check out <a href="https://java.testcontainers.org/supported_docker_environment/" target="_blank" rel="noreferrer noopener">their documentation</a>.</p>



<p>Of course, you must have Java, IDE, and Spring Boot project too, but I believe this is quite obvious 😉 </p>



<h2 class="wp-block-heading" id="h-testcontainers-and-localstack">Testcontainers and LocalStack</h2>



<p>Lastly, I would like to say a few words about the Testcontainers and LocalStack, which in my opinion are a great way to test Spring Boot S3 integration (and other AWS integrations, too). </p>



<h3 class="wp-block-heading" id="h-testcontainers">Testcontainers</h3>



<p><strong>Testcontainers </strong>is a library for providing throwaway, lightweight instances of Docker containers. They are an excellent approach whenever need to test behavior dependent on external services, like AWS, or some external databases. </p>



<p>Long story short, instead of mocking, or manual set up of some test environment, we define test dependencies as code. Then, we can run our test code and disposable containers will be started and deleted after they finish. </p>



<p>Let&#8217;s take a look at the example from <a href="https://docs.spring.io/spring-boot/reference/testing/testcontainers.html" target="_blank" rel="noreferrer noopener">Spring Boot docs</a>: </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="">@Testcontainers
@SpringBootTest
class MyIntegrationTests {

  @Test
  fun myTest() {
    // ...
  }

  companion object {
    @Container
    @JvmStatic
    val neo4j = Neo4jContainer("neo4j:5");
  }
}</pre>



<p>The above code runs a Neo4j docker container before the tests. Of course, this is just an example, so most of the time, we will need to add some more config. </p>



<p>Nevertheless, we can clearly see that this Testcontainers JUnit integration allows us to achieve our goal in an easy and neat manner. </p>



<h3 class="wp-block-heading" id="h-localstack">Localstack </h3>



<p><strong>Localstack</strong>, on the other hand, is a cloud service emulator that runs in a single container. In other words, we can run AWS applications or Lambdas <strong>without connecting to the remote cloud provider</strong>. </p>



<p>And thanks to the Testcontainers module for LocalStack, we can test various AWS integrations with just a few lines of code. </p>



<p>Again, let&#8217;s take a look at the example, but this time from the LocalStack documentation: </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="">DockerImageName localstackImage = DockerImageName.parse("localstack/localstack:3.5.0");

@Rule
public LocalStackContainer localstack = new LocalStackContainer(localstackImage)
        .withServices(S3);</pre>



<p>You will find links to both documentation at the end of this article. But for now, let&#8217;s not distract ourselves and focus on what we came here for 😉</p>



<h2 class="wp-block-heading" id="h-configure-project">Configure Project</h2>



<p>If you are following my S3 series, or you already have a Spring Boot project, then those are the necessary dependencies for us today: </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="">testImplementation("org.springframework.boot:spring-boot-starter-webflux")
testImplementation("org.testcontainers:localstack")
testImplementation("org.springframework.boot:spring-boot-testcontainers")</pre>



<p>As we can see, apart from LocalStack and TestContainers, we must provide the Spring Boot Starter WebFlux. </p>



<p>But why? </p>



<p>Well, this is necessary to work with WebTestClient- a client we will use to test our web servers (REST endpoints). </p>



<p>On the other hand, if you would like to set up a project from scratch, then please navigate to the <a href="https://start.spring.io/" target="_blank" rel="noreferrer noopener">Spring Initializr</a> and select the following:</p>



<figure class="wp-block-image aligncenter size-large"><img decoding="async" width="1024" height="517" src="http://blog.codersee.com/wp-content/uploads/2024/09/codersee_integration_testing_spring_boot_aws_s3_spring_initializr_page-1024x517.png" alt="Image is a screenshot from Spring Initializr page and shows the settings necessary for integartion testing of Spring Boot and AWS S3 connection." class="wp-image-12009173" srcset="https://blog.codersee.com/wp-content/uploads/2024/09/codersee_integration_testing_spring_boot_aws_s3_spring_initializr_page-1024x517.png 1024w, https://blog.codersee.com/wp-content/uploads/2024/09/codersee_integration_testing_spring_boot_aws_s3_spring_initializr_page-300x151.png 300w, https://blog.codersee.com/wp-content/uploads/2024/09/codersee_integration_testing_spring_boot_aws_s3_spring_initializr_page-768x388.png 768w, https://blog.codersee.com/wp-content/uploads/2024/09/codersee_integration_testing_spring_boot_aws_s3_spring_initializr_page.png 1507w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>However, please keep in mind that LocalStack is not provided out of the box in Spring, so we must add it manually. </p>



<p>Moreover, as we have chosen the Spring Web, the WebFlux dependency is not present, too:</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="">testImplementation("org.springframework.boot:spring-boot-starter-webflux")
testImplementation("org.testcontainers:localstack")</pre>



<h2 class="wp-block-heading" id="h-testcontainers-singleton-approach">Testcontainers Singleton Approach</h2>



<p>With all of that being done, let&#8217;s head to the practice part. </p>



<p>When working with Testcontainers, we can configure them in various ways: </p>



<ul class="wp-block-list">
<li>we can use the<strong> JUnit extension</strong> (Jupiter integration)- which allows us to use <em>@Testcontainers</em> and <em>@Container</em> annotations and makes JUnit responsible for the automatic startup and stop of containers in our tests.</li>



<li>we can configure them <strong>manually </strong>in every test case, </li>



<li>or, alternatively, we can use the <strong>singleton approach</strong>&#8211; in which we control containers&#8217; lifecycle <a href="https://java.testcontainers.org/test_framework_integration/manual_lifecycle_control/" target="_blank" rel="noreferrer noopener">manually</a>. But, thanks to that we can easily reuse them across multiple test classes. </li>
</ul>



<p>Of course, these are not all approaches, and based on your needs you may want to configure Testcontainers differently. Nevertheless, in this tutorial, we will focus on the <strong>manual, reusable approach</strong>.</p>



<h3 class="wp-block-heading" id="h-introduce-base-class">Introduce Base Class</h3>



<p>Firstly, let&#8217;s introduce the <code>LocalStackIntegrationTest</code>:</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="">@SpringBootTest(webEnvironment = RANDOM_PORT)
class LocalStackIntegrationTest { }</pre>



<p>As we can see, we mark our class with <em>@SpringBootTest</em> &#8211; annotation necessary to run our integration tests and inject the instance of WebTestClient later in our subclasses. </p>



<h3 class="wp-block-heading" id="h-add-testcontainer">Add Testcontainer</h3>



<p>Following, let&#8217;s take a look at how to instantiate a LocalStack container:</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="">companion object {
  val localStack: LocalStackContainer = LocalStackContainer(
    DockerImageName.parse("localstack/localstack:3.7.2")
  )
}</pre>



<p>Right here, we create an instance of <strong>LocalStackContainer</strong> and we pass the name of a Docker image &#8211; <code>localstack/localstack:3.7.2</code>&#8211; to its constructor. Alternatively, if we are working only with AWS S3 Buckets service, then we can use a dedicated image- <code>localstack:s3-latest</code>. But personally, I am not a big fan of the <strong>latest</strong> tag, which can easily break our code.</p>



<p>Additionally, we put the LocalStackContainer instance in the <strong>companion object</strong>. Why? Because in the next steps, we will reference it in a function annotated with <em>@DynamicPropertySource</em>&#8211; and it must be static.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Note related to <strong>JUnit extension</strong>: </p>



<p><br>This is not the case here, as we want to take care of the container lifecycle manually, but, when using the Jupiter integration, containers declared as static fields will be shared between test methods. They will be started only once before any test method is executed and stopped after the last test method has executed. So, if in your case you pick the JUnit extension and don&#8217;t want that to happen, then you must not put the localstack in the companion object. </p>
</blockquote>



<h3 class="wp-block-heading" id="h-control-testcontainer-lifecycle">Control Testcontainer lifecycle</h3>



<p>As we already know, with this approach <strong>we are responsible</strong> for the container lifecycle control. And although this may sound complicated, it basically means that without the extension we must start the container manually.</p>



<p>So the companion object after the update will look, as follows:  </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="">companion object {
  val localStack: LocalStackContainer = LocalStackContainer(
    DockerImageName.parse("localstack/localstack:3.7.2")
  ).apply {
    start()
  }
}</pre>



<p>Basically, we use the Kotlin scope function (you can learn more about it in my <a href="https://codersee.com/the-complete-kotlin-course/">Kotlin course</a>) to invoke the <code>start()</code> function on the <code>localStack</code> instance. And as the name suggests, this function will start the container (and pull the image, if necessary).</p>



<p>And basically,<strong> that is all we need to do here.</strong> With the above code, the container will be started when the base class is loaded and shared across all inheriting test classes. </p>



<p>Of course, there is also the <code>stop()</code> function that we can invoke to kill and remove the container. </p>



<p>Nevertheless, <strong>we do not have to do it</strong>. Why? Let&#8217;s figure out. </p>



<h3 class="wp-block-heading" id="h-ryuk">Ryuk</h3>



<p>Ryuk is a kind of &#8220;garbage collector&#8221; in Testcontainers. </p>



<p>Whenever we run integration tests, Testcontianers core starts <strong>one more container</strong>: </p>



<figure class="wp-block-image size-full"><img decoding="async" width="756" height="188" src="http://blog.codersee.com/wp-content/uploads/2024/09/image.png" alt="Image is a screenshot from Docker Desktop and presents two running container: the actual one and Ryuk started by Testcontainers core to clean up after integration testing is done." class="wp-image-12009191" srcset="https://blog.codersee.com/wp-content/uploads/2024/09/image.png 756w, https://blog.codersee.com/wp-content/uploads/2024/09/image-300x75.png 300w" sizes="(max-width: 756px) 100vw, 756px" /></figure>



<p>Long story short, this container is responsible for removing containers/networks/volumes created by our test cases. So, even if we do not clean the environment ourselves- for example with the <code>stop()</code> function- the Ryuk container will take care of that. </p>



<h3 class="wp-block-heading" id="h-test-properties-with-dynamicpropertysource">Test Properties With DynamicPropertySource</h3>



<p>With that done, we need to update our environment configuration. </p>



<p>If we try to run our Spring Boot application at this point, our logic responsible for communication with Amazon S3 will try to reach the <strong>actual AWS instance</strong>. It will use the defaults, or make use of the things we configured in the <code>application.yaml</code>. </p>



<p>And this is not what we want, right? Instead, we would like to connect to the Testcontainer LocalStack instance.  </p>



<p>In some examples, you might have seen the usage of application properties files. Nevertheless, if we want to be more flexible and make use of containers started on random ports, then the <strong>@DynamicPropertySource</strong> is our best friend here: </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="">companion object {
  val localStack: LocalStackContainer = LocalStackContainer(
    DockerImageName.parse("localstack/localstack:3.7.2")
  ).apply {
    start()
  }

  @JvmStatic
  @DynamicPropertySource
  fun overrideProperties(registry: DynamicPropertyRegistry) {
    registry.add("spring.cloud.aws.region.static") { localStack.region }
    registry.add("spring.cloud.aws.credentials.access-key") { localStack.accessKey }
    registry.add("spring.cloud.aws.credentials.secret-key") { localStack.secretKey }
    registry.add("spring.cloud.aws.s3.endpoint") { localStack.getEndpointOverride(S3).toString() }
  }

}</pre>



<p>Thanks to that annotation, we can dynamically provide values to our test environment <strong>based on the LocalStack instance</strong>.</p>



<p>Of course, we must remember that methods annotated with <em>@DynamicPropertySource</em> <strong>must be static</strong>! And that&#8217;s why we use it with the @JvmStatic annotation.</p>



<h2 class="wp-block-heading" id="h-utilize-localstack-aws-cli">Utilize LocalStack AWS CLI</h2>



<p>At this point, we have our base class ready, but before we head to the tests, I would like to show you the <strong>LocalStack AWS CLI</strong> and why and how to use it.  </p>



<p>As the first step, let&#8217;s create the <code>util</code> package and add the <code>LocalStackUtil.kt</code> 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="">import org.testcontainers.containers.localstack.LocalStackContainer

fun LocalStackContainer.createBucket(bucketName: String) {
  this.execInContainer("awslocal", "s3api", "create-bucket", "--bucket", bucketName)
}

fun LocalStackContainer.deleteBucket(bucketName: String) {
  this.execInContainer("awslocal", "s3api", "delete-bucket", "--bucket", bucketName)
}

fun LocalStackContainer.deleteObject(bucketName: String, objectName: String) {
  this.execInContainer("awslocal", "s3api", "delete-object", "--bucket", bucketName, "--key", objectName)
}</pre>



<p>As we can see, we introduced 3 helper extension functions that we will later use to create and delete buckets and objects. This way, we can <strong>easily clean up</strong> between the tests (we use the shared approach, right?). Moreover, it will <strong>simplify the setup process</strong> for each test case.</p>



<p>The above code combines the <code>execInContainer</code> &#8211; which will run the passed command in our running LocalStack container, just like with the <code>docker exec</code>, and the <code>awslocal</code>&#8211; a LocalStack wrapper around the AWS CLI. So, if you&#8217;ve ever been working with the AWS command line interface, then you will see that this is 1:1. </p>



<p>Unfortunately, we must provide the command as a separate String value, because otherwise, we will end up with: </p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>OCI runtime exec failed: exec failed: container_linux.go:380: starting container process caused: exec: &#8220;awslocal s3api create-bucket &#8211;bucket bucket-1&#8221;: executable file not found in $PATH: unknown</p>
</blockquote>



<h2 class="wp-block-heading" id="h-write-integration-test-cases">Write Integration Test Cases</h2>



<p>With all of that LocalStack preparation done (I know, quite a bunch of things to learn, but once you learn this, it will be a simple copy-paste), we can finally write some integration tests for our Spring Boot S3 integration.</p>



<p>Firstly, let&#8217;s create the <code>controller</code> package and put the <code>BucketControllerIntegrationTest</code> class:</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="">class BucketControllerIntegrationTest(
  @Autowired private val webTestClient: WebTestClient
) : LocalStackIntegrationTest() { }</pre>



<p>As we can see, no annotations are required. We simply extend the <code>LocalStackIntegrationTest</code> class and inject the <code>WebTestClient</code>.</p>



<h3 class="wp-block-heading" id="h-test-no-buckets-exist">Test No Buckets Exist</h3>



<p>Nextly, let&#8217;s introduce our first test case. If we do not do anything, we expect that no buckets exist in our S3 instance: </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="">@Test
fun `Given no existing buckets When getting list of buckets Then return an empty array`() {
  val buckets = webTestClient
    .get().uri("/buckets")
    .exchange()
    .expectStatus().isOk()
    .expectBody(object : ParameterizedTypeReference&lt;List&lt;String>>() {})
    .returnResult()
    .responseBody

  assertNotNull(buckets)
  assertTrue(buckets.isEmpty())
}</pre>



<p>As mentioned before, we use the <em>WebTestClient</em> to make a GET HTTP request to the <code>/buckets</code> endpoint. Then, we use a small hack with <code>ParameterizedTypeReference</code>&#8211; because the endpoint returns a list of Strings and we use Kotlin- and we obtain the response body. </p>



<p>Lastly, we have plain assertions. We verify that the response body is not null and that our S3 bucket list is empty.</p>



<h3 class="wp-block-heading" id="h-verify-s3-bucket-exists-in-localstack">Verify S3 Bucket Exists In LocalStack</h3>



<p>Following, let&#8217;s see our helper functions in action:</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="">@Test
fun `Given one existing bucket When getting list of buckets Then return an array with expected bucket name`() {
  val bucketName = "bucket-1"
  localStack.createBucket(bucketName)

  val expectedJson = """
    [ "Bucket #1: $bucketName" ]
  """

  webTestClient
    .get().uri("/buckets")
    .exchange()
    .expectStatus().isOk()
    .expectBody()
    .json(expectedJson)

  localStack.deleteBucket(bucketName)
}</pre>



<p>This time, we utilize the <code>createBucket</code> and make sure that the <code>/buckets</code> endpoint returns the expected JSON. Please note that this is another way to assert the response body.   </p>



<p>After all, we delete the existing bucket, so it won&#8217;t affect other test cases.</p>



<h3 class="wp-block-heading" id="h-assert-bucket-created-successfully">Assert Bucket Created Successfully</h3>



<p>As the next step, let&#8217;s take a look at how we can check if our endpoint responsible for creating new S3 buckets works. And I see two paths we can go here.</p>



<p>The first one, using the <code>execInContainer</code>:</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="">@Test
fun `Given no existing buckets When creating bucket Then create bucket successfully`() {
  val bucketName = "bucket-2"
  
  webTestClient
    .post().uri("/buckets")
    .bodyValue(BucketRequest(bucketName = bucketName))
    .exchange()
    .expectStatus().isOk()
  
  val execResult = localStack.execInContainer("awslocal", "s3api", "list-buckets").stdout
  
  assertTrue(execResult.contains(bucketName))  
  localStack.deleteBucket(bucketName)
}</pre>



<p>The important thing to mention here is that the <code>execInContainer</code> returns the <code>ExecResult</code>. And thanks to that, we can read additional info, like <code>stdout</code>, <code>stderr</code>, or <code>exitCode</code>.</p>



<p>And thanks to the <code>stdout</code>, we can get this JSON to verify it contains particular bucket name (or even we could parse that to an object): </p>



<pre class="EnlighterJSRAW" data-enlighter-language="json" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">{
  "Buckets": [
    {
      "Name": "bucket-2",
      "CreationDate": "2024-09-19T05:28:42.000Z"
    }
  ],
  "Owner": {
    "DisplayName": "webfile",
    "ID": "75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a"
  }
}</pre>



<p>Alternatively, we can use the <code>/buckets</code> endpoint once again, too:</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="">@Test
fun `Given no existing buckets When creating bucket Then create bucket successfully`() {
  val bucketName = "bucket-2"

  webTestClient
    .post().uri("/buckets")
    .bodyValue(BucketRequest(bucketName = bucketName))
    .exchange()
    .expectStatus().isOk()

  val expectedJson = """
    [ "Bucket #1: $bucketName" ]
  """
  webTestClient
    .get().uri("/buckets")
    .exchange()
    .expectStatus().isOk()
    .expectBody()
    .json(expectedJson)

  localStack.deleteBucket(bucketName)
}</pre>



<h3 class="wp-block-heading" id="h-test-remaining-cases">Test Remaining Cases</h3>



<p>The remaining cases of our integration test use a more or less similar approach, so I will simply copy-paste them here so that you can analyze them. </p>



<p>At this point, I am pretty sure you understand the general idea behind what I understand by testing of Spring Boot S3 integration with LocalStack, so I don&#8217;t see the need for explaining them one- by one:</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="">@Test
fun `Given no objects existing in the bucket When getting objects of a bucket Then return an empty array`() {
  val bucketName = "bucket-3"
  localStack.createBucket(bucketName)

  val objects = webTestClient
    .get().uri("/buckets/$bucketName/objects")
    .exchange()
    .expectStatus().isOk()
    .expectBody(object : ParameterizedTypeReference&lt;List&lt;String>>() {})
    .returnResult()
    .responseBody

  assertNotNull(objects)
  assertTrue(objects.isEmpty())

  localStack.deleteBucket(bucketName)
}

@Test
fun `Given no objects When creating example object Then return created object`() {
  val bucketName = "bucket-4"
  val objectName = "example.json"
  localStack.createBucket(bucketName)

  val expectedJson = """
    {
      "id": "123",
      "name": "Some name"
    }
  """

  webTestClient
    .post().uri("/buckets/$bucketName/objects")
    .exchange()
    .expectStatus().isOk()
    .expectBody()
    .json(expectedJson)

  localStack.deleteObject(bucketName, objectName)
  localStack.deleteBucket(bucketName)
}

@Test
fun `Given created object When getting list of objects Then return array with one object`() {
  val bucketName = "bucket-5"
  val objectName = "example.json"
  localStack.createBucket(bucketName)

  val expectedJson = """
    [ $objectName ]
  """

  webTestClient
    .post().uri("/buckets/$bucketName/objects")
    .exchange()
    .expectStatus().isOk()

  webTestClient
    .get().uri("/buckets/$bucketName/objects")
    .exchange()
    .expectStatus().isOk()
    .expectBody()
    .json(expectedJson)

  localStack.deleteObject(bucketName, objectName)
  localStack.deleteBucket(bucketName)
}

@Test
fun `Given existing object When getting object by key Then return object content`() {
  val bucketName = "bucket-6"
  val objectName = "example.json"
  localStack.createBucket(bucketName)

  val expected = """
    {
      "id": "123",
      "name": "Some name"
    }
  """

  webTestClient
    .post().uri("/buckets/$bucketName/objects")
    .exchange()

  webTestClient
    .get().uri("/buckets/$bucketName/objects/$objectName")
    .exchange()
    .expectStatus().isOk()
    .expectBody()
    .json(expected)

  localStack.deleteObject(bucketName, objectName)
  localStack.deleteBucket(bucketName)
}

@Test
fun `Given existing bucket with object When deleting bucket Then bucket is removed`() {
  val bucketName = "bucket-7"
  localStack.createBucket(bucketName)

  webTestClient
    .post().uri("/buckets/$bucketName/objects")
    .exchange()
    .expectStatus().isOk()

  webTestClient
    .delete().uri("/buckets/$bucketName")
    .exchange()
    .expectStatus().isOk()

  val buckets = webTestClient
    .get().uri("/buckets")
    .exchange()
    .expectStatus().isOk()
    .expectBody(object : ParameterizedTypeReference&lt;List&lt;String>>() {})
    .returnResult()
    .responseBody

  assertNotNull(buckets)
  assertTrue(buckets.isEmpty())
}</pre>



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



<p>And that is all for this tutorial, in which we learned how to implement <strong>integration tests </strong>for <strong>Spring Boot AWS S3 </strong>integration with <strong>LocalStack </strong>and <strong>Testcontainers.</strong></p>



<p>I hope you enjoyed it and for the source code, please visit <a href="https://github.com/codersee-blog/spring-boot-3-kotlin-aws-s3-localstack-testcontainers" target="_blank" rel="noreferrer noopener">this GitHub repository</a>. </p>



<p>Have a great day and see you in the next articles! 🙂 </p>
<p>The post <a href="https://blog.codersee.com/test-spring-boot-aws-s3-with-localstack-and-testcontainers/">Test Spring Boot AWS S3 with Localstack and Testcontainers</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/test-spring-boot-aws-s3-with-localstack-and-testcontainers/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Spring Boot with Kotlin, AWS S3, and S3Template</title>
		<link>https://blog.codersee.com/spring-boot-with-kotlin-aws-s3-and-s3template/</link>
					<comments>https://blog.codersee.com/spring-boot-with-kotlin-aws-s3-and-s3template/#respond</comments>
		
		<dc:creator><![CDATA[Piotr]]></dc:creator>
		<pubDate>Tue, 17 Sep 2024 05:00:00 +0000</pubDate>
				<category><![CDATA[Spring]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[S3 Object Storage]]></category>
		<category><![CDATA[S3Template]]></category>
		<category><![CDATA[Spring Boot]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=12009151</guid>

					<description><![CDATA[<p>The second article in a series dedicated to Spring Boot AWS S3 integration focused on S3Template and Kotlin.</p>
<p>The post <a href="https://blog.codersee.com/spring-boot-with-kotlin-aws-s3-and-s3template/">Spring Boot with Kotlin, AWS S3, and S3Template</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Welcome to the <strong>second article</strong> in a series dedicated to integrating a Spring Boot Kotlin app with <strong>AWS S3 </strong>Object Storage, in which we will learn how to make our lives easier with <strong>S3Template</strong>. </p>



<p>Of course, I highly encourage you to take a look at other articles in this series, too: </p>



<ul class="wp-block-list">
<li><a href="https://blog.codersee.com/spring-boot-aws-s3-s3client-kotlin/">#1 Spring Boot with AWS S3, S3Client, and Kotlin</a></li>



<li>#2 (This article)</li>



<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>



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



<p>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-with-kotlin-aws-s3-and-s3template/"><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>If you find this content useful,<strong> please leave a subscription</strong> 🙂</p>



<h2 class="wp-block-heading" id="h-what-is-s3template">What is S3Template? </h2>



<p>Before we get our hands dirty with the code, let&#8217;s take a second to understand better with S3Template is and how it can make our lives easier when integrating a Spring Boot app with S3. </p>



<p>Let me quote the S3Template documentation here: </p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Higher level abstraction over S3Client providing methods for the most common use cases.</p>



<p>So, if you have already worked with, or you have seen my previous article with S3Client, then you saw that simple operations require some boilerplate. And that is exactly what S3Template solves. </p>



<p>And as a note from my end, I just wanted to note S3Template handles only some subset of S3Client operations, so in our projects those two will rather coexist, instead of being each others alternatives. </p>
</blockquote>



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



<p>With all of that said, let&#8217;s get to work. </p>



<p>Let&#8217;s add the <code>controller</code> package and <code>BucketController</code> class to it. We will use it to expose a bunch of endpoints triggering various operations on S3 buckets and files. </p>



<p>When it comes to the operations- we will use the same ones as in the previous article, and as the last one, I will show you how to <strong>serialize and deserialize objects with AWS S3 and S3Template.</strong></p>



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



<p>Although the S3Template does not expose any method that would help us with this task, I wanted to mention it as we discussed it in the previous tutorial. </p>



<p>Additionally, this is a great example of <code>S3Client</code> and <code>S3Template</code> co-existence: </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 s3Template: S3Template,
  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>As we can see, not too much S3Template could improve here, so I bet this is the reason why it was not introduced. </p>



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



<p>Nextly, let&#8217;s take a look at how we can create a brand new 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="">@PostMapping
fun createBucket(@RequestBody request: BucketRequest) {
  s3Template.createBucket(request.bucketName)
}

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



<p>As we can see, no additional request classes- the only thing we need is the bucket name. </p>



<p>Of course, let&#8217;s rerun our application and verify if everything is working: </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 POST 'http://localhost:8080/buckets' \
--header 'Content-Type: application/json' \
--data-raw '{
    "bucketName": "codersee-awesome-bucket"
}'</pre>



<p>As a result, we should get 200 OK without a response body. </p>



<h3 class="wp-block-heading" id="h-upload-file-to-s3-bucket">Upload File to S3 Bucket</h3>



<p>Nextly, let&#8217;s take a look at how to upload a new file to S3. </p>



<p>We have a few options, among which the <code>store</code> function is the easiest: </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("/{bucketName}/objects")
fun createObject(@PathVariable bucketName: String, @RequestBody request: ObjectRequest) {
  s3Template.store(bucketName, request.objectName, request.content)
}

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



<p>As we can see, this function takes three arguments: </p>



<ul class="wp-block-list">
<li>the bucket name, </li>



<li>filename,</li>



<li>and the content to upload (to be specific <code>Object object</code>) </li>
</ul>



<p>Alternatively, we could use the <code>upload</code> function, which allows us to send <code>InputStream</code> instance and metadata: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="java" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@Override
public S3Resource upload(
  String bucketName, 
  String key, 
  InputStream inputStream,
  @Nullable ObjectMetadata objectMetadata
) </pre>



<p>Lastly, let&#8217;s verify that everything is fine with the following curl: </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 POST 'http://localhost:8080/buckets/codersee-awesome-bucket/objects' \
--header 'Content-Type: application/json' \
--data-raw '{
    "objectName": "file-example.txt",
    "content": "My file content"
}'</pre>



<p>If everything worked, then a new file should be present in our bucket 🙂 </p>



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



<p>Nextly, let&#8217;s see how we can list the bucket content with <em>S3Template</em>: </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> =
  s3Template.listObjects(bucketName, "")
    .map { s3Resource -> s3Resource.filename }</pre>



<p>We can clearly see that this is not rocket science 😉 </p>



<p>Nevertheless, it is worth mentioning that this time we get the <code>S3Resource</code> instance and instead of the <code>key()</code> we use it <code>getFilename</code> method. </p>



<p>And just like previously, let&#8217;s see the endpoint in action: </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 POST 'http://localhost:8080/buckets/codersee-awesome-bucket/objects' \
--header 'Content-Type: application/json' \
--data-raw '{
    "objectName": "file-example.txt",
    "content": "My file content"
}'

# Response status: 200 OK
# Response body: 
[
    "file-example.txt"
]</pre>



<h3 class="wp-block-heading" id="h-download-a-file">Download a File</h3>



<p>So what about fetching files from the S3 bucket? </p>



<p>With <em>S3Template</em>, it&#8217;s a piece of cake: </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 =
  s3Template.download(bucketName, objectName).getContentAsString(UTF_8)</pre>



<p>We specify the bucket name and the object name, and as a result, we get the <code>S3Resource</code> that exposes a bunch of methods. Among others, the <code>getContentAsString</code> that is pretty descriptive 😉 </p>



<p>Similarly, let&#8217;s hit 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/buckets/codersee-awesome-bucket/objects/file-example.txt'

# Response status: 200 OK
# Response body: 
"My file content"</pre>



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



<p>Last before least, let&#8217;s take a look at how we can get rid of the 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) {
  s3Template.listObjects(bucketName, "")
    .forEach { s3Template.deleteObject(bucketName, it.filename) }

  s3Template.deleteBucket(bucketName)
}</pre>



<p>And just like in the previous article- we must ensure the bucket does not contain any objects. </p>



<p>To do so, we list out objects by specifying the bucket name and objects prefix (as we don&#8217;t have any, we pass an empty String). Then, for each object, we use its key to delete it. And lastly, we simply invoke the <code>deleteBucket</code> by passing the name of a bucket to delete. </p>



<p>Of course, let&#8217;s verify this logic, too: </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 DELETE 'http://localhost:8080/buckets/codersee-awesome-bucket'</pre>



<p>If we run this command and the S3 bucket exists, then we should see 200 OK and our bucket will disappear. </p>



<h3 class="wp-block-heading" id="h-serialize-deserialize-objects">Serialize/Deserialize objects </h3>



<p>As the last thing, let&#8217;s take a look at how easily we can persist objects using the combination of <code>store</code> and <code>read</code> functions: </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("/{bucketName}/objects")
fun createExampleObject(@PathVariable bucketName: String): Example {
  val example = Example(id = UUID.randomUUID(), name = "Some name")

  s3Template.store(bucketName, "example.json", example)

  return s3Template.read(bucketName, "example.json", Example::class.java)
}

data class Example(val id: UUID, val name: String)</pre>



<p>As we can see, we made a small update to our <code>POST /{bucketName}/objects</code> endpoint logic. </p>



<p>Basically, the first part is exactly the same, we use the <code>store</code> again to push the file to the bucket. </p>



<p>Nevertheless, instead of the <code>download</code> we saw previously, we use the <code>read</code> function that uses the <code>S3ObjectConverter</code> that will automatically deserialize the JSON into the <code>Example</code> class instance. </p>



<p>And for the last time, let&#8217;s hit our API: </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 POST 'http://localhost:8080/buckets/codersee-awesome-bucket/objects' \
--data-raw ''

# Response status: 200 OK 
# Response body: 
{
    "id": "3eacd8a3-48b2-4756-86db-e4c9f4e291da",
    "name": "Some name"
}</pre>



<p>And as we can see, the output confirms that everything is working fine. </p>



<p>Summary</p>



<p>And that&#8217;s all for this article on how to make our lives easier in Spring Boot with S3Template. </p>



<p>I hope you enjoyed it, and again wanted to invite you to take a look at other content of this series: </p>



<ul class="wp-block-list">
<li><a href="https://blog.codersee.com/spring-boot-aws-s3-s3client-kotlin/">#1 Spring Boot with AWS S3, S3Client, and Kotlin</a></li>



<li>#2 (This article)</li>



<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>Lastly, just wanted to show that you can find the source code in <a href="https://github.com/codersee-blog/spring-boot-3-kotlin-s3template" target="_blank" rel="noreferrer noopener">this GitHub repository</a> and that you can join my <a href="https://codersee.com/newsletter/">newsletter</a> to stay up-to-date with Kotlin on the backend. </p>
<p>The post <a href="https://blog.codersee.com/spring-boot-with-kotlin-aws-s3-and-s3template/">Spring Boot with Kotlin, AWS S3, and S3Template</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-with-kotlin-aws-s3-and-s3template/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<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>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>What can you expect today? </p>



<p>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>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>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>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>If you already have your Spring Boot project prepared, then feel free to skip this step. </p>



<p>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 loading="lazy" 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="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>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>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>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><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>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>Why do I pick it over the AWS Java SDK? </p>



<p>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>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>Again, feel free to skip it if you are looking for Spring Boot details.</p>
</blockquote>



<p>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 loading="lazy" 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="auto, (max-width: 1008px) 100vw, 1008px" /></figure>



<p>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 loading="lazy" 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="auto, (max-width: 831px) 100vw, 831px" /></figure>



<p>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>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>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>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>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>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>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>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>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>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>Or even: </p>



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



<p>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>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>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>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>So, again, if we had the <code>.aws</code> credentials set, then this value came from there😉</p>



<p>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>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>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>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>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>Nevertheless, <strong>I wanted to emphasize one, important thing</strong>. </p>



<p>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>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>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>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>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>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>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>So at this point, we know how to create buckets in AWS. Nevertheless, we use them to <strong>organise uploaded files</strong>. </p>



<p>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>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>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>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>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>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>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>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>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>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>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>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>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>As we can see, this time, we must perform our actions in a few steps.</p>



<p>Why? </p>



<p>The reason is simple: </p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>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>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>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>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>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>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>
		<item>
		<title>Sending Spring Boot Metrics to Datadog</title>
		<link>https://blog.codersee.com/sending-spring-boot-metrics-datadog/</link>
					<comments>https://blog.codersee.com/sending-spring-boot-metrics-datadog/#respond</comments>
		
		<dc:creator><![CDATA[Piotr]]></dc:creator>
		<pubDate>Tue, 27 Aug 2024 04:00:00 +0000</pubDate>
				<category><![CDATA[Spring]]></category>
		<category><![CDATA[Datadog]]></category>
		<category><![CDATA[Metrics]]></category>
		<category><![CDATA[Micrometer]]></category>
		<category><![CDATA[Monitoring]]></category>
		<category><![CDATA[Spring Boot]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=11009048</guid>

					<description><![CDATA[<p>A step-by-step guide on how to implement and configure a Spring Boot application to send metrics to Datadog.</p>
<p>The post <a href="https://blog.codersee.com/sending-spring-boot-metrics-datadog/">Sending Spring Boot Metrics to Datadog</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Hello and welcome to the next article in which we will briefly discuss Datadog and Micrometer, but what&#8217;s more important- we will learn how to send <strong>Spring Boot metrics</strong> to <strong>Datadog in practice</strong>.</p>



<p>Moreover, we will take a quick look into the Spring Boot Actuator and how it can help us, so, to let&#8217;s not waste our time and let&#8217;s get to work! 🙂 </p>



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



<p>If you prefer <strong>video content</strong>, then check out my video:</p>



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



<p>If you find this content useful,<strong> please leave a subscription</strong> 🙂</p>



<h2 class="wp-block-heading" id="h-datadog">Datadog</h2>



<p>If you came here directly, then I bet you already know what <a href="https://www.datadoghq.com/" target="_blank" rel="noreferrer noopener">Datadog</a> is and what purpose it will serve in your project.</p>



<p>However, for those who hear about it for the first time, let me give you a really short intro.</p>



<p>Long story short- Datadog is an <strong>integrated platform for monitoring &amp; security</strong>. It provides tools for monitoring applications, databases, distributed tracing, logs management, and many many more. In other words, makes all the things that are not so cool, but necessary in production-ready environments, a bit less painful 😉</p>



<p>However, what is the most important for us today- it provides tools to <strong>collect metrics</strong> from our applications so that later we can make use of them.</p>



<p>But, what are they? </p>



<h2 class="wp-block-heading" id="h-what-are-metrics">What Are Metrics? </h2>



<p>According to the Datadog documentation:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Metrics are numerical values that can track anything about your environment over time.</p>
</blockquote>



<p>And I must admit- I love this definition for its completeness in its simplicity.</p>



<p>Because basically, <strong>metric</strong> is everything in our system that we can assign a number to and track. From Garbage Collector stats, JVM memory, CPU, and request latency, to the average cart size. Literally everything.</p>



<p>And in this tutorial, we will see how to send metrics <strong>collected automatically</strong> by our Spring Boot app to DataDog.</p>



<h2 class="wp-block-heading" id="h-create-new-project">Create New Project</h2>



<p>As the first step, let&#8217;s navigate to the <a href="https://start.spring.io/" target="_blank" rel="noreferrer noopener">Spring Initializr page</a>, select the following and import the project to our IDE:</p>



<figure class="wp-block-image aligncenter size-large is-resized"><img loading="lazy" decoding="async" width="1024" height="511" src="http://blog.codersee.com/wp-content/uploads/2024/08/spring_boot_datadog_actuator_1-1024x511.png" alt="Screenshot presents the Spring Initializr page with Spring Boot Actuator and Spring Web dependencies selected." class="wp-image-11009063" style="width:840px;height:auto" srcset="https://blog.codersee.com/wp-content/uploads/2024/08/spring_boot_datadog_actuator_1-1024x511.png 1024w, https://blog.codersee.com/wp-content/uploads/2024/08/spring_boot_datadog_actuator_1-300x150.png 300w, https://blog.codersee.com/wp-content/uploads/2024/08/spring_boot_datadog_actuator_1-768x383.png 768w, https://blog.codersee.com/wp-content/uploads/2024/08/spring_boot_datadog_actuator_1.png 1527w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>Apart from versions and metadata, we can see that we added two dependencies: <strong>Spring Web</strong> and <strong>Spring Boot Actuator.</strong> </p>



<p>The first one is quite obvious, but in terms of metrics Spring Boot Actuator provides dependency management and auto-configuration for <strong>Micrometer</strong> &#8211; a vendor-neutral application observability facade. </p>



<p>With that, Spring Boot takes care of configuring <code>MeterRegistry</code> and the only thing we need to do is to add the <code>micrometer-registry-{system}</code> dependency to the <code>build.gradle.kts</code> :</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.micrometer:micrometer-registry-datadog")</pre>



<p><strong>Note:</strong> we could achieve exactly the same using the Spring Initializr page and searching for the Datadog dependency, but I wanted to emphasize the fact, that other vendors, like Elastic, or OpenTelemetry can be added in the same manner.  </p>



<h2 class="wp-block-heading" id="h-verify-and-expose-metrics-endpoint">Verify and Expose Metrics Endpoint</h2>



<p>After we import our project, let&#8217;s run it, and let&#8217;s take a look at the endpoints exposed by default by Spring Boot Actuator: </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/actuator' </pre>



<p>As a result, we should see the following: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="json" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">{
    "_links": {
        "self": {
            "href": "http://localhost:8080/actuator",
            "templated": false
        },
        "health": {
            "href": "http://localhost:8080/actuator/health",
            "templated": false
        },
        "health-path": {
            "href": "http://localhost:8080/actuator/health/{*path}",
            "templated": true
        }
    }
}</pre>



<p>And the first lesson is that <strong>the metrics endpoint is not exposed by default.</strong></p>



<p>So to change that, let&#8217;s navigate to the <code>application.yaml</code> and add the following:</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="">management:
  endpoints:
    web:
      exposure:
        include: metrics, health</pre>



<p>After that, let&#8217;s rerun the application. </p>



<p>As a result, this time, we should see that two new endpoints are present: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="json" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">{
    "_links": {
        "self": {
            "href": "http://localhost:8080/actuator",
            "templated": false
        },
        "health": {
            "href": "http://localhost:8080/actuator/health",
            "templated": false
        },
        "health-path": {
            "href": "http://localhost:8080/actuator/health/{*path}",
            "templated": true
        },
        "metrics-requiredMetricName": {
            "href": "http://localhost:8080/actuator/metrics/{requiredMetricName}",
            "templated": true
        },
        "metrics": {
            "href": "http://localhost:8080/actuator/metrics",
            "templated": false
        }
    }
}</pre>



<p>The last one- <code>/actuator/metrics</code>&#8211; simply <strong>displays a list of available meter names</strong>, and when we invoke it, we should see the following JSON response: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="json" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">{
    "names": [
        "application.ready.time",
        "application.started.time",
        "disk.free",
        "disk.total",
        "executor.active",
        "executor.completed",
        "executor.pool.core",
        "executor.pool.max",
        "executor.pool.size",
        "executor.queue.remaining",
        "executor.queued",
        "http.server.requests",
        "http.server.requests.active",
        "jvm.buffer.count",
        "jvm.buffer.memory.used",
        "jvm.buffer.total.capacity",
        "jvm.classes.loaded",
        "jvm.classes.unloaded",
        "jvm.compilation.time",
        "jvm.gc.concurrent.phase.time",
        "jvm.gc.live.data.size",
        "jvm.gc.max.data.size",
        "jvm.gc.memory.allocated",
        "jvm.gc.memory.promoted",
        "jvm.gc.overhead",
        "jvm.gc.pause",
        "jvm.info",
        "jvm.memory.committed",
        "jvm.memory.max",
        "jvm.memory.usage.after.gc",
        "jvm.memory.used",
        "jvm.threads.daemon",
        "jvm.threads.live",
        "jvm.threads.peak",
        "jvm.threads.started",
        "jvm.threads.states",
        "logback.events",
        "process.cpu.time",
        "process.cpu.usage",
        "process.start.time",
        "process.uptime",
        "system.cpu.count",
        "system.cpu.usage",
        "tomcat.sessions.active.current",
        "tomcat.sessions.active.max",
        "tomcat.sessions.alive.max",
        "tomcat.sessions.created",
        "tomcat.sessions.expired",
        "tomcat.sessions.rejected"
    ]
}</pre>



<p>In simple words, this is the list of all metrics collected at this point by our Spring Boot application. And yes- we will be able to export them to Datadog. </p>



<p>Nevertheless, let&#8217;s verify what the second endpoint- <code>/actuator/metrics/{requiredMetricName}</code>&#8211; does.</p>



<p>To do that, let&#8217;s make a GET request and specify any metric name:</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/actuator/metrics/process.cpu.usage'</pre>



<p>This time, the endpoint will return metric details along with the <strong>current value</strong>:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="json" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">{
    "name": "process.cpu.usage",
    "description": "The \"recent cpu usage\" for the Java Virtual Machine process",
    "measurements": [
        {
            "statistic": "VALUE",
            "value": 0.038209510879134205
        }
    ],
    "availableTags": []
}</pre>



<h2 class="wp-block-heading" id="h-create-datadog-account">Create Datadog Account</h2>



<p>Excellent! At this point, we are sure that our Spring Boot app collects metrics, so it&#8217;s time to proceed with the Datadog part. </p>



<p>If you haven&#8217;t done it yet, then let&#8217;s create a trial account together. </p>



<p>Let&#8217;s navigate to the <a href="https://www.datadoghq.com/">https://www.datadoghq.com/</a> and click the &#8220;Free trial&#8221; button:</p>



<figure class="wp-block-image aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="456" src="http://blog.codersee.com/wp-content/uploads/2024/08/spring_boot_datadog_free_trial_2-1024x456.png" alt="Screenshot presents the main page of Datadog site with free trial button." class="wp-image-11009071" srcset="https://blog.codersee.com/wp-content/uploads/2024/08/spring_boot_datadog_free_trial_2-1024x456.png 1024w, https://blog.codersee.com/wp-content/uploads/2024/08/spring_boot_datadog_free_trial_2-300x134.png 300w, https://blog.codersee.com/wp-content/uploads/2024/08/spring_boot_datadog_free_trial_2-768x342.png 768w, https://blog.codersee.com/wp-content/uploads/2024/08/spring_boot_datadog_free_trial_2.png 1397w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>Following, let&#8217;s fill out the signup form: </p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="532" height="867" src="http://blog.codersee.com/wp-content/uploads/2024/08/spring_boot_datadog_sign_up_form_3_1.png" alt="Image is a screenshot of the Datadog signup form" class="wp-image-11009072" srcset="https://blog.codersee.com/wp-content/uploads/2024/08/spring_boot_datadog_sign_up_form_3_1.png 532w, https://blog.codersee.com/wp-content/uploads/2024/08/spring_boot_datadog_sign_up_form_3_1-184x300.png 184w" sizes="auto, (max-width: 532px) 100vw, 532px" /></figure>



<p>And with that done, when we confirm our e-mail address, we will land on this page: </p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="960" height="1006" src="http://blog.codersee.com/wp-content/uploads/2024/08/spring_boot_datadog_key_4.png" alt="Screenshot presents the Datadog agent setup page. " class="wp-image-11009073" srcset="https://blog.codersee.com/wp-content/uploads/2024/08/spring_boot_datadog_key_4.png 960w, https://blog.codersee.com/wp-content/uploads/2024/08/spring_boot_datadog_key_4-286x300.png 286w, https://blog.codersee.com/wp-content/uploads/2024/08/spring_boot_datadog_key_4-768x805.png 768w" sizes="auto, (max-width: 960px) 100vw, 960px" /></figure>



<p>As we can see, the above page contains a bunch of guides on how to set up the agent. </p>



<p>And although we are not interested in any of those, this page has one, valuable information for us- the <strong>API key</strong>. </p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><strong>Note:</strong> I think that readers of this blog are well aware of that, but for my own peace of mind- API keys are vulnerable data, so you <strong>must be cautious</strong> with them! 🙂 </p>
</blockquote>



<p>At this point, please copy this value and we will use it in the proceeding steps. </p>



<h2 class="wp-block-heading" id="h-configure-app-to-send-metrics">Configure App To Send Metrics</h2>



<p>When we have our Datadog account ready, we can finally configure our app. </p>



<h3 class="wp-block-heading" id="h-api-key">API Key</h3>



<p>Firstly, let&#8217;s get back to our Spring Boot project and update 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="">management:
  endpoints:
    web:
      exposure:
        include: metrics, health
  datadog:
    metrics:
      export:
        api-key: YOUR_KEY_VALUE</pre>



<p>Of course, we must replace the <code>YOUR_KEY_VALUE</code> with the actual API key. </p>



<h3 class="wp-block-heading" id="h-update-datadog-uri">Update Datadog URI </h3>



<p>If you are using Datadog US (the option you could select when signing up), then that&#8217;s pretty much it and you can restart the application.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>By default, metrics are sent to the Datadog US site (api.datadoghq.com). If your Datadog project is hosted on one of the other sites, or you need to send metrics through a proxy, configure the URI accordingly.</p>
</blockquote>



<p>So given the above, if we use another region, then we need to add one more line: </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="">management:
  endpoints:
    web:
      exposure:
        include: metrics, health
  datadog:
    metrics:
      export:
        api-key: YOUR_KEY_VALUE 
        uri: https://api.datadoghq.eu</pre>



<h3 class="wp-block-heading" id="h-update-metrics-upload-interval">Update Metrics Upload Interval</h3>



<p>Following, let&#8217;s see how easily we can update the interval, in which data are sent to Datadog: </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="">management:
  endpoints:
    web:
      exposure:
        include: metrics, health
  datadog:
    metrics:
      export:
        api-key: YOUR_KEY_VALUE 
        uri: https://api.datadoghq.eu
        step: 10s</pre>



<p>The default value is <strong>30 seconds</strong>. And to change that, the only thing we need to do is to add the <code>step</code> value. </p>



<h3 class="wp-block-heading" id="h-set-spring-logging-level-to-debug">Set Spring Logging Level to DEBUG</h3>



<p>As the last thing, which technically is not directly related to sending metrics, I would like to add the following to the root of our <code>application.yaml</code> file: </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="">logging:
  level:
    root: DEBUG</pre>



<p>This way we set the logging level in our Spring Boot application to <code>DEBUG</code>. </p>



<p>Why? It will be useful in a moment when we will be testing our implementation. </p>



<h2 class="wp-block-heading" id="h-testing">Testing </h2>



<p>With all of that done, we can finally verify that everything is working properly. </p>



<h3 class="wp-block-heading" id="h-verify-spring-boot-sends-data-to-datadog">Verify Spring Boot Sends Data to Datadog</h3>



<p>Firstly, let&#8217;s rerun our application and take a look at the logs.</p>



<p>If we see the following: </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="">i.m.datadog.DatadogMeterRegistry         : successfully sent 78 metrics to datadog</pre>



<p>Then data are successfully sent to the Datadog API. </p>



<p>Let&#8217;s leave it running for some time (like 2 minutes, or something), so that we have actual data later to work with 🙂 </p>



<h2 class="wp-block-heading" id="h-review-metrics-in-datadog">Review Metrics in Datadog </h2>



<p>After that time, the only thing left is to take a look at metrics in Datadog. </p>



<p>If we haven&#8217;t closed the Datadog &#8220;Agent Setup&#8221; page, then it should automatically close and redirect us to the dashboard. </p>



<p>However, if that didn&#8217;t happen, then no worries, we can simply navigate to the <a href="https://app.datadoghq.eu/metric/summary" target="_blank" rel="noreferrer noopener">metrics summary page</a>: </p>



<figure class="wp-block-image aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="733" src="http://blog.codersee.com/wp-content/uploads/2024/08/spring_boot_datadog_metrics_summary_5-1024x733.png" alt="Screenshot presents the Datadog metrics summary page where all Spring Boot metrics are displayed." class="wp-image-11009076" srcset="https://blog.codersee.com/wp-content/uploads/2024/08/spring_boot_datadog_metrics_summary_5-1024x733.png 1024w, https://blog.codersee.com/wp-content/uploads/2024/08/spring_boot_datadog_metrics_summary_5-300x215.png 300w, https://blog.codersee.com/wp-content/uploads/2024/08/spring_boot_datadog_metrics_summary_5-768x550.png 768w, https://blog.codersee.com/wp-content/uploads/2024/08/spring_boot_datadog_metrics_summary_5.png 1350w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>On this page, we can see all the metrics that have been gathered so far. Long story short, this page contains meta-information about metrics themselves, like tags, etc. </p>



<p>I strongly recommend you spend here some time and figure out different options. </p>



<p>And if we would like to take a look at the actual values, then let&#8217;s navigate to the <a href="https://app.datadoghq.eu/metric/explorer" target="_blank" rel="noreferrer noopener">metrics explorer</a>: </p>



<figure class="wp-block-image aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="249" src="http://blog.codersee.com/wp-content/uploads/2024/08/spring_boot_datadog_metrics_explorer_6-1024x249.png" alt="Screenshot presents Datadog metrics explorer with jvm memory used metric chart." class="wp-image-11009077" srcset="https://blog.codersee.com/wp-content/uploads/2024/08/spring_boot_datadog_metrics_explorer_6-1024x249.png 1024w, https://blog.codersee.com/wp-content/uploads/2024/08/spring_boot_datadog_metrics_explorer_6-300x73.png 300w, https://blog.codersee.com/wp-content/uploads/2024/08/spring_boot_datadog_metrics_explorer_6-768x187.png 768w, https://blog.codersee.com/wp-content/uploads/2024/08/spring_boot_datadog_metrics_explorer_6-1536x373.png 1536w, https://blog.codersee.com/wp-content/uploads/2024/08/spring_boot_datadog_metrics_explorer_6-2048x498.png 2048w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>I know, the image is quite small (the &#8220;Open Image in New Tab&#8221; option will help here 😉 ). However, the only thing we need to do here is to specify the metric name in the left upper part and the period we would like to view in the right upper part. </p>



<p>After that, we will see the above chart. </p>



<p>And basically, that&#8217;s how metrics work with Spring Boot and Datadog, but please don&#8217;t close this article yet, I wanted to show you two, interesting things that may be useful to you in real life. </p>



<h2 class="wp-block-heading" id="h-sending-custom-tags">Sending Custom Tags </h2>



<p><strong>Tags</strong> are nothing else, than additional metadata you can send with metrics.  </p>



<p>Why are they useful? Well, thanks to them we can later differentiate different metrics. For example, we can send a tag with the application name, so that later we will group metrics by microservices, or environment value, like dev, staging, or prod. </p>



<p>In Spring Boot, we have several ways to achieve that. And probably the easiest way is to (again) update 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="">management:
  endpoints:
    web:
      exposure:
        include: metrics, health
  datadog:
    metrics:
      export:
        api-key: YOUR_KEY_VALUE 
        uri: https://api.datadoghq.eu
        step: 10s
  metrics:
    tags:
      application: "my-app-one"</pre>



<p>As we can see, we can add a custom tag by using  <code>metrics.tags</code> and specifying that as a key-value pair. In our case- the <code>application</code> tag has the <code>my-app-one</code> value. </p>



<p>This approach is not only straightforward but also quite flexible. We could always put a placeholder for the environment variable instead of the hardcoded value. </p>



<p>Anyway, when we rerun our application and let Spring send some metrics to the Datadog, we will see that from now on we can <strong>filter values using the &#8220;from&#8221; field:</strong></p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="281" src="http://blog.codersee.com/wp-content/uploads/2024/08/spring_boot_datadog_metrics_explorer_custom_tag_7-1024x281.png" alt="" class="wp-image-11009080" srcset="https://blog.codersee.com/wp-content/uploads/2024/08/spring_boot_datadog_metrics_explorer_custom_tag_7-1024x281.png 1024w, https://blog.codersee.com/wp-content/uploads/2024/08/spring_boot_datadog_metrics_explorer_custom_tag_7-300x82.png 300w, https://blog.codersee.com/wp-content/uploads/2024/08/spring_boot_datadog_metrics_explorer_custom_tag_7-768x210.png 768w, https://blog.codersee.com/wp-content/uploads/2024/08/spring_boot_datadog_metrics_explorer_custom_tag_7-1536x421.png 1536w, https://blog.codersee.com/wp-content/uploads/2024/08/spring_boot_datadog_metrics_explorer_custom_tag_7-2048x561.png 2048w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>Again, the &#8220;Open Image in New Tab&#8221; may be helpful here 😉 </p>



<h2 class="wp-block-heading" id="h-turn-off-metrics-export">Turn Off Metrics Export</h2>



<p>As the last thing, let&#8217;s take a look at how we can instruct our Spring app to <strong>stop sending metrics to Datadog</strong>.</p>



<p>Again, let&#8217;s navigate to the <code>application.yaml</code> file and set the <code>enabled</code> flag to false: </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="">management:
  endpoints:
    web:
      exposure:
        include: metrics, health
  datadog:
    metrics:
      export:
        enabled: false
        api-key: YOURE_KEY_VALUE
        uri: https://api.datadoghq.eu
        step: 10s</pre>



<p>Of course, we can use an environment variable placeholder (like <code>${DATADOG_ENABLED}</code>) and turn on/off export in different environments. </p>



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



<p>That&#8217;s all for this tutorial on <strong>how to send Spring Boot metrics to Datadog</strong>. </p>



<p>As always, you can find the source code in <a href="https://github.com/codersee-blog/spring-boot-3-kotlin-datadog" target="_blank" rel="noreferrer noopener">this GitHub repository</a>.</p>



<p>Have a great day and don&#8217;t forget to <strong>leave a comment</strong>! 😀</p>
<p>The post <a href="https://blog.codersee.com/sending-spring-boot-metrics-datadog/">Sending Spring Boot Metrics to Datadog</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/sending-spring-boot-metrics-datadog/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Integration Tests for WebClient with WireMock</title>
		<link>https://blog.codersee.com/integration-tests-webclient-wiremock/</link>
					<comments>https://blog.codersee.com/integration-tests-webclient-wiremock/#comments</comments>
		
		<dc:creator><![CDATA[Piotr]]></dc:creator>
		<pubDate>Wed, 28 Feb 2024 10:56:57 +0000</pubDate>
				<category><![CDATA[Spring]]></category>
		<category><![CDATA[JUnit 5]]></category>
		<category><![CDATA[Spring Boot]]></category>
		<category><![CDATA[Testing]]></category>
		<category><![CDATA[WebClient]]></category>
		<category><![CDATA[WireMock]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=9508747</guid>

					<description><![CDATA[<p>Learn how to perform integration testing for Spring WebClient that invokes external REST APIs with WireMock and JUnit 5.</p>
<p>The post <a href="https://blog.codersee.com/integration-tests-webclient-wiremock/">Integration Tests for WebClient with WireMock</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Hi guys! 🙂 Today we will learn how to do <strong>integration testing</strong> for <strong>Spring WebClient</strong> that invokes external REST APIs with <strong>WireMock</strong> and <strong>JUnit 5</strong>. </p>



<p>As an example, we will implement a simple logic responsible for querying the <strong>GitHub API</strong> using coroutines. Nevertheless, I am pretty sure you will be able to easily adjust this article to your needs. (of course, if you are here only for the WireMock part, then I recommend skipping to the &#8220;WebClient Integration Testing With WireMock&#8221; chapter).</p>



<p>Lastly, I just wanted to add that you can use this testing approach regardless of whether you are using WebClient, Retrofit, Ktor Client, or any other HTTP client. </p>



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



<p>If you prefer <strong>video content</strong>, then check out my video:</p>



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



<p>If you find this content useful,<strong> please leave a subscription</strong> 🙂</p>



<h2 class="wp-block-heading" id="h-which-webclient-testing-strategy-is-the-best">Which WebClient Testing Strategy Is The Best?</h2>



<p>But before we head to the practice part, let&#8217;s try to answer the above question. </p>



<p>Quick answer- there is no such thing as <strong>the best testing strategy</strong>. </p>



<p>And for the longer answer, let&#8217;s take a look at the visualization of the testing pyramid first:</p>



<figure class="wp-block-image aligncenter size-large is-resized"><img loading="lazy" decoding="async" width="1024" height="1024" src="http://blog.codersee.com/wp-content/uploads/2024/02/tests_pyramid-1024x1024.png" alt="Image presents a diagram with testing pyramid including e2e tests, integration tests, and unit tests helping to better understand the best approach for WebClient WireMock testing. " class="wp-image-9508749" style="width:542px;height:auto" srcset="https://blog.codersee.com/wp-content/uploads/2024/02/tests_pyramid-1024x1024.png 1024w, https://blog.codersee.com/wp-content/uploads/2024/02/tests_pyramid-300x300.png 300w, https://blog.codersee.com/wp-content/uploads/2024/02/tests_pyramid-150x150.png 150w, https://blog.codersee.com/wp-content/uploads/2024/02/tests_pyramid-768x768.png 768w, https://blog.codersee.com/wp-content/uploads/2024/02/tests_pyramid.png 1080w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>The tests are ordered based on their scope and execution time. The closer we are to the top, the longer the tests take, but they also give us more confidence. </p>



<ul class="wp-block-list">
<li><strong>unit tests</strong> form the base, as they are quick to run and pinpoint specific code issues,</li>



<li><strong>integration tests</strong> come next, validating the interactions between units,</li>



<li><strong>E2E tests</strong>, with a broader scope, are placed at the top.</li>
</ul>



<p>So long story short, although the e2e tests give us the most confidence, we cannot rely only on them, because the feedback loop would take way too long. And this would affect the development process.</p>



<p>So usually, we make concessions and introduce more integration and unit tests. </p>



<p>But which of these two should we pick for the WebClient? </p>



<p>In my opinion- <strong>integration tests</strong>. </p>



<p>Whenever we are talking about logic responsible for communicating with external API, we must make sure that: </p>



<ul class="wp-block-list">
<li>we query the appropriate URL, </li>



<li>we pass the necessary headers with appropriate values, </li>



<li>request/response bodies are properly serialized/deserialized, </li>



<li>and many, many more. </li>
</ul>



<p>With unit testing, the amount of mocking here would be so significant that I would question whether we need such a test at all. </p>



<p>Moreover, in Spring we can write integration tests for WebClient with WireMock at almost no effort. </p>



<h2 class="wp-block-heading" id="h-set-up-github-api-key">Set Up GitHub API Key </h2>



<p>With that said, let&#8217;s set up the API key we will use to query the GitHub API. </p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p></p>
<cite><strong>Note:</strong> technically, the endpoint we will use does not require <strong>any token.</strong> However, for the learning purposes I want to show you how to deal with passing bearer tokens, too 🙂 </cite></blockquote>



<p>As the first step, let&#8217;s navigate to the <a href="https://github.com/settings/tokens" target="_blank" rel="noreferrer noopener">tokens tab</a> in GitHub settings and click the &#8220;Generate new token (classic)&#8221;: </p>



<figure class="wp-block-image aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="777" height="190" src="http://blog.codersee.com/wp-content/uploads/2024/02/github_generate_token_1.png" alt="Image is a screenshot and presents how to generate new classic GitHub token." class="wp-image-9508752" style="width:777px;height:auto" srcset="https://blog.codersee.com/wp-content/uploads/2024/02/github_generate_token_1.png 777w, https://blog.codersee.com/wp-content/uploads/2024/02/github_generate_token_1-300x73.png 300w, https://blog.codersee.com/wp-content/uploads/2024/02/github_generate_token_1-768x188.png 768w" sizes="auto, (max-width: 777px) 100vw, 777px" /></figure>



<p>On the next page, let&#8217;s specify the name, expiration, and desired scopes: </p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="780" height="457" src="http://blog.codersee.com/wp-content/uploads/2024/02/github_generate_token_2.png" alt="Image is a screenshot from GitHub tokens page." class="wp-image-9508753" srcset="https://blog.codersee.com/wp-content/uploads/2024/02/github_generate_token_2.png 780w, https://blog.codersee.com/wp-content/uploads/2024/02/github_generate_token_2-300x176.png 300w, https://blog.codersee.com/wp-content/uploads/2024/02/github_generate_token_2-768x450.png 768w" sizes="auto, (max-width: 780px) 100vw, 780px" /></figure>



<p></p>



<p>If you don&#8217;t know what scopes you will need, please follow the principle of lowest privilege. At the end of the day, we can generate a new token at some point in the future when the requirements change.</p>



<p>When we are ready, let&#8217;s hit the generate button <strong>copy the token value</strong>, and <strong>store it securely</strong>. We will not see it again! </p>



<h2 class="wp-block-heading" id="h-query-the-api-with-webclient">Query The Api With WebClient</h2>



<p>Before we implement the WireMock tests, let&#8217;s prepare a simple project responsible for querying the external API with WebClient. So, even if you are here only for the WireMock part, I encourage you to briefly check this paragraph to be on the same page 😉 </p>



<p>Long story short, we will implement WebClient with coroutines that will query the &#8220;List repositories for a user&#8221; endpoint. For more details about it, check out the <a href="https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#list-repositories-for-a-user" target="_blank" rel="noreferrer noopener">GitHub API documentation</a>, please. </p>



<h3 class="wp-block-heading" id="h-generate-new-project">Generate New Project</h3>



<p>As the first step, let&#8217;s navigate to the <a href="https://start.spring.io/" target="_blank" rel="noreferrer noopener">https://start.spring.io/</a> page and generate a fresh Spring Boot project:</p>



<figure class="wp-block-image aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="530" src="http://blog.codersee.com/wp-content/uploads/2024/02/webclient_wiremock_generate_fresh_spring_boot_project-1024x530.png" alt="Image presents the Spring Initializr page screenshot of a project setting that we will use to implement WebClient logic and test with WireMock." class="wp-image-9508754" srcset="https://blog.codersee.com/wp-content/uploads/2024/02/webclient_wiremock_generate_fresh_spring_boot_project-1024x530.png 1024w, https://blog.codersee.com/wp-content/uploads/2024/02/webclient_wiremock_generate_fresh_spring_boot_project-300x155.png 300w, https://blog.codersee.com/wp-content/uploads/2024/02/webclient_wiremock_generate_fresh_spring_boot_project-768x398.png 768w, https://blog.codersee.com/wp-content/uploads/2024/02/webclient_wiremock_generate_fresh_spring_boot_project.png 1510w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>In general, the most important thing is that we will use Kotlin and Spring Reactive Web this time. Versions and metadata are totally up to you. </p>



<p>Of course, let&#8217;s click the &#8220;Generate&#8221; button, download the ZIP file, and import that to our IDE. Personally, I am using IntelliJ IDEA and if you would like to learn more about its setup, then check out my video on <a href="https://www.youtube.com/watch?v=npib90rBLE4" target="_blank" rel="noreferrer noopener">how to install IntelliJ for Kotlin development</a>.</p>



<h3 class="wp-block-heading" id="h-update-application-properties">Update Application Properties</h3>



<p>As the next step, let&#8217;s navigate to the <code>resources</code> package and update the <code>application.yaml</code> file: </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="">api:
  github:
    key: ${GITHUB_API_TOKEN:123}
    url: ${GITHUB_API_BASE_URL:http://localhost:8090}
    version: ${GITHUB_API_VERSION:2022-11-28}</pre>



<p>Above we can see our custom properties. </p>



<p>We will source values for our GitHub key, URL, and version from environment variables. </p>



<p>Not only is this approach <strong>secure</strong> (hardcoding the key with token value would be the worst thing you could do), but also <strong>flexible</strong> (we can pass different values depending on the environment we are running our app in). </p>



<h3 class="wp-block-heading" id="h-webclient-configuration">WebClient Configuration</h3>



<p>Following, let&#8217;s configure a WebClient bean that we will use whenever we want to call the GitHub API. </p>



<p>Firstly, let&#8217;s add the <code>config</code> package and put the <code>GitHubApiProperties</code> data class in there:</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="">@ConfigurationProperties("api.github")
data class GitHubApiProperties(
    val url: String,
    val key: String,
    val version: String,
)</pre>



<p>In my opinion, the <code>@ConfigurationProperties</code> annotation is the cleanest way to inject values from properties. As we can see above, the <strong>prefix</strong> and <strong>field names</strong> simply reflect the structure from the previous step. </p>



<p>With that done, let&#8217;s add the <code>GitHubApiConfig</code> in the same package: </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="">@Configuration
@EnableConfigurationProperties(GitHubApiProperties::class)
class GitHubApiConfig(
    private val gitHubApiProperties: GitHubApiProperties,
) {
    @Bean
    fun webClient(builder: WebClient.Builder): WebClient =
        builder
            .baseUrl(gitHubApiProperties.url)
            .defaultHeader("Authorization", "Bearer ${gitHubApiProperties.key}")
            .defaultHeader("X-GitHub-Api-Version", gitHubApiProperties.version)
            .defaultHeader("Accept", "application/vnd.github+json")
            .build()
}</pre>



<p>Quite a few things are happening here, so let&#8217;s dive into each one. </p>



<p>Firstly, we annotate the class with <code>@Configuration</code> and <code>@EnableConfigurationproperties</code>. The second annotation is necessary to for the previous steps to work. </p>



<p>Then, we inject the <code>GitHubApiProperties</code> instance and use its values to configure a new WebClient bean. This configuration is quite straightforward. We set the base URL and a bunch of headers that will be sent with <strong>every request</strong>: the authorization token, GH API version, and the accept header (those two are specific to the GitHub API, so let&#8217;s focus on that too much).  </p>



<h3 class="wp-block-heading" id="h-prepare-model-classes">Prepare Model Classes</h3>



<p>With that done, let&#8217;s prepare a bunch of data classes that we will need to deserialize JSON responses from the API (+ one exception class). If you would like to add other fields, please check out the documentation link I shared with you before. </p>



<p>As the first step, let&#8217;s add the <code>model</code> package and the <code>PageableGitHubResponse</code>: </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="">data class PageableGitHubResponse&lt;T>(
    val items: List&lt;T>,
    val hasMoreItems: Boolean,
)</pre>



<p>The API endpoints allow us to get only a chunk of data using the <code>page</code> and <code>per_page</code> parameters. And this class can be reused whenever we&#8217;re using a paginated endpoint. </p>



<p>As the next step, let&#8217;s implement the <code>GitHubRepoResponse</code> along with <code>GitHubOwnerResponse</code>: </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="">data class GitHubRepoResponse(
    val fork: Boolean,
    val name: String,
    val owner: GitHubOwnerResponse,
)

data class GitHubOwnerResponse(
    val login: String,
)</pre>



<p>As I mentioned at the beginning of this paragraph, we need those two in order to deserialize the actual JSON response payload. </p>



<p>Lastly, let&#8217;s add the <code>UpstreamApiException</code>: </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="">import org.springframework.http.HttpStatusCode

data class UpstreamApiException(
    val msg: String,
    val statusCode: HttpStatusCode,
) : RuntimeException(msg)</pre>



<p>This way, we will be able to throw custom exceptions. Such an exception could be handled later, for example with <a href="https://blog.codersee.com/exception-handling-with-restcontrolleradvice-and-exceptionhandler/" target="_blank" rel="noreferrer noopener">@RestControllerAdvice</a>.</p>



<h3 class="wp-block-heading" id="h-make-use-of-webclient">Make Use Of WebClient</h3>



<p>Finally, let&#8217;s combine all of that together. </p>



<p>So, let&#8217;s add the <code>api</code> package and implement the <code>GitHubApi</code> class:</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="">@Component
class GitHubApi(
  private val webClient: WebClient,
) {

  suspend fun listRepositoriesByUsername(
    username: String,
    page: Int,
    perPage: Int,
  ): PageableGitHubResponse&lt;GitHubRepoResponse>? =
    webClient.get()
      .uri("/users/$username/repos?page=$page&amp;per_page=$perPage")
      .awaitExchangeOrNull(::mapToPageableResponse)
}</pre>



<p>Don&#8217;t worry about the <code>mapToPageableResponse</code>, we will add it in a moment. </p>



<p>But before that, let&#8217;s analyze the above code. </p>



<p>Firstly, we annotate the class as the <code>@Component</code> to make it a Spring bean and inject the <code>WebClient</code> instance we configured earlier. We don&#8217;t have other WebClient instances, so there is no need to use <code>@Qualifier</code> or anything like that. </p>



<p>Following, we add the <code>listRepositoriesByUsername</code> function that lets us pass the <code>username</code> along with <code>page</code> and <code>perPage</code>. This way, we make this function reusable and allow the invoker to decide how to tackle the pagination. </p>



<p>Additionally, we mark it as a suspend function because the <code>awaitExchangeOrNull</code> is a suspend function. However, if you are interested in this topic, then I refer you to my other article about <a href="https://blog.codersee.com/spring-webclient-with-kotlin-coroutines/" target="_blank" rel="noreferrer noopener">WebClient with coroutines</a>. </p>



<p>Lastly, let&#8217;s add the missing code:</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="">private suspend inline fun &lt;reified T> mapToPageableResponse(clientResponse: ClientResponse): PageableGitHubResponse&lt;T>? {
  val hasNext = checkIfMorePagesToFetch(clientResponse)

  return when (val statusCode = clientResponse.statusCode()) {
    HttpStatus.OK ->
      PageableGitHubResponse(
        items = clientResponse.awaitBody&lt;List&lt;T>>(),
        hasMoreItems = hasNext,
      )

    HttpStatus.NOT_FOUND -> null

    else -> throw UpstreamApiException(
      msg = "GitHub API request failed.",
      statusCode = statusCode,
    )
  }
}

fun checkIfMorePagesToFetch(clientResponse: ClientResponse) =
  clientResponse.headers()
    .header("link")
    .firstOrNull()
    ?.contains("next")
    ?: false</pre>



<p>Long story short, the above code works as follows: </p>



<ol class="wp-block-list">
<li>Firstly, we take the response and check if there are more pages. How? Well, without going into details, GitHub API returns the <code>link</code> header. And if its value contains the <code>rel="next"</code>, it means that there are more pages to fetch. </li>



<li>Finally, we check the status code. If it is <code>200 OK</code>, we simply read the JSON value. When we get <code>404 Not Found</code>, we return null (we can expect this status code when we pass a non-existing username, and in my opinion, this case is not exceptional). And whenever we receive another status code, we simply throw the <code>UpstreamApiException</code>.</li>
</ol>



<h2 class="wp-block-heading" id="h-webclient-integration-testing-with-wiremock">WebClient Integration Testing With WireMock</h2>



<p>Excellent, at this point we have (almost) everything we need to test our WebClient implementation with WireMock. </p>



<p>We will see only a few example test cases that you will be able to easily adapt according to your needs:</p>



<ul class="wp-block-list">
<li>200 OK response with an empty list, 200 OK with items, and more pages, and 200 OK with items and no more pages to fetch</li>



<li>404 Not Found, </li>



<li>401 Unauthorized</li>
</ul>



<p>When you will be implementing your own integration tests, this is the moment when you should do the homework and analyze the API you are working with. What are the possible happy paths? How should your code behave in case of any error status code? What if we add some latency? And many, many more 🙂 </p>



<h3 class="wp-block-heading" id="h-additional-dependencies">Additional Dependencies</h3>



<p>At this point, we defined our test cases, so we are ready to get our hands dirty. </p>



<p>So, let&#8217;s start by adding the necessary dependencies: </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="">testImplementation("org.springframework.cloud:spring-cloud-contract-wiremock:4.1.1")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0")</pre>



<p>As we can see, the first one- the Spring Cloud Contract WireMock module- allows us to use WireMock in our app, whereas the <code>kotlinx-coroutines-test</code> is necessary due to our coroutines WebClient implementation.</p>



<h3 class="wp-block-heading" id="h-add-expected-json-files">Add Expected JSON Files</h3>



<p>So, if we know what test cases we would like to test, we should prepare the example JSON API responses. </p>



<p>Personally, whenever working with WireMock I am a big fan of externalizing the expected JSON files. We put them inside the <code>/test/resources</code> directory and refer to them later in the tests. This way, we can improve the readability of the test classes. Of course, <strong>unless we want to prepare JSON responses dynamically</strong>. </p>



<p>In our case, we could simply invoke the GitHub API and persist responses for different cases. This way, we prepare the following files and put them inside the <code>/test/resources/responses/external/github</code>: </p>



<ul class="wp-block-list">
<li></li>
</ul>



<ul class="wp-block-list">
<li>list_github_repositories_200_OK_empty_list.json</li>



<li>list_github_repositories_200_OK_page_1.json</li>



<li>list_github_repositories_401_UNAUTHORIZED.json</li>



<li>list_github_repositories_404_NOT_FOUND.json</li>
</ul>



<p>You can find all of these files in my GH repo <a href="https://github.com/codersee-blog/integration-testing-spring-webflux-webclient-wiremock/tree/main/src/test/resources/responses/external/github" target="_blank" rel="noreferrer noopener">here</a>. </p>



<p>The example for <code>401 Unauthorized</code> looks as follows:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="json" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">{
  "message": "Bad credentials",
  "documentation_url": "https://docs.github.com/rest"
}</pre>



<h3 class="wp-block-heading" id="h-prepare-util-function">Prepare Util Function</h3>



<p>As the last thing before we implement our test class, let&#8217;s add the util function inside the <code>util</code> package (of course, in tests). </p>



<p>This function will be responsible for reading JSON files inside the test resources as String values:</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="">import org.springframework.core.io.ClassPathResource

fun getResponseBodyAsString(path: String): String =
    ClassPathResource(path).getContentAsString(
        Charsets.UTF_8,
    )</pre>



<h3 class="wp-block-heading" id="h-implement-githubapitest-class">Implement GitHubApiTest Class</h3>



<p>Finally, let&#8217;s start implementing the <code>GitHubApiTest</code> class: </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="">private const val TEST_KEY = "TEST_KEY"
private const val TEST_PORT = 8082
private const val TEST_VERSION = "2022-11-28"

@AutoConfigureWireMock(port = TEST_PORT)
@TestPropertySource(
  properties = [
    "api.github.url=http://localhost:${TEST_PORT}",
    "api.github.key=$TEST_KEY",
    "api.github.version=$TEST_VERSION",
  ],
)
@SpringBootTest(
  webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
)
class GitHubApiTest {
  @Autowired
  private lateinit var wireMockServer: WireMockServer

  @Autowired
  private lateinit var gitHubApi: GitHubApi

  private val page = 1
  private val perPage = 2
  private val username = UUID.randomUUID().toString()

}</pre>



<p>Conceptually, everything starts with the <code>@SpringBootTest</code> and <code>@AutoConfigureWireMock</code> annotations.</p>



<p>We use the <code>@SpringBootTest</code> annotation in Spring Boot integration tests. This way a new ApplicationContext will be created. In our case, we set the <code>webEnvironment</code> with <code>RANDOM_PORT</code> value, so that a reactive context listening on the random port is created. </p>



<p>Additionally, we combine that together with <code>@AutoConfigureWireMock</code> and fixed test port. This way a WireMock server will be started as a part of Application Context. </p>



<p>Lastly, we do 3 things: </p>



<ol class="wp-block-list">
<li>We use the <code>@TestPropertySource</code> to pass values for properties.</li>



<li>We inject the <code>WireMockServer</code> and <code>GitHubApi</code> instances that we will work with later. Please note that we can do that just like we would do with a standard, non-test, Spring component. </li>



<li>We prepare some dummy data for <code>page</code>, <code>perPage</code>, and <code>username</code> values we will need for testing. </li>
</ol>



<p>As a note of comment from my side- this &#8220;skeleton&#8221; is useful whenever working with WebClient and WireMock.</p>



<h3 class="wp-block-heading" id="h-test-404-not-found-api-response-case">Test 404 Not Found API Response Case</h3>



<p>Wonderful! </p>



<p>As the first test, let&#8217;s start with the <code>404 Not Found</code> case. Usually, we will start with happy path cases, but for learning purposes, this one is the shortest case 😉 </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="">@Test
fun `Given 404 NOT FOUND response When fetching repository by username Then should return null`() = runTest {
  // Given
  wireMockServer.stubFor(
    WireMock.get(WireMock.urlEqualTo("/users/$username/repos?page=$page&amp;per_page=$perPage"))
      .withHeader("Authorization", WireMock.equalTo("Bearer $TEST_KEY"))
      .withHeader("X-GitHub-Api-Version", WireMock.equalTo(TEST_VERSION))
      .withHeader("Accept", WireMock.equalTo("application/vnd.github+json"))
      .willReturn(
        WireMock.aResponse()
          .withStatus(HttpStatus.NOT_FOUND.value())
          .withHeader(HttpHeaders.CONTENT_TYPE, "application/json; charset=utf-8")
          .withBody(
            getResponseBodyAsString("/responses/external/github/list_github_repositories_404_NOT_FOUND.json"),
          ),
      ),
  )

  // When
  val result = gitHubApi.listRepositoriesByUsername(username, page, perPage)

  // Then
  Assertions.assertNull(result)
}</pre>



<p>We mark our test with the <code>@Test</code> (JUnit 5), specify the meaningful name for the test (with awesome Kotlin backticks), and wrap our test with <code>runTest</code>. The last one is necessary as we are working with coroutines and invoke the suspended function. Long story short, in JVM it will behave just like <code>runBlocking</code> (but it will skip delays). </p>



<p>Next comes the WireMock part. And to be even more specific- <strong>stubbing.</strong> </p>



<p>Stubbing is nothing else than &#8220;the ability to return canned HTTP responses for requests matching criteria&#8221; (from docs). Simply said, whenever an outgoing request that matches our criteria is made, a mocked response configured in <code>willReturn</code> is returned. </p>



<p>So, as we can see, the above logic will work only when: </p>



<ul class="wp-block-list">
<li>the GET request is made, </li>



<li>URL matches, </li>



<li>Headers sent have appropriate values </li>
</ul>



<p>And this part <strong>already tests our logic.</strong> Pretty neat, isn&#8217;t it? 🙂 </p>



<p>Lastly, we simply invoke the function and assert that <code>null</code> is returned. Which confirms that everything is working fine. </p>



<h3 class="wp-block-heading" id="h-test-401-unauthorized">Test 401 Unauthorized </h3>



<p>As we already started with error cases, let&#8217;s verify that whenever the endpoint returns <code>401 Unauthorized</code>, the <code>UpstreamApiException</code> is thrown:</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="">@Test
fun `Given 401 UAUTHORIZED response When fetching repository by username Then should throw UpstreamApiException`() = runTest {
  // Given
  wireMockServer.stubFor(
    WireMock.get(WireMock.urlEqualTo("/users/$username/repos?page=$page&amp;per_page=$perPage"))
      .withHeader("Authorization", WireMock.equalTo("Bearer $TEST_KEY"))
      .withHeader("X-GitHub-Api-Version", WireMock.equalTo(TEST_VERSION))
      .withHeader("Accept", WireMock.equalTo("application/vnd.github+json"))
      .willReturn(
        WireMock.aResponse()
          .withStatus(HttpStatus.UNAUTHORIZED.value())
          .withHeader(HttpHeaders.CONTENT_TYPE, "application/json; charset=utf-8")
          .withBody(
            getResponseBodyAsString("/responses/external/github/list_github_repositories_401_UNAUTHORIZED.json"),
          ),
      ),
  )

  // When
  val exception = assertThrows&lt;UpstreamApiException> {
    gitHubApi.listRepositoriesByUsername(username, page, perPage)
  }

  // Then
  assertNotNull(exception)

  assertEquals("GitHub API request failed.", exception.msg)
  assertEquals(HttpStatus.UNAUTHORIZED, exception.statusCode)
}</pre>



<p>As we can see, the logic responsible for stubbing only slightly changed. </p>



<p>The interesting part here is the <code>assertThrows</code>, which not only makes sure that the exception of an appropriate type is thrown but also returns it. This way we can make additional assertions.</p>



<h3 class="wp-block-heading" id="h-verify-200-ok-with-empty-list">Verify 200 OK With Empty List</h3>



<p>Following, let&#8217;s cover the empty list case:</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="">@Test
fun `Given 200 OK response with empty list When fetching repository by username Then should return repository with correct properties and has next false`() =
  runTest {
    // Given
    val linkHeader = """&lt;https://api.github.com/user/64011387/repos?page=3&amp;per_page=2>; rel="prev","""

    wireMockServer.stubFor(
      WireMock.get(WireMock.urlEqualTo("/users/$username/repos?page=$page&amp;per_page=$perPage"))
        .withHeader("Authorization", WireMock.equalTo("Bearer $TEST_KEY"))
        .withHeader("X-GitHub-Api-Version", WireMock.equalTo(TEST_VERSION))
        .withHeader("Accept", WireMock.equalTo("application/vnd.github+json"))
        .willReturn(
          WireMock.aResponse()
            .withStatus(HttpStatus.OK.value())
            .withHeader(HttpHeaders.CONTENT_TYPE, "application/json; charset=utf-8")
            .withHeader(HttpHeaders.LINK, linkHeader)
            .withBody(
              getResponseBodyAsString("/responses/external/github/list_github_repositories_200_OK_empty_list.json"),
            ),
        ),
    )

    // When
    val result = gitHubApi.listRepositoriesByUsername(username, page, perPage)

    // Then
    val expected =
      PageableGitHubResponse(
        items = emptyList&lt;GitHubRepoResponse>(),
        hasMoreItems = false,
      )

    assertEquals(expected, result)
  }</pre>



<p>This time, we assign the returned value to the <code>result</code> and compare that with the <code>expected</code>. </p>



<h3 class="wp-block-heading" id="h-check-200-ok-with-has-next-true">Check 200 OK With Has Next True</h3>



<p>Last before least, let&#8217;s make sure that we got the expected objects in a list and that the logic responsible for the <code>link</code> header check returns true:</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="">@Test
fun `Given 200 OK response with payload When fetching repository by username Then should return repository with correct properties and has next true`() =
  runTest {
    // Given
    val linkHeader = """&lt;https://api.github.com/user/64011387/repos?page=3&amp;per_page=2>; rel="next","""

    wireMockServer.stubFor(
      WireMock.get(WireMock.urlEqualTo("/users/$username/repos?page=$page&amp;per_page=$perPage"))
        .withHeader("Authorization", WireMock.equalTo("Bearer $TEST_KEY"))
        .withHeader("X-GitHub-Api-Version", WireMock.equalTo(TEST_VERSION))
        .withHeader("Accept", WireMock.equalTo("application/vnd.github+json"))
        .willReturn(
          WireMock.aResponse()
            .withStatus(HttpStatus.OK.value())
            .withHeader(HttpHeaders.CONTENT_TYPE, "application/json; charset=utf-8")
            .withHeader(HttpHeaders.LINK, linkHeader)
            .withBody(
              getResponseBodyAsString("/responses/external/github/list_github_repositories_200_OK_page_1.json"),
            ),
        ),
    )

    // When
    val result = gitHubApi.listRepositoriesByUsername(username, page, perPage)

    // Then
    val expected =
      PageableGitHubResponse(
        items =
        listOf(
          GitHubRepoResponse(
            fork = false,
            name = "controlleradvice-vs-restcontrolleradvice",
            owner = GitHubOwnerResponse(login = "codersee-blog"),
          ),
          GitHubRepoResponse(
            fork = false,
            name = "freecodecamp-spring-boot-kotlin-excel",
            owner = GitHubOwnerResponse(login = "codersee-blog"),
          ),
        ),
        hasMoreItems = true,
      )

    assertEquals(expected, result)
  }</pre>



<h3 class="wp-block-heading" id="h-test-200-ok-with-has-next-false">Test 200 OK With Has Next False</h3>



<p>And finally, let&#8217;s double-check the flag logic:</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="">@Test
fun `Given 200 OK response with payload When fetching repository by username Then should return repository with correct properties and has next false`() =
  runTest {
    // Given
    val linkHeader = """&lt;https://api.github.com/user/64011387/repos?page=3&amp;per_page=2>; rel="prev","""

    wireMockServer.stubFor(
      WireMock.get(WireMock.urlEqualTo("/users/$username/repos?page=$page&amp;per_page=$perPage"))
        .withHeader("Authorization", WireMock.equalTo("Bearer $TEST_KEY"))
        .withHeader("X-GitHub-Api-Version", WireMock.equalTo(TEST_VERSION))
        .withHeader("Accept", WireMock.equalTo("application/vnd.github+json"))
        .willReturn(
          WireMock.aResponse()
            .withStatus(HttpStatus.OK.value())
            .withHeader(HttpHeaders.CONTENT_TYPE, "application/json; charset=utf-8")
            .withHeader(HttpHeaders.LINK, linkHeader)
            .withBody(
              getResponseBodyAsString("/responses/external/github/list_github_repositories_200_OK_page_1.json"),
            ),
        ),
    )

    // When
    val result = gitHubApi.listRepositoriesByUsername(username, page, perPage)

    // Then
    val expected =
      PageableGitHubResponse(
        items =
        listOf(
          GitHubRepoResponse(
            fork = false,
            name = "controlleradvice-vs-restcontrolleradvice",
            owner = GitHubOwnerResponse(login = "codersee-blog"),
          ),
          GitHubRepoResponse(
            fork = false,
            name = "freecodecamp-spring-boot-kotlin-excel",
            owner = GitHubOwnerResponse(login = "codersee-blog"),
          ),
        ),
        hasMoreItems = false,
      )

    assertEquals(expected, result)
  }</pre>



<h3 class="wp-block-heading" id="h-integration-tests-with-wiremock-and-webclient-summary">Integration Tests With WireMock And WebClient Summary</h3>



<p>And basically, that&#8217;s all for this tutorial on how to perform integration testing for Spring WebClient that invokes external REST APIs with WireMock and JUnit 5.</p>



<p>I hope you enjoyed this one and I recommend you check out my other articles related to <a href="https://blog.codersee.com/category/spring/">Spring Framework</a>.</p>
<p>The post <a href="https://blog.codersee.com/integration-tests-webclient-wiremock/">Integration Tests for WebClient with WireMock</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/integration-tests-webclient-wiremock/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>Spring Boot 3 (Spring Security 6) with Kotlin &#038; JWT</title>
		<link>https://blog.codersee.com/spring-boot-3-spring-security-6-with-kotlin-jwt/</link>
					<comments>https://blog.codersee.com/spring-boot-3-spring-security-6-with-kotlin-jwt/#comments</comments>
		
		<dc:creator><![CDATA[Piotr]]></dc:creator>
		<pubDate>Thu, 26 Oct 2023 05:30:09 +0000</pubDate>
				<category><![CDATA[Spring]]></category>
		<category><![CDATA[Access Tokens]]></category>
		<category><![CDATA[Authentication]]></category>
		<category><![CDATA[Authorization]]></category>
		<category><![CDATA[JSON Web Tokens]]></category>
		<category><![CDATA[Refresh Tokens]]></category>
		<category><![CDATA[Spring Boot 3]]></category>
		<category><![CDATA[Spring Security]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=9008217</guid>

					<description><![CDATA[<p>In this article, I will show you how to implement a secure REST API with Spring Boot 3, Spring Security, Kotlin, and JWT tokens.</p>
<p>The post <a href="https://blog.codersee.com/spring-boot-3-spring-security-6-with-kotlin-jwt/">Spring Boot 3 (Spring Security 6) with Kotlin &#038; JWT</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>If you would like to learn how to secure a REST API with <strong>Spring Boot 3</strong> (<strong>Spring Security 6</strong>), <strong>Kotlin</strong>, and <strong>JWT tokens</strong>, then you came to the right place 😉 </p>



<p>In this, comprehensive guide I will show you step-by-step how to:</p>



<ul class="wp-block-list">
<li>authenticate and authorize users, </li>



<li>assign and verify roles, </li>



<li>generate JWT tokens,</li>



<li>and implement a refresh token flow.</li>
</ul>



<p>And everything that with our beloved Kotlin programming language! </p>



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



<p>If you prefer <strong>video content</strong>, then you can find this tutorial as a YouTube playlist <a href="https://www.youtube.com/playlist?list=PLvN8k8yxjoeud4ESoB-wjiieqYGaDVqPR" target="_blank" rel="noreferrer noopener">right here</a>. </p>



<p>Alternatively, you can start from the introduction:</p>



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



<p>If you find this content useful,<strong> please leave a subscription&nbsp;</strong> 😉</p>



<h2 class="wp-block-heading" id="h-what-exactly-will-we-implement">What Exactly Will We Implement? </h2>



<div style="height:35px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image aligncenter size-large is-resized"><img loading="lazy" decoding="async" width="1024" height="665" src="http://blog.codersee.com/wp-content/uploads/2023/11/articles_users_auth_api-1024x665.png" alt="Image shows sticky notes presenting what exactly will be implemented in this tutorial. " class="wp-image-9008335" style="aspect-ratio:1.5398496240601505;width:805px;height:auto" srcset="https://blog.codersee.com/wp-content/uploads/2023/11/articles_users_auth_api-1024x665.png 1024w, https://blog.codersee.com/wp-content/uploads/2023/11/articles_users_auth_api-300x195.png 300w, https://blog.codersee.com/wp-content/uploads/2023/11/articles_users_auth_api-768x499.png 768w, https://blog.codersee.com/wp-content/uploads/2023/11/articles_users_auth_api.png 1287w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p></p>



<div style="height:35px" aria-hidden="true" class="wp-block-spacer"></div>



<p>Well, in our project we will implement a REST API for <strong>articles</strong> and <strong>users</strong>. </p>



<p><strong>Articles</strong> will be visible to any user who successfully authenticates using the JWT access token. </p>



<p><strong>Users </strong>endpoint, on the other hand, will be accessible <strong>only for admin users</strong> in our system (with one exception- a public endpoint for creating new users). </p>



<p>Of course, we will expose additional authentication endpoints, which will take data from users, like <code>email</code> and <code>password</code> and generate access and refresh tokens for our system. </p>



<p>Long story short- I believe this project will be simple enough to easily understand the topic, and comprehensive enough to cover the most popular real-life cases. </p>



<h2 class="wp-block-heading" id="h-a-tiny-bit-of-theory">A Tiny Bit Of Theory</h2>



<p>In this paragraph, I&#8217;d like to cover what exactly JWT tokens are, the difference between authorization and authentication, and present from a bird&#8217;s eye view how Spring Security works. </p>



<p>So, if you feel that you understand these and came here for the practice, then please skip to the next chapter 😉 </p>



<h3 class="wp-block-heading" id="h-authentication-vs-authorization">Authentication vs Authorization </h3>



<p>I am pretty sure that you heard both terms plenty of times already and even heard people using them alternately. </p>



<p>Nevertheless, although similar, these two terms refer to completely different things. </p>



<p>Imagine you&#8217;re at the entrance of a super-secret club:</p>



<p><strong>Authentication</strong> is like showing your ID to prove you are who you say you are. It&#8217;s the process of confirming your identity. So, in our club scenario, it&#8217;s like showing your driver&#8217;s license to the bouncer.</p>



<p><strong>Authorization</strong>, on the other hand, is like being allowed into different parts of the club based on your VIP status. Once you&#8217;re inside (thanks to authentication), authorization decides what you can and cannot do. For example, VIP members might access the VIP lounge, while regular guests can&#8217;t.</p>



<h3 class="wp-block-heading" id="h-jwt-tokens">JWT Tokens </h3>



<p>Among plenty of existing ways to authenticate users, JWTs (JSON Web Tokens) are one of the most popular ones. </p>



<p>They are like a digital secret handshake on the internet. When we log into a website, the server gives us a special JWT. This token is like a digital badge that says, &#8220;Hey, this person is legit!&#8221; </p>



<p>Why are they so cool? Well, they are <strong>safe, compact, </strong>and allow us to <strong>hold information</strong>. </p>



<p>JWTs consist of three parts separated by dots: header.payload.signature: </p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="534" src="http://blog.codersee.com/wp-content/uploads/2023/10/spring_boot_3_security_jwt_io-1024x534.png" alt="The image is a screenshot from jwt.io page and present and example encoded JWT token with its decoded value." class="wp-image-9008239" srcset="https://blog.codersee.com/wp-content/uploads/2023/10/spring_boot_3_security_jwt_io-1024x534.png 1024w, https://blog.codersee.com/wp-content/uploads/2023/10/spring_boot_3_security_jwt_io-300x156.png 300w, https://blog.codersee.com/wp-content/uploads/2023/10/spring_boot_3_security_jwt_io-768x400.png 768w, https://blog.codersee.com/wp-content/uploads/2023/10/spring_boot_3_security_jwt_io.png 1191w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>As we can see, the encoded value can be easily decoded and JWT token values can be read. </p>



<p>The <strong>header </strong>typically consists of two parts: the type of the token (missing above), and the signing algorithm being used (HMAC SHA512).</p>



<p>The second part of the token is the payload, which contains the <strong>claims</strong>. Claims, Claims are statements about an entity (typically, the user) and additional data.</p>



<p>Lastly, the <strong>signature</strong> is used to verify that the sender of the JWT is who it says it is and to ensure that the message wasn&#8217;t changed along the way.</p>



<h3 class="wp-block-heading" id="h-bird-s-eye-view-on-spring-security">Bird&#8217;s Eye View on Spring Security</h3>



<p>At this point, we know the difference between authentication and authorization and also what JWT tokens are. Awesome! </p>



<p>So, now I&#8217;ll try to do the impossible- explain how Spring Security works in a few sentences 🙂 </p>



<p><strong>Spring Security</strong> is like the security team of a building. It manages authentication and authorization for our app with:</p>



<ol class="wp-block-list">
<li><strong>Filters</strong>&#8211; think of security filters as checkpoints at different doors in the building. When a request comes in, it goes through these filters. These filters handle tasks like authentication and authorization. For instance, there might be a filter that checks if you have a proper access card (authentication), and another that ensures you can enter specific rooms (authorization).</li>



<li><strong>Authentication</strong>&#8211; when you log in, Spring Security checks your credentials (like username and password). If they match, you&#8217;re authenticated. Spring Security uses authentication providers, which can be a database, LDAP, or any other source, to verify your identity.</li>



<li><strong>Security Context</strong>&#8211; once authenticated, your security details are stored in the Security Context. It&#8217;s like being given a special pass after passing through the checkpoint. This pass (security context) contains your roles and permissions.</li>



<li><strong>Authorization</strong>&#8211; now, when you try to access a specific part of the application, Spring Security checks your roles and permissions stored in the security context. If you&#8217;re authorized (based on your roles), you&#8217;re allowed in. Otherwise, you might be denied access.</li>



<li><strong>Customization</strong>&#8211; Spring Security allows you to customize the security filters, authentication providers, and access rules according to your application&#8217;s requirements. You can configure which URLs need authentication, what roles are required, etc.</li>
</ol>



<p> And that&#8217;s <strong>basically what we&#8217;re gonna learn today</strong>.</p>



<h2 class="wp-block-heading" id="h-import-spring-boot-3-project">Import Spring Boot 3 Project</h2>



<p>As the first step, let&#8217;s generate a new Spring Boot 3 project. </p>



<p>To do so, we can use the <a href="https://start.spring.io/" target="_blank" rel="noreferrer noopener">Spring Initializr</a> page:</p>



<div style="height:35px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="509" src="http://blog.codersee.com/wp-content/uploads/2023/10/spring_boot_3_security_jwt_1-1024x509.png" alt="Image is a screenshot from Spring Initializr page and shows a minimum project configuration." class="wp-image-9008218" srcset="https://blog.codersee.com/wp-content/uploads/2023/10/spring_boot_3_security_jwt_1-1024x509.png 1024w, https://blog.codersee.com/wp-content/uploads/2023/10/spring_boot_3_security_jwt_1-300x149.png 300w, https://blog.codersee.com/wp-content/uploads/2023/10/spring_boot_3_security_jwt_1-768x382.png 768w, https://blog.codersee.com/wp-content/uploads/2023/10/spring_boot_3_security_jwt_1.png 1502w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<div style="height:35px" aria-hidden="true" class="wp-block-spacer"></div>



<p>As we can see, in this project, we will use <em>Spring Boot 3.1.5</em>, <em>Kotlin</em> as a programming language, and <em>Kotlin DSL</em> for our Gradle build config. </p>



<p>At this point, the only dependency required to expose a REST API is <em>Spring Web</em>. We will add Spring Security, and libraries necessary for JWT tokens later (and we will see why later, too). </p>



<p>With all of that done, we can hit the <strong>generate</strong> button, download the zip file, and import it to IntelliJ. </p>



<h2 class="wp-block-heading" id="h-expose-articles-api">Expose Articles API </h2>



<p>Following, let&#8217;s implement the logic necessary to expose our first endpoint- <code>GET /api/article</code> .</p>



<p>In the future, this endpoint will be accessible only to users with a valid JWT token. </p>



<h3 class="wp-block-heading" id="h-create-article-model">Create Article Model</h3>



<p>As the first step, let&#8217;s introduce a new package- <code>model</code> &#8211; and create a new data class- <code>Article</code>: </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="">import java.util.*

data class Article(
  val id: UUID,
  val title: String,
  val content: String,
)</pre>



<p>This class consists of three example fields simulating a real-life article. </p>



<h3 class="wp-block-heading" id="h-implement-article-repository">Implement Article Repository</h3>



<p>Secondly, we must introduce a new class responsible for article retrieval and storage. </p>



<p>Let&#8217;s add the <code>repository</code> package and implement <code>ArticleRepository</code>: </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="">import com.codersee.jwtauth.model.Article
import org.springframework.stereotype.Repository
import java.util.UUID

@Repository
class ArticleRepository {

  private val articles = listOf(
    Article(id = UUID.randomUUID(), title= "Article 1", content= "Content 1"),
    Article(id = UUID.randomUUID(), title= "Article 2", content= "Content 2"),
  )

  fun findAll(): List&lt;Article> =
    articles

}</pre>



<p>In our example, articles are nothing else, than a hardcoded private list of items. </p>



<p>Additionally, we expose the <code>findAll()</code> method, which encapsulates the list and which we will be invoking from outside the class. </p>



<p>Of course, I did it this way to keep it simple and so that we do not lose focus on what matters in this tutorial- Spring Boot security with Kotlin and JWT tokens. </p>



<p>Nevertheless, you can easily adjust this project to your needs. In real life, this would be the class where you can put logic responsible for communication with your database. </p>



<h3 class="wp-block-heading" id="h-create-service">Create Service</h3>



<p>As the next step, let&#8217;s create the <code>service</code> package and introduce the <code>ArticleService</code>: </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="">import com.codersee.jwtauth.model.Article
import com.codersee.jwtauth.repository.ArticleRepository
import org.springframework.stereotype.Service

@Service
class ArticleService(
  private val articleRepository: ArticleRepository
) {

  fun findAll(): List&lt;Article> =
    articleRepository.findAll()
}</pre>



<p>As we can see, this class exposes the <code>findAll()</code> function and does nothing else than invoking the <code>findAll()</code> method from our repository. </p>



<p>Again, this might look a bit like an <strong>overengineering</strong>. But trust me, keeping a well-organized project from the very beginning will pay off in the future. </p>



<h3 class="wp-block-heading" id="h-expose-article-rest-api">Expose Article REST API </h3>



<p>Lastly, let&#8217;s add the <code>controller.article</code> package and put two classes in it. </p>



<p>Let&#8217;s start with the <code>ArticleResponse</code>: </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="">import java.util.*

data class ArticleResponse(
  val id: UUID,
  val title: String,
  val content: String,
)</pre>



<p>Before returning any article from our REST API, we will map model objects into the response objects. </p>



<p>And with that done, let&#8217;s implement the <code>ArticleController</code>:</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="">import com.codersee.jwtauth.model.Article
import com.codersee.jwtauth.service.ArticleService
import org.springframework.web.bind.annotation.*
import java.util.*

@RestController
@RequestMapping("/api/article")
class ArticleController(
  private val articleService: ArticleService
) {

  @GetMapping
  fun listAll(): List&lt;ArticleResponse> =
    articleService.findAll()
      .map { it.toResponse() }

  private fun Article.toResponse(): ArticleResponse =
    ArticleResponse(
      id = this.id,
      title = this.title,
      content = this.content,
    )
}</pre>



<p>As we can see, we must annotate our controller class with the <em>@RestController </em>annotation in order to expose a REST API in Spring Boot. </p>



<p>Additionally, we add the <code>@RequestMapping</code> annotation so that whenever we hit the <code>/api/article</code> endpoint, Spring knows that this is the class it should use to handle the request. </p>



<p>Lastly, we mark the <code>listAll()</code> function with <code>@GetMapping</code>, and convert all found <em>Article</em> instances to the <em>ArticleResponse.</em> </p>



<p>Later, when we add the JWT security to our Spring Boot Kotlin project, these endpoints will be accessible only for <code>ADMIN</code> users with one exception- the endpoint responsible for creating new users- which will be publicly exposed.   </p>



<h3 class="wp-block-heading" id="h-add-user-model">Add User Model</h3>



<p>With that said, let&#8217;s add the <code>User.kt</code> file to the <code>model</code> package:</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="">import java.util.*

data class User(
  val id: UUID,
  val email: String,
  val password: String,
  val role: Role
)

enum class Role {
  USER, ADMIN
}</pre>



<p>As we can see, apart from the <code>id</code>, the User class contains three important fields: <code>email</code>, <code>password</code>, and <code>role</code>, which we will extensively when dealing with JWT tokens. </p>



<h3 class="wp-block-heading" id="h-implement-userrepository">Implement UserRepository</h3>



<p>Following, let&#8217;s create the <code>UserRepository</code> in the <code>repository</code> package:</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="">import com.codersee.jwtauth.model.Role
import com.codersee.jwtauth.model.User
import org.springframework.stereotype.Repository
import java.util.*

@Repository
class UserRepository {

  private val users = mutableSetOf(
    User(
      id = UUID.randomUUID(),
      email = "email-1@gmail.com",
      password = "pass1",
      role = Role.USER,
    ),
    User(
      id = UUID.randomUUID(),
      email = "email-2@gmail.com",
      password = "pass2",
      role = Role.ADMIN,
    ),
    User(
      id = UUID.randomUUID(),
      email = "email-3@gmail.com",
      password = "pass3",
      role = Role.USER,
    ),
  )

  fun save(user: User): Boolean =
    users.add(user)

  fun findByEmail(email: String): User? =
    users
      .firstOrNull { it.email == email }

  fun findAll(): Set&lt;User> =
    users

  fun findByUUID(uuid: UUID): User? =
    users
      .firstOrNull { it.id == uuid }

  fun deleteByUUID(uuid: UUID): Boolean {
    val foundUser = findByUUID(uuid)

    return foundUser?.let {
      users.removeIf {
        it.id == uuid
      }
    } ?: false
  }
}</pre>



<p>Similarly to our previous repository- we simply introduce a mutable list, which we can edit with a bunch of util methods. </p>



<p>And don&#8217;t worry about the password stored in plain text- we will get back to that topic, too 🙂 </p>



<h3 class="wp-block-heading" id="h-add-userservice">Add UserService</h3>



<p>Nextly, we must go to the <code>service</code> package and introduce this <code>UserService</code>:</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="">import com.codersee.jwtauth.model.User
import com.codersee.jwtauth.repository.UserRepository
import org.springframework.stereotype.Service
import java.util.*

@Service
class UserService(
  private val userRepository: UserRepository
) {

  fun createUser(user: User): User? {
    val found = userRepository.findByEmail(user.email)

    return if (found == null) {
      userRepository.save(user)
      user
    } else null
  }

  fun findByUUID(uuid: UUID): User? =
    userRepository.findByUUID(uuid)

  fun findAll(): List&lt;User> =
    userRepository.findAll()
      .toList()

  fun deleteByUUID(uuid: UUID): Boolean =
    userRepository.deleteByUUID(uuid)
}</pre>



<p>Nothing spectacular here, so let&#8217;s move to the next step. </p>



<h3 class="wp-block-heading" id="h-expose-user-rest-api">Expose User REST API </h3>



<p>Just like with articles, let&#8217;s start with introducing request/response objects. </p>



<p>This time, we will need both the <code>UserRequest</code>: </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="">data class UserRequest(
  val email: String,
  val password: String,
)</pre>



<p>And the <code>UserResponse</code>, which will be serialized into JSON objects:</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="">import java.util.*

data class UserResponse(
  val uuid: UUID,
  val email: String,
)</pre>



<p>And with that done, we can add the <code>UserController</code> class:</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="">import com.codersee.jwtauth.model.Role
import com.codersee.jwtauth.model.User
import com.codersee.jwtauth.service.UserService
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
import org.springframework.web.server.ResponseStatusException
import java.util.*

@RestController
@RequestMapping("/api/user")
class UserController(
  private val userService: UserService
) {

  @PostMapping
  fun create(@RequestBody userRequest: UserRequest): UserResponse =
    userService.createUser(userRequest.toModel())
      ?.toResponse()
      ?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Cannot create user.")

  @GetMapping
  fun listAll(): List&lt;UserResponse> =
    userService.findAll()
      .map { it.toResponse() }

  @GetMapping("/{uuid}")
  fun findByUUID(@PathVariable uuid: UUID): UserResponse =
    userService.findByUUID(uuid)
      ?.toResponse()
      ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "User not found.")


  @DeleteMapping("/{uuid}")
  fun deleteByUUID(@PathVariable uuid: UUID): ResponseEntity&lt;Boolean> {
    val success = userService.deleteByUUID(uuid)

    return if (success)
      ResponseEntity.noContent()
        .build()
    else
      throw ResponseStatusException(HttpStatus.NOT_FOUND, "User not found.")
  }

  private fun User.toResponse(): UserResponse =
    UserResponse(
      uuid = this.id,
      email = this.email,
    )

  private fun UserRequest.toModel(): User =
    User(
      id = UUID.randomUUID(),
      email = this.email,
      password = this.password,
      role = Role.USER,
    )
}</pre>



<p>As we can see, the logic above is pretty similar to what we&#8217;ve done before. </p>



<p>We mark our class with <code>@RestController</code> and <code>@RequestMapping</code>, functions with annotations matching HTTP Methods they are gonna handle, and also our path variables and request bodies. </p>



<p>When converting between models/requests/responses, we make use of <strong>extension functions</strong>, which are my favorite approach for mappers in Kotlin. </p>



<p>Lastly, whenever we want to return anything else than <code>200 OK</code>, we use the <strong>ResponseStatusException</strong>, which is a clean way to do so. </p>



<p>If you would like to test this API now, then check out the following <a href="https://drive.google.com/file/d/1eNG-bOqBmepaZQDZbNpywyh5sWSeazt2/view?usp=sharing" target="_blank" rel="noreferrer noopener">Postman collection</a>.</p>



<h2 class="wp-block-heading" id="h-import-spring-security-and-jwt-library">Import Spring Security and JWT Library</h2>



<p>Wonderful. At this point, we have everything we need to finally add Spring Security to our Spring Boot Kotlin project, and start implementing <strong>JWT authentication</strong> and <strong>authorization</strong>.</p>



<h3 class="wp-block-heading" id="h-update-build-gradle-kts">Update build.gradle.kts</h3>



<p>As the first step, let&#8217;s add the following to the <code>build.gradle.kts</code> file:</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="">dependencies {
	// other imports
	implementation("io.jsonwebtoken:jjwt-api:0.12.3")
	implementation("io.jsonwebtoken:jjwt-impl:0.12.3")
	implementation("io.jsonwebtoken:jjwt-jackson:0.12.3")
	implementation("org.springframework.boot:spring-boot-starter-security")
	testImplementation("org.springframework.security:spring-security-test")
}</pre>



<p>As we can see, in order to secure our Spring Boot 3 REST API, we must add not only the Spring Security but also an additional library to deal with JWT tokens. </p>



<p>Although the Spring security is a must-have, please feel free to choose whatever JWT library suits you best (you can find a full list <a href="https://jwt.io/libraries" target="_blank" rel="noreferrer noopener">here</a>). I have decided to go with <code>io.jsonwebtoken</code> which is the most popular Java library.</p>



<h3 class="wp-block-heading" id="h-sync-and-rerun-project">Sync and Rerun Project</h3>



<p>Following, let&#8217;s sync our Gradle project and rerun our application. </p>



<p>Among other logs, we will see the following lines: </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="">Using generated security password: 52d7d5b8-ce49-4d93-a4e1-721780290e58

This generated password is for development use only. Your security configuration must be updated before running your application in production.</pre>



<p>Moreover, when we try to query our endpoints, we will get <code>401 Unauthorized</code> for each request. </p>



<p>But what happened? </p>



<p>Well, whenever we add Spring Security to our Spring Boot project, the <strong>authentication gets enabled by default. </strong></p>



<p>In other words, we can query our endpoint now using the <strong>basic access authentication</strong> with <code>user</code> as a username and the randomly generated password as the <code>password</code>. </p>



<p>As a proof, when we update the <code>Authorization</code> in our Postman collection, we will start seeing results again: </p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="350" src="http://blog.codersee.com/wp-content/uploads/2023/10/spring_boot_3_security_jwt_2-1024x350.png" alt="Image presents Postman request using the Basic Auth as the authorization mechanism. " class="wp-image-9008223" srcset="https://blog.codersee.com/wp-content/uploads/2023/10/spring_boot_3_security_jwt_2-1024x350.png 1024w, https://blog.codersee.com/wp-content/uploads/2023/10/spring_boot_3_security_jwt_2-300x102.png 300w, https://blog.codersee.com/wp-content/uploads/2023/10/spring_boot_3_security_jwt_2-768x262.png 768w, https://blog.codersee.com/wp-content/uploads/2023/10/spring_boot_3_security_jwt_2.png 1163w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<h2 class="wp-block-heading" id="h-create-jwt-tokens">Create JWT Tokens</h2>



<p>Although basic auth may be a good choice in some cases, in this tutorial, I would like to show you how to authenticate and authorize requests using <strong>JWT access tokens</strong>. </p>



<p>So, let&#8217;s start everything by implementing a service responsible for token operations. </p>



<h3 class="wp-block-heading" id="h-edit-application-yaml-file">Edit application.yaml file</h3>



<p>As the first step, let&#8217;s navigate to the <code>resources</code> folder and edit the <code>application.yaml</code> file: </p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p></p>
<cite>Note: when generating a new Spring Boot project, the <code>application.properties</code> file is created automatically. So before we start, let&#8217;s change the extension to <code>yaml</code>.</cite></blockquote>



<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="">jwt:
  key: ${JWT_KEY}
  access-token-expiration: 3600000
  refresh-token-expiration: 86400000</pre>



<p>As we can see, we introduce 3 custom properties: the key and expiration time (in milliseconds) for both access and refresh tokens. </p>



<p>The key is value will be sourced from the <code>JWT_TOKEN</code> environment variable, which is nothing else than a randomly generated String value. Later, we will see where and why we need it. </p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p></p>
<cite><strong>Important:</strong> vulnerable values should never be hardcoded when implementing a Spring Boot (and not only) application. <br><br>Regardless of the environment the app is running in, values should be stored securely and sourced to our application using environment variables. <br></cite></blockquote>



<h3 class="wp-block-heading" id="h-make-use-of-configurationproperties">Make Use Of @ConfigurationProperties</h3>



<p>As the next step, let&#8217;s make use of the <em>@ConfigurationProperties</em> to easily convert our properties into Kotlin objects.</p>



<p>Let&#8217;s start by adding the <code>configuration</code> package and the <code>JwtProperties</code> class:</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="">import org.springframework.boot.context.properties.ConfigurationProperties

@ConfigurationProperties("jwt")
data class JwtProperties(
  val key: String,
  val accessTokenExpiration: Long,
  val refreshTokenExpiration: Long,
)</pre>



<p>If you&#8217;d like to learn more about @ConfigurationProperties, then I have <a href="https://blog.codersee.com/fail-spring-boot-app-on-missing-environment-variables/" target="_blank" rel="noreferrer noopener">another article</a> you might want to check out later. </p>



<p>Additionally, let&#8217;s add the <code>Configuration</code> class: </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="">import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Configuration


@Configuration
@EnableConfigurationProperties(JwtProperties::class)
class Configuration</pre>



<p>This way, we enable support for beans annotated with @ConfigurationProperties in our project. </p>



<h3 class="wp-block-heading" id="h-add-tokenservice">Add TokenService</h3>



<p>With all of that done, let&#8217;s add the <code>TokenService</code> class: </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="">@Service
class TokenService(
  jwtProperties: JwtProperties
) {

  private val secretKey = Keys.hmacShaKeyFor(
    jwtProperties.key.toByteArray()
  )

  fun generate(
    userDetails: UserDetails,
    expirationDate: Date,
    additionalClaims: Map&lt;String, Any> = emptyMap()
  ): String =
    Jwts.builder()
      .claims()
      .subject(userDetails.username)
      .issuedAt(Date(System.currentTimeMillis()))
      .expiration(expirationDate)
      .add(additionalClaims)
      .and()
      .signWith(secretKey)
      .compact()

  fun isValid(token: String, userDetails: UserDetails): Boolean {
    val email = extractEmail(token)

    return userDetails.username == email &amp;&amp; !isExpired(token)
  }

  fun extractEmail(token: String): String? =
    getAllClaims(token)
      .subject

  fun isExpired(token: String): Boolean =
    getAllClaims(token)
      .expiration
      .before(Date(System.currentTimeMillis()))

  private fun getAllClaims(token: String): Claims {
    val parser = Jwts.parser()
      .verifyWith(secretKey)
      .build()

    return parser
      .parseSignedClaims(token)
      .payload
  }
}</pre>



<p>As we can see, this class exposes functions, which we can use to deal with JWT access and refresh tokens. </p>



<p>Let&#8217;s walk quickly through our logic: </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="">private val secretKey = Keys.hmacShaKeyFor(
  jwtProperties.key.toByteArray()
)</pre>



<p>The <code>secretKey</code> is nothing else than an instance of <code>SecretKey</code>, which we will use to <strong>sign</strong> and <strong>verify</strong> JWT tokens in our system. In our case, we will use the <code>HMAC-SHA</code> algorithm based on the random key we provided to our project. </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="">fun generate(
  userDetails: UserDetails,
  expirationDate: Date,
  additionalClaims: Map&lt;String, Any> = emptyMap()
): String =
  Jwts.builder()
    .claims()
    .subject(userDetails.username)
    .issuedAt(Date(System.currentTimeMillis()))
    .expiration(expirationDate)
    .add(additionalClaims)
    .and()
    .signWith(secretKey)
    .compact()</pre>



<p>The <code>generate</code> function is responsible for creating a serialized, URL-safe String with JWT tokens. We set the <code>subject</code>, expiration date, and additional claims based on the arguments passed to it. </p>



<p>Lastly, we sign the token using the <code>SecretKey</code> instance created in the previous step. </p>



<p>Additionally, we provide more functions which we will use later to extract values from tokens and validate them:</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="">fun isValid(token: String, userDetails: UserDetails): Boolean {
  val email = extractEmail(token)

  return userDetails.username == email &amp;&amp; !isExpired(token)
}

fun extractEmail(token: String): String? =
  getAllClaims(token)
    .subject

fun isExpired(token: String): Boolean =
  getAllClaims(token)
    .expiration
    .before(Date(System.currentTimeMillis()))

private fun getAllClaims(token: String): Claims {
  val parser = Jwts.parser()
    .verifyWith(secretKey)
    .build()

  return parser
    .parseSignedClaims(token)
    .payload
}</pre>



<h2 class="wp-block-heading" id="h-implement-custom-userdetailsservice">Implement Custom UserDetailsService</h2>



<p>As the next step, let&#8217;s introduce a custom implementation of <code>UserDetailsService</code>.</p>



<p>But what exactly is <code>UserDetailsService</code> and <code>UserDetails</code>? </p>



<p>Well, the <strong>UserDetails</strong> represents the core user information. In our case, we will use the default implementation called <code>User</code>, which holds the default information, such as username, password, and a collection of granted authorities (roles). </p>



<p>The <strong>UserDetailsService</strong> is nothing else than an interface used to load user-specific data. It is used by Spring Security to interact with our data source and validate users during authentication. </p>



<p>With that said, let&#8217;s add a new class- <code>CustomUserDetailsService</code>&#8211; to the <code>service</code> package:</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="">import com.codersee.jwtauth.repository.UserRepository
import org.springframework.security.core.userdetails.User
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.core.userdetails.UsernameNotFoundException
import org.springframework.stereotype.Service

typealias ApplicationUser = com.codersee.jwtauth.model.User

@Service
class CustomUserDetailsService(
  private val userRepository: UserRepository
) : UserDetailsService {

  override fun loadUserByUsername(username: String): UserDetails =
    userRepository.findByEmail(username)
      ?.mapToUserDetails()
      ?: throw UsernameNotFoundException("Not found!")

  private fun ApplicationUser.mapToUserDetails(): UserDetails =
    User.builder()
      .username(this.email)
      .password(this.password)
      .roles(this.role.name)
      .build()
}</pre>



<p>As we can see, both the <code>username</code> and <code>email</code> in our system refer to the same thing, which is used to uniquely identify users. </p>



<p>And in order to fetch <code>UserDetails</code> we must find the user in our repository and map it to the <code>User</code> instance. </p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p></p>
<cite>Additionally, we make use of the <code>typealias</code> which is a great way to avoid fully qualified name (we have both User in our <code>model</code> package and User from Spring Security</cite></blockquote>



<h2 class="wp-block-heading" id="h-update-configuration">Update Configuration </h2>



<p>With that done, let&#8217;s get back to the <code>Configuration</code> class and register a few beans:</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="">import com.codersee.jwtauth.repository.UserRepository
import com.codersee.jwtauth.service.CustomUserDetailsService
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.AuthenticationProvider
import org.springframework.security.authentication.dao.DaoAuthenticationProvider
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
import org.springframework.security.crypto.password.PasswordEncoder

@Configuration
@EnableConfigurationProperties(JwtProperties::class)
class Configuration {

  @Bean
  fun userDetailsService(userRepository: UserRepository): UserDetailsService =
    CustomUserDetailsService(userRepository)

  @Bean
  fun encoder(): PasswordEncoder = BCryptPasswordEncoder()

  @Bean
  fun authenticationProvider(userRepository: UserRepository): AuthenticationProvider =
    DaoAuthenticationProvider()
      .also {
        it.setUserDetailsService(userDetailsService(userRepository))
        it.setPasswordEncoder(encoder())
      }

  @Bean
  fun authenticationManager(config: AuthenticationConfiguration): AuthenticationManager =
    config.authenticationManager
}</pre>



<p>Firstly, we must register the bean of type <code>UserDetailsService</code>, which we introduced in the previous step. </p>



<p>Following, we register a <code>PasswordEncoder</code>. To put it simply, <strong>we should never store passwords</strong> in plain text. They should be always <strong>encrypted</strong>, or <strong>hashed</strong>. In this tutorial, we will use the <code>BCrypt</code> strong hashing function for that purpose. </p>



<p>Following, we must provide the <code>AuthenticationProvider</code> and configure which UserDetailsService we will use and the password encoder. </p>



<h2 class="wp-block-heading" id="h-update-userrepository">Update UserRepository</h2>



<p>In the previous step, I mentioned that we should never store passwords in plain text. </p>



<p>So, to fix that, let&#8217;s get back to the <code>UserRepository</code> and make the necessary changes:</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="">@Repository
class UserRepository(
  private val encoder: PasswordEncoder
) {

  private val users = mutableSetOf(
    User(
      id = UUID.randomUUID(),
      email = "email-1@gmail.com",
      password = encoder.encode("pass1"),
      role = Role.USER,
    ),
    User(
      id = UUID.randomUUID(),
      email = "email-2@gmail.com",
      password = encoder.encode("pass2"),
      role = Role.ADMIN,
    ),
    User(
      id = UUID.randomUUID(),
      email = "email-3@gmail.com",
      password = encoder.encode("pass3"),
      role = Role.USER,
    ),
  )

  fun save(user: User): Boolean {
    val updated = user.copy(password = encoder.encode(user.password))

    return users.add(updated)
  }
}</pre>



<p>As we can see, after this step both our predefined users and all users which we will create will have their passwords hashed. </p>



<h2 class="wp-block-heading" id="h-implement-authenticationfilter">Implement AuthenticationFilter</h2>



<p>Uff, quite a lot of preparation must be done before we secure our Spring Boot 3 Kotlin app with JWT tokens, isn&#8217;t it? </p>



<p>But don&#8217;t worry, we&#8217;re almost there. </p>



<p>As the next step, let&#8217;s navigate to the <code>config</code> package and introduce our custom Filter:</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="">import com.codersee.jwtauth.service.CustomUserDetailsService
import com.codersee.jwtauth.service.TokenService
import jakarta.servlet.FilterChain
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource
import org.springframework.stereotype.Component
import org.springframework.web.filter.OncePerRequestFilter

@Component
class JwtAuthenticationFilter(
  private val userDetailsService: CustomUserDetailsService,
  private val tokenService: TokenService,
) : OncePerRequestFilter() {

  override fun doFilterInternal(
    request: HttpServletRequest,
    response: HttpServletResponse,
    filterChain: FilterChain
  ) {
    val authHeader: String? = request.getHeader("Authorization")

    if (authHeader.doesNotContainBearerToken()) {
      filterChain.doFilter(request, response)
      return
    }

    val jwtToken = authHeader!!.extractTokenValue()
    val email = tokenService.extractEmail(jwtToken)

    if (email != null &amp;&amp; SecurityContextHolder.getContext().authentication == null) {
      val foundUser = userDetailsService.loadUserByUsername(email)

      if (tokenService.isValid(jwtToken, foundUser))
        updateContext(foundUser, request)

      filterChain.doFilter(request, response)
    }
  }

  private fun String?.doesNotContainBearerToken() =
    this == null || !this.startsWith("Bearer ")

  private fun String.extractTokenValue() =
    this.substringAfter("Bearer ")

  private fun updateContext(foundUser: UserDetails, request: HttpServletRequest) {
    val authToken = UsernamePasswordAuthenticationToken(foundUser, null, foundUser.authorities)
    authToken.details = WebAuthenticationDetailsSource().buildDetails(request)
    SecurityContextHolder.getContext().authentication = authToken
  }

}</pre>



<p>I know, quite a lot of logic, but I&#8217;ll explain it step-by-step in a moment. </p>



<p>But before that, let&#8217;s understand <strong>what are Filters in Spring Framework.</strong> </p>



<p>Well, every request made to our Spring Framework application goes through the filter chain, where each filter in the chain can perform some operations on the request or response.</p>



<p>In our case, we want to use this feature to <strong>authenticate </strong>requests made to our REST API. We want to check whether a user sent a JWT token and validate it, and if everything is fine, we want to update the Spring Security Context. </p>



<p>And that&#8217;s exactly what&#8217;s happening in the code above. </p>



<p>Firstly, we check if the request <strong>contains an Authorization header</strong>. If no, we do not proceed with this function and we simply pass the request down the filter chain.  </p>



<p>Nextly, we extract the JWT token. A valid <code>Authorization</code> header value consists of the <code>Bearer &lt;JWT&gt;</code>, so we must extract the token itself. </p>



<p>Following, we read the <code>email</code> value from the token. And when we make sure it is not null and there&#8217;s no previously authenticated principal in the security context, we fetch the <code>UserDetails</code>, which we then use to validate them with the token. </p>



<p>Lastly, we simply update the security context in our system with <code>foundUser</code> (which is <code>UserDetails</code>) and authorities (which, in our case will be either <code>ADMIN</code> or <code>USER</code>).</p>



<p>To sum up, this function <strong>will be invoked once per each request.</strong> In simple terms, it will check the JWT token and if everything is fine, it will update the Spring Security context with information about user and its roles.</p>



<h2 class="wp-block-heading" id="h-add-security-configuration">Add Security Configuration</h2>



<p>At this point, we did everything we needed to to in order to <strong>authenticate the user</strong>. </p>



<p>In this step, we will learn how to <strong>authorize</strong> him.</p>



<p>And to do so, let&#8217;s introduce a new class- <code>SecurityConfiguration</code> &#8211; in the <code>config</code> package:</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="">import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.HttpMethod
import org.springframework.security.authentication.AuthenticationProvider
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.http.SessionCreationPolicy
import org.springframework.security.web.DefaultSecurityFilterChain
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter

@Configuration
@EnableWebSecurity
class SecurityConfiguration(
  private val authenticationProvider: AuthenticationProvider
) {

  @Bean
  fun securityFilterChain(
    http: HttpSecurity,
    jwtAuthenticationFilter: JwtAuthenticationFilter
  ): DefaultSecurityFilterChain {
    http
      .csrf { it.disable() }
      .authorizeHttpRequests {
        it
          .requestMatchers("/api/auth", "api/auth/refresh", "/error")
          .permitAll()
          .requestMatchers(HttpMethod.POST, "/api/user")
          .permitAll()
          .requestMatchers("/api/user**")
          .hasRole("ADMIN")
          .anyRequest()
          .fullyAuthenticated()
      }
      .sessionManagement {
        it.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
      }
      .authenticationProvider(authenticationProvider)
      .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter::class.java)

    return http.build()
  }
}</pre>



<p>As we can see, the above config exposes a new bean of type <code>DefaultSecurityFilterChain</code>. </p>



<p>To put it simply, this is the way we can modify the default chain by adding, removing, or replacing filters to tailor the security configuration according to our needs. </p>



<p>The first thing we do is <strong>disable the CSRF protection</strong>. It&#8217;s enabled by default, and in most cases not needed (but in real-life projects, please learn a bit more about CSRF attacks and whether your codebase is vulnerable). </p>



<p>Following, we configure the <strong>authorization</strong>. We can clearly see that requests to &#8220;/api/auth&#8221;, &#8220;api/auth/refresh&#8221;, &#8220;/error&#8221;, and POST requests to &#8220;/api/user&#8221; will be <strong>accessible without a token</strong>. And that&#8217;s correct- we cannot require a valid access token for users, who want to sign in, who want to create a new account, and when the API client wants to refresh the token. </p>



<p>And if you are wondering why is the &#8220;/error&#8221; in this list, too, then here comes the answer. It is because of the way Spring handles errors internally. Without that, every exception we throw in our codebase <strong>will return 403 Forbidden</strong>, instead of the HTTP status code we provided. </p>



<p>When it comes to the rest of the &#8220;/api/user&#8221; requests- we want to allow only users with role <code>ADMIN</code>.</p>



<p>And what with the rest of the requests? Well, we want them to be accessed only by fully authenticated users. But what does it mean? In our, stateless REST API, it means simply that every user with a valid JWT token will be able to access them (regardless of his role). </p>



<p>After that, we informed Spring that it should never create a HttpSession (we want our security to be stateless) and that it should never use it to obtain the SecurityContext.</p>



<p>And lastly, we register our <code>CustomAuthenticationProvider</code> and let Spring Security know that <code>JwtAuthenticationFilter</code> we implemented previously should be added before the default <code>UsernamePasswordAuthenticationFilter</code> (the one that required us to specify basic auth).</p>



<h2 class="wp-block-heading" id="h-expose-login-endpoint-generate-access-token">Expose Login Endpoint &#8211; Generate Access Token</h2>



<p>With all of that done, we can finally expose the endpoint, which will be used by our REST API consumers to get the <strong>JWT access token</strong>.</p>



<p>Let&#8217;s create the <code>config.auth</code> package and add a new controller class:</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="">import com.codersee.jwtauth.service.AuthenticationService
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

@RestController
@RequestMapping("/api/auth")
class AuthController(
  private val authenticationService: AuthenticationService
) {

  @PostMapping
  fun authenticate(
    @RequestBody authRequest: AuthenticationRequest
  ): AuthenticationResponse =
    authenticationService.authentication(authRequest)

}</pre>



<p>As we can see, this controller uses <code>AuthenticationService</code> which we will implement in a moment.</p>



<p>But before we do so, let&#8217;s add the <code>AuthenticationRequest</code>:</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="">data class AuthenticationRequest(
  val email: String,
  val password: String,
)</pre>



<p>And the response class:</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="">data class AuthenticationResponse(
  val accessToken: String,
)</pre>



<p>After that, let&#8217;s get back to the <code>service</code> package and implement a service, which will be responsible for authentication and token generation:</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="">import com.codersee.jwtauth.controller.auth.AuthenticationRequest
import com.codersee.jwtauth.controller.auth.AuthenticationResponse
import com.codersee.jwtauth.controller.config.JwtProperties
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.stereotype.Service
import java.util.*

@Service
class AuthenticationService(
  private val authManager: AuthenticationManager,
  private val userDetailsService: CustomUserDetailsService,
  private val tokenService: TokenService,
  private val jwtProperties: JwtProperties,
) {

  fun authentication(authenticationRequest: AuthenticationRequest): AuthenticationResponse {
    authManager.authenticate(
      UsernamePasswordAuthenticationToken(
        authenticationRequest.email,
        authenticationRequest.password
      )
    )

    val user = userDetailsService.loadUserByUsername(authenticationRequest.email)

    val accessToken = createAccessToken(user)

    return AuthenticationResponse(
      accessToken = accessToken,
    )
  }

  private fun createAccessToken(user: UserDetails) = tokenService.generate(
    userDetails = user,
    expirationDate = getAccessTokenExpiration()
  )

  private fun getAccessTokenExpiration(): Date =
    Date(System.currentTimeMillis() + jwtProperties.accessTokenExpiration)

}</pre>



<p>As we can see, the first thing we do is invoke the <code>authenticate</code> method from <code>AuthenticationManager</code>. </p>



<p>If the <code>email</code> and <code>password</code> values don&#8217;t match with any of the users in our system, the <code>authenticate</code> method will throw <code>AuthenticationException</code> and the API will return <code>403 Forbidden</code>.</p>



<p>On the other hand, when they match, we will fetch the <code>UserDetails</code> and use it to generate a JWT token with an expiration time set in <code>application.yaml</code></p>



<p>At the moment, I highly encourage you to run our application and try to call endpoints with different cases. If you would like to use a ready-to-go Postman collection, then you can find one with all endpoints in the next chapter about refresh tokens. </p>



<h2 class="wp-block-heading" id="h-jwt-refresh-token">JWT Refresh Token</h2>



<p>Excellent! At this point, our REST API is properly secured and utilizing the JWT access tokens. </p>



<p>But depending on your needs, you may want to introduce the <strong>JWT refresh tokens </strong>to the system.</p>



<h3 class="wp-block-heading" id="h-introduce-new-endpoint">Introduce New Endpoint </h3>



<p>Firstly, let&#8217;s introduce a new endpoint and update our response classes. </p>



<p>Let&#8217;s start by adding a new <code>TokenResponse</code>:</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="">data class TokenResponse(
  val token: String
)</pre>



<p>We will use this class to return the <strong>refreshed access token.</strong></p>



<p>Following, let&#8217;s make the necessary changes to the <code>AuthenticationResponse</code>: </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="">data class AuthenticationResponse(
  val accessToken: String,
  val refreshToken: String,
)</pre>



<p>As we can see, the <code>refreshToken</code> value will be returned when a user authenticates successfully along with the JWT token value. </p>



<p>After that, let&#8217;s add the <code>RefreshTokenRequest</code>, which will be used to deserialize refresh token sent by a user:</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="">data class RefreshTokenRequest(
  val token: String
)</pre>



<p>Great! </p>



<p>At this point, we can add a new 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="">  @PostMapping("/refresh")
  fun refreshAccessToken(
    @RequestBody request: RefreshTokenRequest
  ): TokenResponse =
    authenticationService.refreshAccessToken(request.token)
      ?.mapToTokenResponse()
      ?: throw ResponseStatusException(HttpStatus.FORBIDDEN, "Invalid refresh token.")

  private fun String.mapToTokenResponse(): TokenResponse =
    TokenResponse(
      token = this
    )</pre>



<p>Don&#8217;t worry about the missing <code>refreshAccessToken</code> method, we will get back to it in a moment.</p>



<h3 class="wp-block-heading" id="h-implement-refresh-token-repository">Implement Refresh Token Repository</h3>



<p>As the next step, let&#8217;s go to the <code>repository</code> package and introduce the <code>RefreshTokenRepository</code>: </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="">import org.springframework.security.core.userdetails.UserDetails
import org.springframework.stereotype.Component

@Component
class RefreshTokenRepository {

  private val tokens = mutableMapOf&lt;String, UserDetails>()

  fun findUserDetailsByToken(token: String) : UserDetails? =
    tokens[token]

  fun save(token: String, userDetails: UserDetails) {
    tokens[token] = userDetails
  }

}</pre>



<p>As we can see, this class will be responsible for persisting and retrieving refresh tokens. </p>



<p>Along with our tokens, we will store the associated <code>UserDetails</code> instances, so that later we could match the associated subject (email). </p>



<h3 class="wp-block-heading" id="h-edit-authenticationservice">Edit AuthenticationService</h3>



<p>As the last thing, we must get back to the <code>AuthenticationService</code> and make the necessary changes:</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="">import com.codersee.jwtauth.controller.auth.AuthenticationRequest
import com.codersee.jwtauth.controller.auth.AuthenticationResponse
import com.codersee.jwtauth.controller.config.JwtProperties
import com.codersee.jwtauth.repository.RefreshTokenRepository
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.stereotype.Service
import java.util.*

@Service
class AuthenticationService(
  private val authManager: AuthenticationManager,
  private val userDetailsService: CustomUserDetailsService,
  private val tokenService: TokenService,
  private val jwtProperties: JwtProperties,
  private val refreshTokenRepository: RefreshTokenRepository,
) {

  fun authentication(authenticationRequest: AuthenticationRequest): AuthenticationResponse {
    authManager.authenticate(
      UsernamePasswordAuthenticationToken(
        authenticationRequest.email,
        authenticationRequest.password
      )
    )

    val user = userDetailsService.loadUserByUsername(authenticationRequest.email)

    val accessToken = createAccessToken(user)
    val refreshToken = createRefreshToken(user)

    refreshTokenRepository.save(refreshToken, user)

    return AuthenticationResponse(
      accessToken = accessToken,
      refreshToken = refreshToken
    )
  }

  fun refreshAccessToken(refreshToken: String): String? {
    val extractedEmail = tokenService.extractEmail(refreshToken)

    return extractedEmail?.let { email ->
      val currentUserDetails = userDetailsService.loadUserByUsername(email)
      val refreshTokenUserDetails = refreshTokenRepository.findUserDetailsByToken(refreshToken)

      if (!tokenService.isExpired(refreshToken) &amp;&amp; refreshTokenUserDetails?.username == currentUserDetails.username)
        createAccessToken(currentUserDetails)
      else
        null
    }
  }

  private fun createAccessToken(user: UserDetails) = tokenService.generate(
    userDetails = user,
    expirationDate = getAccessTokenExpiration()
  )

  private fun createRefreshToken(user: UserDetails) = tokenService.generate(
    userDetails = user,
    expirationDate = getRefreshTokenExpiration()
  )

  private fun getAccessTokenExpiration(): Date =
    Date(System.currentTimeMillis() + jwtProperties.accessTokenExpiration)

  private fun getRefreshTokenExpiration(): Date =
    Date(System.currentTimeMillis() + jwtProperties.refreshTokenExpiration)
}</pre>



<p>Let&#8217;s summarize what exactly has changed here. </p>



<p>Firstly, we imported the <code>RefreshTokenRepository</code>, so that we could persist new tokens and retrieve the saved ones. </p>



<p>Moreover, we must generate a new refresh token whenever a user authenticates successfully. And that&#8217;s why we added the <code>getRefreshTokenExpiration</code> function and two additional lines in the <code>authentication</code> method. </p>



<p>When it comes to the refresh token flow, it can be summarized in the following steps: </p>



<ol class="wp-block-list">
<li>We extract the user <code>email</code> from the passed refresh token. </li>



<li>If this step is completed successfully, we fetch the <strong>current</strong> user details by the value. </li>



<li>After that, we look for the <strong>persisted</strong> user details with the refresh token. </li>



<li>Lastly, if the refresh token is not expired and the email from JWT subject matches the <strong>current</strong> user details, then a new <strong>access token</strong> is generated for that user. </li>



<li>Otherwise, we return null, which will be translated to <code>403 Forbidden</code> in our controller. </li>
</ol>



<p>And basically, that&#8217;s all 🙂 </p>



<p>If you would like to use a ready-to-go Postman collection with auh headers, then you can <a href="https://drive.google.com/file/d/1fXZ2Wa672rgNAKbwtBDo-r4f_yF0-Z1w/view?usp=sharing" target="_blank" rel="noreferrer noopener">find it right here</a>.</p>



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



<p>And that&#8217;s all for this tutorial in which we have learned how to secure the REST API with Spring Boot 3 (Spring Security 6) with JWT access and refresh tokens and Kotlin. Good job! </p>



<p>If you would like to get the source code for this lesson, then check out <a href="https://github.com/codersee-blog/kotlin-spring-boot-3-spring-security-jwt-access-refresh-tokens" target="_blank" rel="noreferrer noopener">this GitHub repository</a>.</p>



<p>Lastly, if you enjoyed this one, then do not forget to leave a comment and check out my <a href="https://codersee.com/the-complete-kotlin-course/">Complete Kotlin Course</a> 🙂 </p>
<p>The post <a href="https://blog.codersee.com/spring-boot-3-spring-security-6-with-kotlin-jwt/">Spring Boot 3 (Spring Security 6) with Kotlin &#038; JWT</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-3-spring-security-6-with-kotlin-jwt/feed/</wfw:commentRss>
			<slash:comments>8</slash:comments>
		
		
			</item>
		<item>
		<title>Fail Spring Boot App on Missing Environment Variables</title>
		<link>https://blog.codersee.com/fail-spring-boot-app-on-missing-environment-variables/</link>
					<comments>https://blog.codersee.com/fail-spring-boot-app-on-missing-environment-variables/#respond</comments>
		
		<dc:creator><![CDATA[Piotr]]></dc:creator>
		<pubDate>Sat, 16 Sep 2023 05:43:54 +0000</pubDate>
				<category><![CDATA[Spring]]></category>
		<category><![CDATA[@ConfigurationProperties]]></category>
		<category><![CDATA[Configuration]]></category>
		<category><![CDATA[Spring Boot]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=9007594</guid>

					<description><![CDATA[<p>In this article we will learn 4 ways to prevent Spring Boot app from starting when environment variable is missing. </p>
<p>The post <a href="https://blog.codersee.com/fail-spring-boot-app-on-missing-environment-variables/">Fail Spring Boot App on Missing Environment Variables</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Hello and welcome to my blog🙂 If you&#8217;ve been working with <strong>@ConfigurationProperties</strong> and <strong>environment variables</strong> in Spring Boot, then you probably saw that by default, Spring Boot does not fail when the value is missing.</p>



<p>In this article, I will show you <strong>four ideas</strong> on how to solve this issue. </p>



<p>Please keep in mind that all of them are workarounds, and if you came up with something even better, then do not forget to share in the comments🙂</p>



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



<p>If you prefer <strong>video content</strong>, then check out my video:</p>



<div style="text-align: center; width: 90%; margin-left: 5%;">
<p></p></div>


<a href="https://blog.codersee.com/fail-spring-boot-app-on-missing-environment-variables/"><img decoding="async" src="https://blog.codersee.com/wp-content/plugins/wp-youtube-lyte/lyteCache.php?origThumbUrl=%2F%2Fi.ytimg.com%2Fvi%2F6hAGbp8I5ow%2Fhqdefault.jpg" alt="YouTube Video"></a><br /><br /></p>



<p>If you find this content useful,<strong> please leave a subscription&nbsp;</strong> 😉</p>



<h2 class="wp-block-heading" id="h-problem">Problem</h2>



<p>To be on the same page, let&#8217;s take a quick look at a simple Spring Boot configuration with @ConfigurationProperties.<br><br>Firstly, let&#8217;s navigate to the <code>application.yaml</code> file: </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="">example:
  some-property: ${SOME_VARIABLE}</pre>



<p>As we can see, we defined a new property inside our config file for which the value should be set from the environment variable named <code>SOME_VARIABLE</code>.<br><br>Following, let&#8217;s introduce the <em>@ConfigurationProperties</em> class:</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="">@ConfigurationProperties(prefix = "example")
data class ExampleConfigurationProperties(
  val someProperty: String
)</pre>



<p>With this code, we expect that Spring Framework will read all properties defined in the configuration file and inject values into the matching properties.<br></p>



<p></p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p></p>
<cite>Note: Spring recognizes the kebab case (some-property) and matches it with the property using camel case (someProperty)</cite></blockquote>



<p><br>After that, we need to explicitly inform Spring about our configuration properties class: </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="">@Configuration
@EnableConfigurationProperties(ExampleConfigurationProperties::class)
class Config</pre>



<p>Lastly, we must add a class, which injects the <em>ExampleConfigurationProperties</em> and prints out the value of <em>someProperty:</em></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="">@Component
class SomeComponent(
  private val properties: ExampleConfigurationProperties
) {

  @PostConstruct
  fun init() {
    println(properties.someProperty)
  }

}</pre>



<p>Finally, when we run our application <strong>without the <em>SOME_VARIABLE</em> environment variable missing</strong>,<strong> </strong>we will see the following output: </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="">${SOME_VARIABLE}</pre>



<p>A bit counterintuitive, right? <br><br>It would seem pretty obvious, that if we declare <em>someProperty </em>as a non-nullable String and environment variable is missing, our Spring Boot app should fail to start. <br><br>Unfortunately, that&#8217;s not the case when using <em>@ConfigurationProperties</em> and Spring will default the String value to the name of placeholder. <br><br>Moreover, at the moment of writing, there is no built-in way we could enforce that. </p>



<h2 class="wp-block-heading" id="h-solution-with-spring-validation">Solution With Spring Validation</h2>



<p>As a first solution (or rather workaround), let&#8217;s add the <a href="https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-validation" target="_blank" rel="noreferrer noopener">spring-boot-starter-validation</a> to our project:</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="">implementation("org.springframework.boot:spring-boot-starter-validation")</pre>



<p>Nextly, let&#8217;s instruct Spring that it should default to the blank String value whenever our environment variable is missing:</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="">example:
  some-property: ${SOME_VARIABLE:}</pre>



<p>After that let&#8217;s modify our configuration properties class:</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="">import jakarta.validation.constraints.NotBlank
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.validation.annotation.Validated

@ConfigurationProperties(prefix = "example")
@Validated
data class ExampleConfigurationProperties(
  @field:NotBlank val someProperty: String
)</pre>



<p>As we can see, with a combination of <em>@Validated</em> and <em>@NotBlank</em>, we ask Spring Framework to check if the value is <strong>not null</strong> and contains at least <strong>one non-whitespace character</strong>.<br> </p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p></p>
<cite>Note: in Kotlin, we must use the @field: to annotate Java field. <br><br>If you would like to learn this, and many more then check out my <a href="https://blog.codersee.com/kotlin-handbook-learn-through-practice-course/">Kotlin course</a>.</cite></blockquote>



<p><br>Finally, when we try to run our application, we will see the following:</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="">***************************
APPLICATION FAILED TO START
***************************

Description:

Binding to target org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'example' to com.codersee.properties.ExampleConfigurationProperties failed:

    Property: example.someProperty
    Value: ""
    Origin: class path resource [application.yaml] - 2:18
    Reason: must not be blank


Action:

Update your application's configuration


Process finished with exit code 1</pre>



<p>And maybe this solution is nott ideal, but it does its job. </p>



<p>And it is neat. Not only does the application not start, but also whenever the environment variable for our @ConfigurationProperties is missing, we will see a meaningful message that will speed up the debugging process.</p>



<h2 class="wp-block-heading" id="h-custom-applicationrunner-idea">Custom ApplicationRunner Idea</h2>



<p>Another solution for our problem does not require any additional dependencies added to our project. </p>



<p>Let&#8217;s take a look at the following implementation of <em>ApplicationRunner</em>: </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="">import org.springframework.boot.ApplicationArguments
import org.springframework.boot.ApplicationRunner
import org.springframework.stereotype.Component

@Component
class MyApplicationRunner : ApplicationRunner {

  companion object {
    private val requiredVariables = setOf(
      "SOME_VARIABLE"
    )
  }

  override fun run(args: ApplicationArguments) {
    requiredVariables.forEach {
      System.getenv(it)
        ?: throw MissingEnvVariableException(it)
    }
  }

}

class MissingEnvVariableException(variableName: String) :
  RuntimeException("Application failed to start. Missing environment variable: $variableName")
</pre>



<p>This time, we introduce our set of required environment variables and check if they are present.</p>



<p>Whenever it&#8217;s missing, we throw the <em>MissingEnvVariableException </em>and prevent the app from starting:</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="">java.lang.IllegalStateException: Failed to execute ApplicationRunner
	at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:765) ~[spring-boot-3.1.3.jar:3.1.3]
	at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:752) ~[spring-boot-3.1.3.jar:3.1.3]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:319) ~[spring-boot-3.1.3.jar:3.1.3]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1306) ~[spring-boot-3.1.3.jar:3.1.3]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1295) ~[spring-boot-3.1.3.jar:3.1.3]
	at com.codersee.properties.PropertiesApplicationKt.main(PropertiesApplication.kt:13) ~[main/:na]
Caused by: com.codersee.properties.MissingEnvVariableException: Application failed to start. Missing environment variable: SOME_VARIABLE
	at com.codersee.properties.MyApplicationRunner.run(MyApplicationRunner.kt:19) ~[main/:na]
	at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:762) ~[spring-boot-3.1.3.jar:3.1.3]
	... 5 common frames omitted


Process finished with exit code 1</pre>



<p>This solution gives us a bit more control and allows us to customize the behavior better. </p>



<p>Would we like to log an error, or maybe send an alert request? No problem. With a few lines of code, we can easily achieve that. </p>



<p>Nevertheless, with this approach, we introduce a new, separate place in the code we need to remember every time we want to add a new environment variable. And this might be easily forgotten in bigger projects.</p>



<h2 class="wp-block-heading" id="h-fix-with-conditional">Fix With @Conditional </h2>



<p>As the next step, let&#8217;s take a look at the approach with <em>@Conditional</em> annotation, which slightly reduces the chance of forgetting about additional checks. </p>



<p>Firstly, let&#8217;s add a new class called <em>RequiredEnvVariablesCondition</em>:</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="">class RequiredEnvVariablesCondition : Condition {

  private val logger: Logger = LogManager.getLogger(this::class.java)

  companion object {
    private val requiredVariables = setOf(
      "SOME_VARIABLE"
    )
  }

  override fun matches(context: ConditionContext, metadata: AnnotatedTypeMetadata): Boolean {
    val isEnvVariableMissing = requiredVariables
      .any { envVariableName ->
        val isMissing = System.getenv(envVariableName) == null

        if (isMissing)
          logger.error("Variable $envVariableName is missing!")

        isMissing
      }

    return !isEnvVariableMissing
  }

}</pre>



<p>As we can see, this logic is pretty similar to what we&#8217;ve done previously. </p>



<p>Every time the environment variable is missing, we log an error message and return false from the overridden <em>matches</em> function. </p>



<p>So with that done, we can annotate our config class with <em>@Conditional</em>:  </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="">import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Conditional
import org.springframework.context.annotation.Configuration

@Configuration
@EnableConfigurationProperties(ExampleConfigurationProperties::class)
@Conditional(RequiredEnvVariablesCondition::class)
class Config</pre>



<p>This time, we will get the following output if we try to run the app without passing the environment variable:</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="">2023-09-14T07:20:58.813+02:00 ERROR 16188 --- [           main] c.c.p.RequiredEnvVariablesCondition      : Variable SOME_VARIABLE is missing!

***************************
APPLICATION FAILED TO START
***************************

Description:

Parameter 0 of constructor in com.codersee.properties.SomeComponent required a bean of type 'com.codersee.properties.ExampleConfigurationProperties' that could not be found.


Action:

Consider defining a bean of type 'com.codersee.properties.ExampleConfigurationProperties' in your configuration.


Process finished with exit code 1</pre>



<h2 class="wp-block-heading" id="h-bonus-use-value-instead">(Bonus) Use @Value Instead</h2>



<p>There&#8217;s no doubt that <em>@ConfigurationProperties</em> helps us write cleaner code and better organize configuration in our codebase. Moreover, we can effortlessly add new values to the project with this approach. </p>



<p>However, in some cases, we should consider using @Value annotation instead:   </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="">package com.codersee.properties

import jakarta.annotation.PostConstruct
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Component

@Component
class SomeComponent(
  @Value("\${example.some-property}") private val someProperty: String
) {

  @PostConstruct
  fun init() {
    println(someProperty)
  }
}</pre>



<p>This time, we can completely get rid of <em>@Configuration</em> and <em>@ConfigurationProperties</em> classes.</p>



<p>Instead of injecting a whole configuration object, we focus on the desired config value. </p>



<p>And whenever it&#8217;s missing, we get the following output out of the box:</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="">Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
2023-09-14T07:16:43.532+02:00 ERROR 18412 --- [           main] o.s.boot.SpringApplication               : Application run failed

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'someComponent' defined in file: Unexpected exception during bean creation
...
Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'SOME_VARIABLE' in value "${SOME_VARIABLE}"
...
Process finished with exit code 1</pre>



<p>As we can see, our Spring Boot app won&#8217;t start if the environment variable is missing and we will get a meaningful message. </p>



<p>But again, we should use <em>@Value</em> wisely and most of the time <em>@ConfigurationProperties</em> can be a better approach. </p>



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



<p>And that&#8217;s all for this article on how to fail a Spring Boot app on missing environment variables. </p>



<p>As I mentioned in the beginning- these are just a few ideas that came to my mind. If you figure out some other way, then please share them in the comments section below. </p>



<p>As always, you can find the source code in this GitHub repository. </p>



<p>Thank you for reading, and see you next time! 🙂 </p>
<p>The post <a href="https://blog.codersee.com/fail-spring-boot-app-on-missing-environment-variables/">Fail Spring Boot App on Missing Environment Variables</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/fail-spring-boot-app-on-missing-environment-variables/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Spring Boot 3 With Kotlin DSL. REST API Without Annotations</title>
		<link>https://blog.codersee.com/spring-boot-3-kotlin-dsl-rest-api-without-annotations/</link>
					<comments>https://blog.codersee.com/spring-boot-3-kotlin-dsl-rest-api-without-annotations/#respond</comments>
		
		<dc:creator><![CDATA[Piotr]]></dc:creator>
		<pubDate>Tue, 28 Mar 2023 06:00:42 +0000</pubDate>
				<category><![CDATA[Spring]]></category>
		<category><![CDATA[Kotlin]]></category>
		<category><![CDATA[Kotlin DSL]]></category>
		<category><![CDATA[Spring Boot]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=6004437</guid>

					<description><![CDATA[<p>In this step-by-step guide, we will learn how to expose a REST API using Spring Boot 3, Kotlin, bean definition, and router DSL.</p>
<p>The post <a href="https://blog.codersee.com/spring-boot-3-kotlin-dsl-rest-api-without-annotations/">Spring Boot 3 With Kotlin DSL. REST API Without Annotations</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading" id="h-1-introduction">1. Introduction</h2>



<p>Hello ! 🙂 Continuing the DSL topic, I would like to show you how to expose a <strong>REST API</strong> using <strong>Spring Boot 3</strong>, <strong>Kotlin</strong>, <strong>bean definition</strong>, and <strong>router DSL</strong>.</p>



<p>In my <a href="https://blog.codersee.com/spring-boot-3-kotlin-router-dsl/" target="_blank" rel="noopener">previous article</a>, I showed you how the router DSL works and how to expose endpoints manually without needing any web-related annotations, like <em>@RequestMapping</em>,<em> @RestController</em>,<em> @RequestBody</em>, etc&#8230;</p>



<p>But is it possible to eliminate the annotations when defining beans? Yes, and in this article, I will prove to you that it&#8217;s possible to expose a REST API without any annotations, like @Component or any specialization.</p>



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



<p>If you prefer <strong>video content</strong>, then check out my video:</p>



<div style="text-align: center; width: 90%; margin-left: 5%;">
<p></p></div>


<a href="https://blog.codersee.com/spring-boot-3-kotlin-dsl-rest-api-without-annotations/"><img decoding="async" src="https://blog.codersee.com/wp-content/plugins/wp-youtube-lyte/lyteCache.php?origThumbUrl=%2F%2Fi.ytimg.com%2Fvi%2FqITPCIq1xxw%2Fhqdefault.jpg" alt="YouTube Video"></a><br /><br /></p>



<p>If you find this content useful,<strong> please leave a subscription&nbsp;</strong> 😉</p>



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



<p>Firstly, let&#8217;s navigate to <a href="https://start.spring.io/" target="_blank" rel="noopener">https://start.spring.io/</a> and generate a new project:</p>



<figure class="wp-block-image aligncenter"><img loading="lazy" decoding="async" width="1024" height="1024" src="http://blog.codersee.com/wp-content/uploads/2023/03/spring_initializr_screenshot_spring_boot_3_kotlin-1024x1024.png" alt="The image is a screenshot from the start.spring.io page, which shows the setting necessary in order to create a Spring Boot 3 project with Kotlin DSL." class="wp-image-6004441" srcset="https://blog.codersee.com/wp-content/uploads/2023/03/spring_initializr_screenshot_spring_boot_3_kotlin-1024x1024.png 1024w, https://blog.codersee.com/wp-content/uploads/2023/03/spring_initializr_screenshot_spring_boot_3_kotlin-300x300.png 300w, https://blog.codersee.com/wp-content/uploads/2023/03/spring_initializr_screenshot_spring_boot_3_kotlin-150x150.png 150w, https://blog.codersee.com/wp-content/uploads/2023/03/spring_initializr_screenshot_spring_boot_3_kotlin-768x768.png 768w, https://blog.codersee.com/wp-content/uploads/2023/03/spring_initializr_screenshot_spring_boot_3_kotlin.png 1261w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>As we can see, in order to expose a REST API with Spring Boot 3 and Kotlin DSL, we don&#8217;t have to add any additional dependencies. The only one is the Spring Web, which lets us build web applications.</p>



<p>Following, please import the project to your favorite IDE (like IntelliJ).</p>



<h2 class="wp-block-heading" id="h-3-add-models">3. Add Models</h2>



<p>Nextly, let&#8217;s create the <code class="EnlighterJSRAW" data-enlighter-language="raw">model</code> package and add 3 data classes to our application:</p>



<ul class="wp-block-list">
<li><code class="EnlighterJSRAW" data-enlighter-language="raw">User</code>, which will transfer the data between the app and the data source,</li>



<li><code class="EnlighterJSRAW" data-enlighter-language="raw">UserDTO</code>, which will be serialized to JSON responses,</li>



<li>and <code class="EnlighterJSRAW" data-enlighter-language="raw">ErrorResponse</code>, shipping information about backend errors.</li>
</ul>



<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="">// User.kt
data class User(
  val id: Long? = null,
  val email: String,
  val name: String,
  val age: Int
)

// UserDTO.kt
data class UserDTO(
  val email: String,
  val name: String,
  val age: Int
)

// ErrorResponse.kt
data class ErrorResponse(
  val message: String
)
</pre>



<h2 class="wp-block-heading" id="h-4-implement-a-repository">4. Implement a Repository</h2>



<p>As the next step, let&#8217;s create a <code class="EnlighterJSRAW" data-enlighter-language="raw">repository</code> package.</p>


<p>[elementor-template id=&#8221;9007393&#8243;]</p>



<h3 class="wp-block-heading" id="h-4-1-add-userrepository-interface">4.1 Add UserRepository Interface</h3>



<p>Following, let&#8217;s introduce a <code class="EnlighterJSRAW" data-enlighter-language="raw">UserRepository</code>:</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="">interface UserRepository {
  fun create(user: User): User
  fun findAll(): List&lt;User>
  fun findById(id: Long): User?
  fun updateById(id: Long, user: User): User?
  fun deleteById(id: Long)
}
</pre>



<p>And although oftentimes I skip this part for the simplicity of my tutorials, you will later see that working with interfaces, rather than implementations gives us much more flexibility. And in real-life scenarios, we should consider using them.</p>



<h3 class="wp-block-heading" id="h-4-2-add-implementation">4.2 Add Implementation</h3>



<p>So nextly, let&#8217;s add the <code class="EnlighterJSRAW" data-enlighter-language="raw">UserCrudRepository</code> to the codebase:</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="">class UserCrudRepository(
  private val dataSource: MutableMap&lt;Long, User>
) : UserRepository {

  override fun create(user: User): User {
    val lastId = this.dataSource.keys.max()
    val incrementedId = lastId + 1
    val updatedUser = user.copy(id = incrementedId)

    this.dataSource[incrementedId] = updatedUser

    return updatedUser
  }

  override fun findAll(): List&lt;User> =
    this.dataSource.values
      .toList()

  override fun findById(id: Long): User? =
    this.dataSource[id]

  override fun updateById(id: Long, user: User): User? =
    this.dataSource[id]
      ?.let { foundUser -> 
        val updatedUser = user.copy(id = foundUser.id)
        this.dataSource[id] = updatedUser
        updatedUser
      }

  override fun deleteById(id: Long) {
    this.dataSource.remove(id)
  }
}
</pre>



<p>As we can see, this class is just a dummy in-memory database, which will be operating on the <code class="EnlighterJSRAW" data-enlighter-language="raw">dataSource</code> map provided as a constructor argument.</p>



<h3 class="wp-block-heading" id="h-4-3-implement-datasource">4.3 Implement DataSource</h3>



<p>With that being done, let&#8217;s create a new object called <code class="EnlighterJSRAW" data-enlighter-language="raw">DataSource</code> (can go to the <code class="EnlighterJSRAW" data-enlighter-language="raw">config</code> package):</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="">object DataSource {

  val devDataSource: MutableMap&lt;Long, User> = mutableMapOf(
    1L to User(1L, "email-1@gmail.com", "Name 1", 22),
    2L to User(2L, "email-2@gmail.com", "Name 2", 43),
    3L to User(3L, "email-3@gmail.com", "Name 3", 26),
    4L to User(4L, "email-4@gmail.com", "Name 4", 50)
  )

  val prodDataSource: MutableMap&lt;Long, User> = mutableMapOf(
    1L to User(1L, "prod-email-1@gmail.com", "Name 1", 22),
    2L to User(2L, "prod-email-2@gmail.com", "Name 2", 43),
    3L to User(3L, "prod-email-3@gmail.com", "Name 3", 26),
    4L to User(4L, "prod-email-4@gmail.com", "Name 4", 50)
  )

}
</pre>



<p>Well, in our application we will be dealing with dummy data.</p>



<p>However, in real life instead of two maps implementations, we would rather have a few implementations of UserRepository, for example:</p>



<ul class="wp-block-list">
<li>one, in-memory, which could be used for local development and testing,</li>



<li>the second, &#8220;real&#8221;, one, responsible for database connection.</li>
</ul>



<p>Either way, the purpose of this tutorial is to learn a bit more about Kotlin with bean definition DSL, and this setup will be useful in our considerations.</p>



<h2 class="wp-block-heading" id="h-5-configure-routing">5. Configure Routing</h2>



<p>In order to expose REST endpoints with Kotlin route DSL, we need to add two things:</p>



<ul class="wp-block-list">
<li><code class="EnlighterJSRAW" data-enlighter-language="raw">UserHandler</code>&#8211; which normally would be a UserController when working with annotations,</li>



<li>custom <code class="EnlighterJSRAW" data-enlighter-language="raw">RouteFunctionDSL</code>&#8211; responsible for URL mappings.</li>
</ul>



<h3 class="wp-block-heading" id="h-5-1-add-userhandler">5.1 Add UserHandler</h3>



<p>Firstly, let&#8217;s add a <code class="EnlighterJSRAW" data-enlighter-language="raw">handler</code> package and implement the <code class="EnlighterJSRAW" data-enlighter-language="raw">UserHandler</code>:</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="">class UserHandler(
  private val userRepository: UserRepository
) {

  fun createUser(
    request: ServerRequest
  ): ServerResponse {
    val userRequest = request.body(UserDTO::class.java)

    val createdUserResponse = userRepository.create(
      user = userRequest.toModel()
    )
      .toDTO()

    return ServerResponse.ok()
      .body(createdUserResponse)
  }

  fun findAllUsers(
    request: ServerRequest
  ): ServerResponse {
    val usersResponses = userRepository.findAll()
      .map(User::toDTO)

    return ServerResponse.ok()
      .body(usersResponses)
  }

  fun findUserById(
    request: ServerRequest
  ): ServerResponse {
    val id = request.pathVariable("id")
      .toLongOrNull()
      ?: return badRequestResponse("Invalid id")

    val userResponse = userRepository.findById(id)
      ?.toDTO()

    return userResponse
      ?.let { response ->
        ServerResponse.ok()
          .body(response)
      }
      ?: notFoundResponse(id)
  }

  fun updateUserById(
    request: ServerRequest
  ): ServerResponse {
    val id = request.pathVariable("id")
      .toLongOrNull()
      ?: return badRequestResponse("Invalid id")

    val userRequest = request.body(UserDTO::class.java)

    val updatedUser = userRepository.updateById(
      id = id,
      user = userRequest.toModel()
    )

    return updatedUser
      ?.let { response ->
        ServerResponse.ok()
          .body(response)
      }
      ?: notFoundResponse(id)
  }

  fun deleteUserById(
    request: ServerRequest
  ): ServerResponse {
    val id = request.pathVariable("id")
      .toLongOrNull()
      ?: return badRequestResponse("Invalid id")

    userRepository.deleteById(id)

    return ServerResponse.noContent()
      .build()
  }

  private fun badRequestResponse(reason: String): ServerResponse =
    ServerResponse.badRequest()
      .body(
        ErrorResponse(reason)
      )

  private fun notFoundResponse(id: Long): ServerResponse =
    ServerResponse.badRequest()
      .body(
        ErrorResponse("User with id: $id was not found.")
      )

}

private fun UserDTO.toModel(): User =
  User(
    email = this.email,
    name = this.name,
    age = this.age
  )

private fun User.toDTO(): UserDTO =
  UserDTO(
    email = this.email,
    name = this.name,
    age = this.age
  )
</pre>



<p>As we can see, the handler has only one parameter of the <code class="EnlighterJSRAW" data-enlighter-language="raw">UserRepository</code>&nbsp; type. Thanks do that, <strong>we are not coupled with any implementation</strong>. Moreover, we gained flexibility and we can provide the implementation even on the fly when configuring beans.</p>



<p>Another interesting thing is the usage of <em>ServerRequests</em> and <em>ServerResponses</em>. When working with Spring Boot and Kotlin routes DSL, that&#8217;s how we deal with incoming requests and responses. And although for more details I redirect you to my previous article about this topic, I wanted to mention this because of one thing. As you can see- instead of throwing exceptions, which then Spring would translate to HTTP responses, we have the possibility to return our custom payload (and status codes, as well).</p>



<h3 class="wp-block-heading" id="h-5-2-implement-routing">5.2 Implement Routing</h3>



<p>Nevertheless, handler implementation is not sufficient. We have to instruct Spring about how HTTP requests should be handled.</p>



<p>To do so, let&#8217;s add the <code class="EnlighterJSRAW" data-enlighter-language="raw">Routes.kt</code> file inside the <code class="EnlighterJSRAW" data-enlighter-language="raw">config</code> package:</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="">fun appRouter(userHandler: UserHandler) = router {
  "/api".nest {
    "/users".nest {
      POST(userHandler::createUser)
      GET(userHandler::findAllUsers)
      GET("/{id}", userHandler::findUserById)
      PUT("/{id}", userHandler::updateUserById)
      DELETE("/{id}", userHandler::deleteUserById)
    }
  }
}
</pre>



<p>The <code class="EnlighterJSRAW" data-enlighter-language="raw">appRouter</code> function has one parameter of the <code class="EnlighterJSRAW" data-enlighter-language="raw">UserHandler</code> type, which we then use to provide references to functions inside it.</p>



<p>It&#8217;s worth mentioning that function references in Kotlin are an interesting syntactic sugar. However, if you prefer not to use them, you can always simply make use of brackets:</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="">POST { request -> userHandler.createUser(request) }
</pre>



<h2 class="wp-block-heading" id="h-6-kotlin-bean-definition-dsl">6. Kotlin Bean Definition DSL</h2>



<p>With all of that being done, we can finally learn <strong>how the Kotlin bean definition DSL work with Spring Boot 3</strong>.</p>



<h2 class="wp-block-heading" id="h-6-1-implement-beansconfig">6.1 Implement BeansConfig</h2>



<p>Firstly, let&#8217;s add a new file called <code class="EnlighterJSRAW" data-enlighter-language="raw">BeansConfig.kt</code> inside the <code class="EnlighterJSRAW" data-enlighter-language="raw">config</code> package:</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="">val beans = beans {
  // beans definitions
}
</pre>



<p>The <code class="EnlighterJSRAW" data-enlighter-language="raw">beans {}</code> is nothing else than a function, which leverages the concept of <a href="https://kotlinlang.org/docs/type-safe-builders.html" target="_blank" rel="noopener">type-safe builders</a>.</p>



<p>Secondly, let&#8217;s add the <em>BeansConfig</em> class:</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="">class BeansConfig : ApplicationContextInitializer&lt;GenericApplicationContext> {

  override fun initialize(context: GenericApplicationContext) =
    beans.initialize(context)

}
</pre>



<p>In order to register the beans in our application context, we have to invoke the <code class="EnlighterJSRAW" data-enlighter-language="raw">initialize()</code>.</p>



<p>Lastly, we need to make Spring aware of our initializer in <code class="EnlighterJSRAW" data-enlighter-language="raw">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="">context:
  initializer:
    classes: com.codersee.kotlindsl.config.BeansConfig
</pre>



<h2 class="wp-block-heading" id="h-6-2-autowiring-by-type">6.2 Autowiring By Type</h2>



<p>As the next step, let&#8217;s see how we can define <em>UserHandler</em> and <em>router</em> beans and instruct Spring to<strong> autowire by type</strong>:</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="">bean&lt;UserHandler>()
bean(::appRouter)
</pre>



<p>As we can see, we don&#8217;t even have to specify the types of particular parameters and Spring will take care of that automatically.</p>



<p>Moreover, with<strong> callable reference</strong>, we can define a bean with the <code class="EnlighterJSRAW" data-enlighter-language="raw">appRouter</code> top-level function.</p>



<h2 class="wp-block-heading" id="h-6-3-explicitly-specify-bean-type">6.3 Explicitly Specify Bean Type</h2>



<p>Of course, if we would like to specify the type of the bean manually or wire by name, then we can do it, as well:</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="">bean("myHandlerBean") {
  UserHandler(ref())
}
bean {
  appRouter(
    ref("myHandlerBean")
  )
}
</pre>



<p>And this time we simply make use of the ref function, which is responsible for getting a reference to beans by type (line 2), or by type and name (line 6).</p>



<h2 class="wp-block-heading" id="h-6-4-conditional-beans-based-on-profile">6.4 Conditional Beans Based On Profile</h2>



<p>Following, let&#8217;s see how we can differentiate the data source based on the profile property:</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="">profile("dev") {
  bean {
    UserCrudRepository(devDataSource)
  }
}

profile("prod") {
  bean {
    UserCrudRepository(prodDataSource)
  }
}
</pre>



<p>With this approach, beans defined inside the <code class="EnlighterJSRAW" data-enlighter-language="raw">profile()</code> won&#8217;t be created unless the specified profile is active.</p>



<p>Of course, we can activate profiles, for example inside the <code class="EnlighterJSRAW" data-enlighter-language="raw">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:
  profiles:
    active: dev
</pre>



<h2 class="wp-block-heading" id="h-6-5-define-additional-beans-properties">6.5 Define Additional Beans Properties</h2>



<p>Following, let&#8217;s see how we can customize bean definitions:</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="">bean(
  name = "userHandler",
  scope = BeanDefinitionDsl.Scope.SINGLETON,
  isLazyInit = true,
  isPrimary = true,
  isAutowireCandidate = true,
  initMethodName = "",
  destroyMethodName = "",
  description = "description",
  role = BeanDefinitionDsl.Role.APPLICATION
)
</pre>



<p>As we can see, all parameters of the <code class="EnlighterJSRAW" data-enlighter-language="raw">bean</code> function have default values assigned.</p>



<p>Nevertheless, if we would like to set any values explicitly, then we can do it with ease.</p>



<h2 class="wp-block-heading" id="h-6-6-reading-environment-variables">6.6 Reading Environment Variables</h2>



<p>As the last thing, let&#8217;s see how we can read environment variables:</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="">val someVariable = env.systemEnvironment["SOME_VARIABLE"]
</pre>



<p>The systemEnvironment is nothing else than a <code class="EnlighterJSRAW" data-enlighter-language="raw">Map&lt;String, Object&gt;</code> instance, which is a result of <code class="EnlighterJSRAW" data-enlighter-language="raw">System.getenv()</code> invocation.</p>



<h2 class="wp-block-heading" id="h-7-spring-boot-3-kotlin-dsl-summary">7. Spring Boot 3 Kotlin DSL Summary</h2>



<p>And that&#8217;s all for this tutorial about how to implement a REST API without annotations using Spring Boot 3, Kotlin bean definition, and router DSL.</p>



<p>I&#8217;m happy to<strong> hear your feedback</strong>&#8211; trust me, such a simple comment can motivate a lot and help me to work on my weaknesses 🙂</p>



<p>Of course, if you would like to see the source code for this article, then you can find it in this <a href="https://github.com/codersee-blog/kotlin-spring-boot-3-kotlin-bean-definition-router-dsl" target="_blank" rel="noopener">GitHub repository</a>.</p>
<p>The post <a href="https://blog.codersee.com/spring-boot-3-kotlin-dsl-rest-api-without-annotations/">Spring Boot 3 With Kotlin DSL. REST API Without Annotations</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-3-kotlin-dsl-rest-api-without-annotations/feed/</wfw:commentRss>
			<slash:comments>0</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-15 18:22:40 by W3 Total Cache
-->