<?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>Piotr, Author at Codersee blog- Kotlin on the backend</title>
	<atom:link href="https://blog.codersee.com/author/piotr/feed/" rel="self" type="application/rss+xml" />
	<link></link>
	<description>Kotlin &#38; Backend Tutorials - Learn Through Practice.</description>
	<lastBuildDate>Thu, 17 Apr 2025 09:45:17 +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>Piotr, Author at Codersee blog- Kotlin on the backend</title>
	<link></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>A Quick Guide to htmx in Kotlin</title>
		<link>https://blog.codersee.com/quick-quide-to-htmx-kotlin/</link>
					<comments>https://blog.codersee.com/quick-quide-to-htmx-kotlin/#respond</comments>
		
		<dc:creator><![CDATA[Piotr]]></dc:creator>
		<pubDate>Sat, 29 Mar 2025 17:25:53 +0000</pubDate>
				<category><![CDATA[Kotlin]]></category>
		<category><![CDATA[Ktor]]></category>
		<category><![CDATA[htmx]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=18012838</guid>

					<description><![CDATA[<p>In this tutorial, we will learn the most important htmx concepts and create a basic application with Kotlin HTML DSL and Tailwind CSS.</p>
<p>The post <a href="https://blog.codersee.com/quick-quide-to-htmx-kotlin/">A Quick Guide to htmx in Kotlin</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>To be more specific, we will see a step-by-step process of writing <strong>HTML </strong>with typesafe <strong>Kotlin DSL</strong>, integrating <strong>htmx</strong>, and handling requests with <strong>Ktor</strong>. </p>



<p>Eventually, we will get the below user table:</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="521" src="http://blog.codersee.com/wp-content/uploads/2025/03/htmx_kotlin_app_screen-1024x521.webp" alt="Image presents a screenshot of result application created in Kotlin and htmx." class="wp-image-18012839" srcset="https://blog.codersee.com/wp-content/uploads/2025/03/htmx_kotlin_app_screen-1024x521.webp 1024w, https://blog.codersee.com/wp-content/uploads/2025/03/htmx_kotlin_app_screen-300x153.webp 300w, https://blog.codersee.com/wp-content/uploads/2025/03/htmx_kotlin_app_screen-768x391.webp 768w, https://blog.codersee.com/wp-content/uploads/2025/03/htmx_kotlin_app_screen.webp 1113w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p></p>



<p>But before we start, just a short disclaimer: although the main focus for this tutorial is the htmx / Kotlin / Ktor combination, I decided to bring <strong>Tailwind CSS</strong> to the project with the help of <a href="https://www.material-tailwind.com/" target="_blank" rel="noreferrer noopener nofollow">Material Tailwind components</a>. This way, I wanted to showcase the code that is closer to real-life scenarios. Not a next, plain HTML example. </p>



<p>So, although I tried my best, please keep in mind that the HTML part may need some more love when adapting 🫡</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Note: if you enjoy this content and would like to learn Ktor step-by-step, then check out my <a href="https://codersee.com/courses/ktor-server-pro/">Ktor Server Pro course</a>.</p>
</blockquote>



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


<figure class="wp-block-embed-youtube wp-block-embed is-type-video is-provider-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><a href="https://blog.codersee.com/quick-quide-to-htmx-kotlin/"><img decoding="async" src="https://blog.codersee.com/wp-content/plugins/wp-youtube-lyte/lyteCache.php?origThumbUrl=%2F%2Fi.ytimg.com%2Fvi%2FtstB08EDClw%2Fhqdefault.jpg" alt="YouTube Video"></a><br /><br /><figcaption></figcaption></figure>


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



<p>If you are here, then there is a high chance you&#8217;ve been looking for a Kotlin &amp; htmx combination, so you already know what it is. </p>



<p>Nevertheless, so we are all on on the same page: </p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>It is a library that allows you to access modern browser features directly from HTML, rather than using javascript.</p>
</blockquote>



<p>And we could add plenty of other things here, like the fact that it is small, dependency-free, and allows us to use AJAX, CSS Transitions, WebSockets, and so on. </p>



<p>But, IMO, the most important thing from the practical standpoint is that we can use attributes in HTML, and the library will do the &#8220;magic&#8221; for us:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="html" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">&lt;td>
  &lt;button hx-delete="/users/7a9079f0-c5a2-45d0-b4ae-e304b6908787" hx-swap="outerHTML" hx-target="closest tr">
... the rest
   </pre>



<p>The above following snippet means that when we click the button:</p>



<ul class="wp-block-list">
<li>the <code>DELETE /users/7a9079f0-c5a2-45d0-b4ae-e304b6908787</code> request is made</li>



<li>the closest tr element is replaced with the HTML response we receive</li>
</ul>



<p>And I believe this is all we need to know for now. </p>



<p>Again, a short note from my end: the <a href="https://htmx.org/docs/" target="_blank" rel="noreferrer noopener">htmx documentation</a> is a great resource, and I will refer a lot to it throughout this course to not reinvent the wheel. But at the same time, I want to deliver you a fully-contained article so that you don&#8217;t need to jump between pages 😉</p>



<h2 class="wp-block-heading" id="h-generate-ktor-project">Generate Ktor Project</h2>



<p>As the first step, let&#8217;s quickly generate a new Ktor project using <a href="https://start.ktor.io/">https://start.ktor.io/</a>:</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="590" src="http://blog.codersee.com/wp-content/uploads/2025/03/ktor_htmx_kotlin_html_dsl-1024x590.png" alt="" class="wp-image-18012840" srcset="https://blog.codersee.com/wp-content/uploads/2025/03/ktor_htmx_kotlin_html_dsl-1024x590.png 1024w, https://blog.codersee.com/wp-content/uploads/2025/03/ktor_htmx_kotlin_html_dsl-300x173.png 300w, https://blog.codersee.com/wp-content/uploads/2025/03/ktor_htmx_kotlin_html_dsl-768x443.png 768w, https://blog.codersee.com/wp-content/uploads/2025/03/ktor_htmx_kotlin_html_dsl.png 1277w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>As we can see, the only plugin we need to select is the <strong>Kotlin HTML DSL </strong>(this way, the Routing plugin is added, too).</p>



<p>Regarding the config, we are going to use <strong>Ktor 3.1.1</strong> with <strong>Netty</strong>, <strong>YAML </strong>config, and <strong>without a version catalog</strong>. But, of course, feel free to adjust it here according to your needs.</p>



<p>With that done, let&#8217;s download the project and import it to our IDE. </p>



<h2 class="wp-block-heading" id="h-create-user-repository">Create User Repository</h2>



<p>Following, let&#8217;s add the <code>repository</code> package and introduce a simple, in-memory user repository:</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 User(
    val id: String = UUID.randomUUID().toString(),
    val firstName: String,
    val lastName: String,
    val enabled: Boolean,
    val createdAt: LocalDateTime = LocalDateTime.now(),
)
class UserRepository {
    private val users = mutableListOf(
        User(firstName = "Jane", lastName = "Doe", enabled = true),
        User(firstName = "John", lastName = "Smith", enabled = true),
        User(firstName = "Alice", lastName = "Johnson", enabled = false),
        User(firstName = "Bob", lastName = "Williams", enabled = true),
    )
    fun create(firstName: String, lastName: String, enabled: Boolean): User =
        User(firstName = firstName, lastName = lastName, enabled = enabled)
            .also(users::add)
    fun findAll(): List&lt;User> = users
    fun delete(id: String): Boolean = users.removeIf { it.id == id }
}</pre>



<p>As we can see, nothing spectacular. Just 3 functions responsible for creating, searching, and deleting users. </p>



<h2 class="wp-block-heading" id="h-return-html-response-in-ktor">Return HTML Response in Ktor</h2>



<p>Following, let&#8217;s add the <code>routing</code> package and <code>Routing.kt</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="">fun Application.configureRouting(userRepository: UserRepository) {
    routing {
        get("/") {
            call.respondHtml {
                renderIndex(userRepository)
            }
        }
    }
}</pre>



<p>And update the main <code>Application.kt</code> to incorporate those 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="">fun Application.module() {
    val userRepository = UserRepository()
    configureRouting(userRepository)
}</pre>



<p>In a moment, we will add the <code>renderIndex</code> function, but for now, let&#8217;s focus on the above. </p>



<p>Long story short, the above code instructs the Ktor server to respond with the HTML response whenever it reaches the root path. By default, the <code>localhost:8080</code>. </p>



<h2 class="wp-block-heading" id="h-generate-html-page-with-kotlin-dsl">Generate HTML page with Kotlin DSL</h2>



<p>With that done, we have everything we need to start returning HTML responses. So in this section, we will prepare the baseline for htmx. </p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Note: if you feel that continuous server restarting is painful, please check out <a href="https://blog.codersee.com/ktor-auto-reload/">how to enable auto-reload in Ktor?</a>  </p>
</blockquote>



<h3 class="wp-block-heading" id="h-render-homepage">Render Homepage</h3>



<p>As the first step, let&#8217;s add the <code>html</code> package and the <code>renderIndex</code> function in <code>Index.kt</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.repository.UserRepository
import kotlinx.html.*
fun HTML.renderIndex(userRepository: UserRepository) {
    body {
        div {
            insertHeader()
        }
    }
}
private fun FlowContent.insertHeader() {
    h5 {
        +"Users list"
    }
}</pre>



<p>At this point, such a structure is overengineering. Nevertheless, our codebase is about to grow quickly in this tutorial, so we rely on Kotlin extension functions from the very beginning. </p>



<p>And as we can see, we use the HTML that represents the root element of an HTML document. Inside it, we can define the structure in a type-safe manner, thanks to the <a href="https://blog.codersee.com/kotlin-type-safe-builders-make-your-custom-dsl/">Kotlin DSL</a> feature. For the inner tags, we can use the <code>FlowContent</code> that is the marker interface for plenty of classes representing HTML tags, like div, headers, or the body.</p>



<p>And before we rerun the application, we must add the necessary import in <code>Routing.kt</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.repository.html.renderIndex</pre>



<p>With that done, let&#8217;s rerun the application and verify that everything is working. </p>



<h3 class="wp-block-heading" id="h-insert-html-form">Insert HTML Form</h3>



<p>Following, let&#8217;s use the HTML DSL to define the user form. </p>



<p>As the first step, let&#8217;s add the <code>Form.kt</code> to inside the <code>html</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 kotlinx.html.*
fun FlowContent.insertUserForm() {
    div {
        form {
            div {
                div {
                    label {
                        htmlFor = "first-name"
                        +"First Name"
                    }
                    input {
                        type = InputType.text
                        name = "first-name"
                        id = "first-name"
                        placeholder = "First Name"
                    }
                }
                div {
                    label {
                        htmlFor = "last-name"
                        +"Last Name"
                    }
                    input {
                        type = InputType.text
                        name = "last-name"
                        id = "last-name"
                        placeholder = "Last Name"
                    }
                }
                div {
                    label {
                        +"Account enabled"
                    }
                    div {
                        div {
                            input {
                                type = InputType.radio
                                name = "enabled-radio"
                                id = "radio-button-1"
                                value = "true"
                            }
                            label {
                                htmlFor = "radio-button-1"
                                +"Yes"
                            }
                        }
                        div {
                            input {
                                type = InputType.radio
                                name = "enabled-radio"
                                id = "radio-button-2"
                                value = "false"
                                checked = true
                            }
                            label {
                                htmlFor = "radio-button-2"
                                +"No"
                            }
                        }
                    }
                }
                div {
                    button {
                        +"Add user"
                    }
                }
            }
        }
    }
}</pre>



<p>As we can see, the <strong>Kotlin DSL allows us to define everything in a neat, structured manner</strong>. And although I had a chance to use it in various places, with HTML, it feels so&#8230;natural. We write the code pretty similar to HTML.</p>



<p>Of course, before heading to the next part, let&#8217;s make use of our function: </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 HTML.renderIndex(userRepository: UserRepository) {
    body {
        div {
            insertHeader()
            insertUserForm()
        }
    }
}</pre>



<p>We can clearly see that the extraction was a good idea 😉 </p>



<h3 class="wp-block-heading" id="h-create-user-table">Create User Table</h3>



<p>As the next step, let&#8217;s add the <code>UserTable.kt</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.repository.User
import kotlinx.html.*
import java.time.format.DateTimeFormatter
fun FlowContent.insertUserTable(users: List&lt;User>) {
    div {
        table {
            thead {
                tr {
                    th {
                        +"User"
                    }
                    th {
                        +"Status"
                    }
                    th {
                        +"Created At"
                    }
                    th {}
                }
            }
            tbody {
                id = "users-table"
                users.forEach { user ->
                    tr {
                        insertUserRowCells(user)
                    }
                }
            }
        }
    }
}
fun TR.insertUserRowCells(user: User) {
    td {
        div {
            p {
                +"${user.firstName} ${user.lastName}"
            }
        }
    }
    td {
        div {
            div {
                val enabledLabel = if (user.enabled) "Enabled" else "Disabled"
                val labelColor = if (user.enabled) "green" else "red"
                span { +enabledLabel }
            }
        }
    }
    td {
        p {
            +user.createdAt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
        }
    }
    td {
        button {
            + "Delete"
        }
    }
}</pre>



<p>As we can see this time, the Kotlin HTML DSL allows us to easily generate <strong>dynamic HTML tags</strong>. We use the passed user list to create a row for every user we &#8220;persisted&#8221;. We even make the decision about the label and future color based on the list. </p>



<p>Again, let&#8217;s get back to <code>Routing.kt</code> and invoke our function:</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 HTML.renderIndex(userRepository: UserRepository) {
    body {
        div {
            insertHeader()
            insertUserForm()
            insertUserTable(userRepository.findAll())
        }
    }
}
</pre>



<p>And although I am pretty sure it won&#8217;t become the eighth wonder of the World, our application starts looking similar to what we want to achieve:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="382" height="428" src="http://blog.codersee.com/wp-content/uploads/2025/03/ktor_kotlin_html_dsl_plain_application_screen.png" alt="Image is a screenshot of a plain HTML application generated with Kotlin HTML DSL in Ktor" class="wp-image-18512857" style="object-fit:cover" srcset="https://blog.codersee.com/wp-content/uploads/2025/03/ktor_kotlin_html_dsl_plain_application_screen.png 382w, https://blog.codersee.com/wp-content/uploads/2025/03/ktor_kotlin_html_dsl_plain_application_screen-268x300.png 268w" sizes="auto, (max-width: 382px) 100vw, 382px" /></figure>



<h2 class="wp-block-heading" id="h-htmx-and-kotlin">htmx and Kotlin</h2>



<p>With all of that done, we have everything prepared to start actually working with <strong>htmx and Kotlin</strong>.</p>



<p>Again, you will see a lot of references taken from the <a href="https://htmx.org/docs/">docs</a> (which I encourage you to visit after this tutorial).</p>



<h3 class="wp-block-heading" id="h-import"> Import</h3>



<p>As the first step, let&#8217;s import <strong>htmx</strong>. </p>



<p>Again, it is nothing else than the JavaScript library, so the only thing we need is to add it inside the script block of our HTML. </p>



<p>And the easiest way is to simply fetch it from their CDN and put it inside the Kotlin <code>script</code> DSL block:</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 HTML.renderIndex(userRepository: UserRepository) {
    head {
        script {
            src = "https://unpkg.com/htmx.org@2.0.4"
        }
    }
    body {
        div {
            insertHeader()
            insertUserForm()
            insertUserTable(userRepository.findAll())
        }
    }
}</pre>



<h3 class="wp-block-heading" id="h-ajax-requests-create-user">AJAX Requests &#8211; Create User</h3>



<p>As we saw in the very beginning, htmx allows us to define requests with attributes. </p>



<p>To be more specific, we can use the following attributes: </p>



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



<li>hx-post</li>



<li>hx-put</li>



<li>hx-patch</li>



<li>hx-delete</li>
</ul>



<p>And, long story short, when the element is triggered, an AJAX request is made to the specified URL.</p>



<p>So, let&#8217;s update our form then: </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 FlowContent.insertUserForm() {
    div {
        form {
            attributes["hx-post"] = "/users"
... the rest</pre>



<p>This way, our html now contains:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="html" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">&lt;form hx-post="/users"></pre>



<p>And when we hit the <code>Add user</code> button, we can see that the request is triggered (but, it results in <code>404</code> response given we have no handler). </p>



<p>So, let&#8217;s add the endpoint responsible for user creation: </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 Application.configureRouting(userRepository: UserRepository) {
    routing {
        get("/") {
            call.respondHtml {
                renderIndex(userRepository)
            }
        }
        route("/users") {
            post {
                val formParams = call.receiveParameters()
                val firstName = formParams["first-name"]!!
                val lastName = formParams["last-name"]!!
                val enabled = formParams["enabled-radio"]!!.toBoolean()
                val createdItem = userRepository.create(firstName, lastName, enabled)
                val todoItemHtml = createHTML().tr { insertUserRowCells(createdItem) }
                call.respondText(
                    todoItemHtml,
                    contentType = ContentType.Text.Html,
                )
            }
        }
    }
}</pre>



<p>At this point, it should not be a surprise, but we can see that in Ktor, we can do that quite easily. </p>



<p>Our code snippet will read the form parameters sent from the browser, &#8220;create&#8221; a new user, and return a <code>200 OK</code> response with:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="html" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">&lt;tr>
  &lt;td>
    &lt;div>
      &lt;p>Admiral Jahas&lt;/p>
    &lt;/div>
  &lt;/td>
  &lt;td>
    &lt;div>
      &lt;div>&lt;span>Disabled&lt;/span>&lt;/div>
    &lt;/div>
  &lt;/td>
  &lt;td>
    &lt;p>2025-03-29 08:16:40&lt;/p>
  &lt;/td>
  &lt;td>&lt;button>Delete&lt;/button>&lt;/td>
&lt;/tr>
</pre>



<p>The <strong>important </strong>thing to mention here is that <code>respondHtml</code> requires us to respond with whole body! So, to bypass that, we use the <code>respondText</code> function and set the content type as HTML. </p>



<p>However, when we open up the browser, we can see this:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="388" height="362" src="http://blog.codersee.com/wp-content/uploads/2025/03/image-3.png" alt="Image is a screenshot from htmx POST invocation with invalid target." class="wp-image-18512868" srcset="https://blog.codersee.com/wp-content/uploads/2025/03/image-3.png 388w, https://blog.codersee.com/wp-content/uploads/2025/03/image-3-300x280.png 300w" sizes="auto, (max-width: 388px) 100vw, 388px" /></figure>



<p>And I am pretty sure that is not what we wanted 😀 </p>



<h3 class="wp-block-heading" id="h-htmx-target">htmx Target</h3>



<p><strong>Lesson one:</strong> if we want to instruct htmx to load the response into a different element than the one that made the request, we must use the <code>hx-target</code> attribute that takes the CSS selector, or:</p>



<ul class="wp-block-list">
<li><code>this</code> keyword- to refer to the element with <code>hx-target</code> attribute</li>



<li><code>closest</code>, <code>next</code>, <code>previous</code> <code>&lt;CSS selector&gt;</code> (like <code>closest div</code>)- to target the closest ancestor element or itself</li>



<li><code>find &lt;CSS selector</code> &#8211; to target the first child descendant element that matches the given CSS selector</li>
</ul>



<p>As a proof, let&#8217;s take a look at what happened previously: </p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="345" height="137" src="http://blog.codersee.com/wp-content/uploads/2025/03/image-4.png" alt="" class="wp-image-18512871" srcset="https://blog.codersee.com/wp-content/uploads/2025/03/image-4.png 345w, https://blog.codersee.com/wp-content/uploads/2025/03/image-4-300x119.png 300w" sizes="auto, (max-width: 345px) 100vw, 345px" /></figure>



<p>As we can see, the table row was inserted inside the <code>form</code>. And that does not make sense, at all. </p>



<p>So, to fix that, let&#8217;s target the <code>table tbody</code> 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="">fun FlowContent.insertUserForm() {
    div {
        form {
            attributes["hx-post"] = "/users"
            attributes["hx-target"] = "#users-table"</pre>



<p>As a result, all the other rows are deleted, but it seems to be closer to what we want:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="379" height="249" src="http://blog.codersee.com/wp-content/uploads/2025/03/image-5.png" alt="" class="wp-image-18512872" srcset="https://blog.codersee.com/wp-content/uploads/2025/03/image-5.png 379w, https://blog.codersee.com/wp-content/uploads/2025/03/image-5-300x197.png 300w" sizes="auto, (max-width: 379px) 100vw, 379px" /></figure>



<h3 class="wp-block-heading" id="h-swapping-in-htmx">Swapping in htmx</h3>



<p><strong>Next lesson:</strong> by default, <strong>htmx replaces the innerHTML of the target element</strong>. </p>



<p>So, in our case, the user was added successfully. We can even refresh the page and see that the array contains all created users. However, we have not defined the <code>hx-swap</code> so the <code>tbody</code> inner HTML was deleted, and our returned one was inserted instead. </p>



<p>So, we must add the <code>hx-swap</code> with one of the following values:</p>



<ul class="wp-block-list">
<li><code>innerHTML</code>&#8211; puts the content inside the target element</li>



<li><code>outerHTML</code>&#8211; replaces the entire target element with the returned content</li>



<li><code>afterbegin</code>&#8211; prepends the content before the first child inside the target</li>



<li><code>beforebegin</code>&#8211; prepends the content before the target in the target’s parent element</li>



<li><code>beforeend</code>&#8211; appends the content after the last child inside the target</li>



<li><code>afterend</code>&#8211; appends the content after the target in the target’s parent element</li>



<li><code>delete</code>&#8211; deletes the target element regardless of the response</li>



<li><code>none</code>&#8211; does not append content from response (Out of Band Swaps and Response Headers will still be processed)</li>
</ul>



<p>And in our case, the <code>beforeend</code> is the one we should pick to append the created user at the end of the list:</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 FlowContent.insertUserForm() {
    div {
        form {
            attributes["hx-post"] = "/users"
            attributes["hx-target"] = "#users-table"
            attributes["hx-swap"] = "beforeend"</pre>



<p>When we restart the app, everything works fine! 🙂 </p>



<h3 class="wp-block-heading" id="h-dynamic-htmx-tags-in-kotlin">Dynamic htmx Tags in Kotlin</h3>



<p>At this point, we know how to display and add new users with htmx. So, let&#8217;s learn how to delete them.</p>



<p>As the first step, let&#8217;s prepare a Ktor handler inside the <code>route("/users")</code> for the DELETE request:</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="">delete("/{id}") {
    val id = call.parameters["id"]!!
    userRepository.delete(id)
    call.respond(HttpStatusCode.OK)
}</pre>



<p>With that code, whenever a <code>DELETE /users/{some-id}</code> is made, we remove the user from our list and return 200 OK.</p>



<p><strong>Important lesson here:</strong> for simplicity, we return <code>200 OK</code> (and not <code>204 No Content</code>), because by default, htmx ignores successful responses other than 200.</p>



<p>Following, let&#8217;s update our button: </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="">td {
        button {
            attributes["hx-delete"] = "/users/${user.id}"
            attributes["hx-swap"] = "outerHTML"
            attributes["hx-target"] = "closest tr"</pre>



<p>So, firstly, whenever we generate our button, we use <strong>Kotlin string interpolation</strong> to put the user identifier in the <code>hx-delete</code> attribute value. A neat and easy way to achieve that with Kotlin. </p>



<p>When it comes to swapping, we want to find the closest <code>tr</code> parent and swap the entire element with the response. And as the response contains <strong>nothing</strong>, it will be simply removed😉</p>



<p>After we rerun the application, we will see everything working perfectly fine!</p>



<h3 class="wp-block-heading" id="h-error-handling-with-ktor-and-htmx">Error Handling with Ktor and htmx</h3>



<p>Following, let&#8217;s learn how we can handle any Ktor error response in htmx. </p>



<p>For that purpose, let&#8217;s update the POST handler in Ktor:</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 {
    val formParams = call.receiveParameters()
    val firstName = formParams["first-name"]!!
    val lastName = formParams["last-name"]!!
    val enabled = formParams["enabled-radio"]!!.toBoolean()
    if (firstName.isBlank() || lastName.isBlank())
        return@post call.respond(HttpStatusCode.BadRequest)
... the rest of the code</pre>



<p>With that validation, whenever <code>first-name</code> or <code>last-name</code> form parameter is blank, the API client receives <code>400 Bad Request</code>.</p>



<p>After we restart the server and try to make a request without passing first or last name, we see that nothing is happening. No pop-ups, alerts, nothing. The only indication that the request is actually made is thenetwork tab of our browser.</p>



<p>Well, unfortunately (or fortunately?), htmx does not provide any handling out-of-the-box.</p>



<p>But, it throws two events:</p>



<ul class="wp-block-list">
<li><code>htmx:responseError</code>&#8211; in the event of an error response from the server, like <code>400 Bad Request</code></li>



<li><code>htmx:sendError</code>&#8211; in case of connection error</li>
</ul>



<p>So, let&#8217;s add a tiny bit of JS in Kotlin, then: </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 fun BODY.insertErrorHandlingScripts() {
    script {
        +"""
            document.body.addEventListener('htmx:responseError', function(evt) {
              alert('An error occurred! HTTP status:' + evt.detail.xhr.status);
            });
            
            document.body.addEventListener('htmx:sendError', function(evt) {
              alert('Server unavailable!');
            });
        """.trimIndent()
    }
}</pre>



<p>And let&#8217;s add this script at the end of the body when rendering the homepage:</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 HTML.renderIndex(userRepository: UserRepository) {
    head {
        script {
            src = "https://unpkg.com/htmx.org@2.0.4"
        }
    }
    body {
        div {
            insertHeader()
            insertUserForm()
            insertUserTable(userRepository.findAll())
        }
        insertErrorHandlingScripts()
    }
}</pre>



<p>Excellent! From now on, whenever the API client receives an error response, the alert is displayed. Moreover, if we turn off our server, we will see the error response, too.</p>



<p>And basically, that is all for the htmx part with Kotlin. From now on, we are going to work on the styling of our application😉</p>



<h2 class="wp-block-heading" id="h-returning-images-in-ktor">Returning Images in Ktor</h2>



<p>Before we head to the Tailwind CSS part, let&#8217;s learn one more thing in Ktor: static responses handling.</p>



<p>So, let&#8217;s put the below image in the <code>resources -&gt; img</code> directory:</p>



<figure class="wp-block-image size-full is-resized"><img loading="lazy" decoding="async" width="500" height="500" src="http://blog.codersee.com/wp-content/uploads/2025/03/placeholder.png" alt="Image is a placeholder used ad an example in our project and shows Codersee logo." class="wp-image-18512878" style="width:100px" srcset="https://blog.codersee.com/wp-content/uploads/2025/03/placeholder.png 500w, https://blog.codersee.com/wp-content/uploads/2025/03/placeholder-300x300.png 300w, https://blog.codersee.com/wp-content/uploads/2025/03/placeholder-150x150.png 150w" sizes="auto, (max-width: 500px) 100vw, 500px" /></figure>



<p>And let&#8217;s add this image as a placeholder to each row in our table: </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 TR.insertUserRowCells(user: User) {
    td {
        div {
            img {
                src = "/img/placeholder.png"
            }</pre>



<p>When we rerun the application, we can see that it does not work.</p>



<p>Well, to fix that, we must instruct Ktor to serve our resources as static content:</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 Application.configureRouting(userRepository: UserRepository) {
    routing {
        staticResources("/img", "img")</pre>



<p>This time, when we restart the application, we see that placeholders are working fine. </p>



<p>And we will style them in a moment 😉 </p>



<h2 class="wp-block-heading" id="h-styling-with-tailwind-css">Styling With Tailwind CSS</h2>



<p>At this point, we have a fully working Kotlin and htmx integration.</p>



<p>So, if we already did something else than the JSON response, let&#8217;s make it nice😄</p>



<h3 class="wp-block-heading" id="h-import-tailwind">Import Tailwind</h3>



<p>Just like with htmx, let&#8217;s use the CDN to import Tailwind to the 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 HTML.renderIndex(userRepository: UserRepository) {
    head {
        script {
            src = "https://unpkg.com/htmx.org@2.0.4"
        }
        script {
            src = "https://unpkg.com/@tailwindcss/browser@4"
        }
    }</pre>



<h3 class="wp-block-heading" id="h-update-index">Update Index </h3>



<p>Then, let&#8217;s navigate to the <code>Index.kt</code> and add adjustments:</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 HTML.renderIndex(userRepository: UserRepository) {
    head {
        script {
            src = "https://unpkg.com/htmx.org@2.0.4"
        }
        script {
            src = "https://unpkg.com/@tailwindcss/browser@4"
        }
    }
    body {
        div {
            classes = setOf("m-auto max-w-5xl w-full overflow-hidden")
            insertHeader()
            insertUserForm()
            insertUserTable(userRepository.findAll())
        }
        insertErrorHandlingScripts()
    }
}
private fun FlowContent.insertHeader() {
    h5 {
        classes =
            setOf("py-8 block font-sans text-xl antialiased font-semibold leading-snug tracking-normal text-blue-gray-900")
        +"Users list"
    }
}
private fun BODY.insertErrorHandlingScripts() {
    script {
        +"""
            document.body.addEventListener('htmx:responseError', function(evt) {
              alert('An error occurred! HTTP status:' + evt.detail.xhr.status);
            });
            
            document.body.addEventListener('htmx:sendError', function(evt) {
              alert('Server unavailable!');
            });
        """.trimIndent()
    }
}</pre>



<p>As we can see, we can use the <code>classes</code> in Kotlin HTML DSL to prive classes names as a Set of String values:</p>



<p>classes = setOf(&#8220;m-auto max-w-5xl w-full overflow-hidden&#8221;)</p>



<p>In my case, I prefer simply copy-pasting those values instead of separating them with colons.</p>



<h2 class="wp-block-heading" id="h-refactor-form">Refactor Form</h2>



<p>Then, let&#8217;s update the <code>Form.kt</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="">fun FlowContent.insertUserForm() {
    div {
        classes = setOf("mx-auto w-full")
        form {
            attributes["hx-post"] = "/users"
            attributes["hx-target"] = "#users-table"
            attributes["hx-swap"] = "beforeend"
            div {
                classes = setOf("-mx-3 flex flex-wrap")
                div {
                    classes = setOf("w-full px-3 sm:w-1/4")
                    label {
                        classes = setOf("mb-3 block text-base font-medium text-[#07074D]")
                        htmlFor = "first-name"
                        +"First Name"
                    }
                    input {
                        classes =
                            setOf("w-full rounded-md border border-[#e0e0e0] bg-white py-3 px-6 text-base font-medium text-[#6B7280] outline-none focus:border-[#6A64F1] focus:shadow-md")
                        type = InputType.text
                        name = "first-name"
                        id = "first-name"
                        placeholder = "First Name"
                    }
                }
                div {
                    classes = setOf("w-full px-3 sm:w-1/4")
                    label {
                        classes = setOf("mb-3 block text-base font-medium text-[#07074D]")
                        htmlFor = "last-name"
                        +"Last Name"
                    }
                    input {
                        classes =
                            setOf("w-full rounded-md border border-[#e0e0e0] bg-white py-3 px-6 text-base font-medium text-[#6B7280] outline-none focus:border-[#6A64F1] focus:shadow-md")
                        type = InputType.text
                        name = "last-name"
                        id = "last-name"
                        placeholder = "Last Name"
                    }
                }
                div {
                    classes = setOf("w-full px-3 sm:w-1/4")
                    label {
                        classes = setOf("mb-3 block text-base font-medium text-[#07074D]")
                        +"Account enabled"
                    }
                    div {
                        classes = setOf("flex items-center space-x-6 pt-3")
                        div {
                            classes = setOf("flex items-center")
                            input {
                                classes = setOf("h-5 w-5")
                                type = InputType.radio
                                name = "enabled-radio"
                                id = "radio-button-1"
                                value = "true"
                            }
                            label {
                                classes = setOf("pl-3 text-base font-medium text-[#07074D]")
                                htmlFor = "radio-button-1"
                                +"Yes"
                            }
                        }
                        div {
                            classes = setOf("flex items-center")
                            input {
                                classes = setOf("h-5 w-5")
                                type = InputType.radio
                                name = "enabled-radio"
                                id = "radio-button-2"
                                value = "false"
                                checked = true
                            }
                            label {
                                classes = setOf("pl-3 text-base font-medium text-[#07074D]")
                                htmlFor = "radio-button-2"
                                +"No"
                            }
                        }
                    }
                }
                div {
                    classes = setOf("w-full px-3 sm:w-1/4 pt-8")
                    button {
                        classes =
                            setOf("cursor-pointer rounded-md bg-slate-800 py-3 px-8 text-center text-base font-semibold text-white outline-none")
                        +"Add user"
                    }
                }
            }
        }
    }
}</pre>



<p>Similarly, we don&#8217;t change anything in here apart from adding a bunch of classes. A whooooole bunch of classes 🙂 </p>



<h2 class="wp-block-heading" id="h-modify-user-table">Modify User Table</h2>



<p>As the last step, let&#8217; apply changes to our user table: </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 FlowContent.insertUserTable(users: List&lt;User>) {
    div {
        classes = setOf("px-0 overflow-scroll")
        table {
            classes = setOf("w-full mt-4 text-left table-auto min-w-max")
            thead {
                tr {
                    th {
                        classes = setOf("p-4 border-y border-blue-gray-100 bg-blue-gray-50/50")
                        +"User"
                    }
                    th {
                        classes = setOf("p-4 border-y border-blue-gray-100 bg-blue-gray-50/50")
                        +"Status"
                    }
                    th {
                        classes = setOf("p-4 border-y border-blue-gray-100 bg-blue-gray-50/50")
                        +"Created At"
                    }
                    th {
                        classes = setOf("p-4 border-y border-blue-gray-100 bg-blue-gray-50/50")
                    }
                }
            }
            tbody {
                id = "users-table"
                users.forEach { user ->
                    tr {
                        insertUserRowCells(user)
                    }
                }
            }
        }
    }
}
fun TR.insertUserRowCells(user: User) {
    td {
        classes = setOf("p-4 border-b border-blue-gray-50")
        div {
            classes = setOf("flex items-center gap-3")
            img {
                classes = setOf("relative inline-block h-9 w-9 !rounded-full object-cover object-center")
                src = "/img/placeholder.png"
            }
            p {
                classes = setOf("block font-sans text-sm antialiased font-normal leading-normal text-blue-gray-900")
                +"${user.firstName} ${user.lastName}"
            }
        }
    }
    td {
        classes = setOf("p-4 border-b border-blue-gray-50")
        div {
            classes = setOf("w-max")
            div {
                val enabledLabel = if (user.enabled) "Enabled" else "Disabled"
                val labelColor = if (user.enabled) "green" else "red"
                classes =
                    setOf("relative grid items-center px-2 py-1 font-sans text-xs font-bold text-black-900 uppercase rounded-md select-none whitespace-nowrap bg-$labelColor-500/20")
                span { +enabledLabel }
            }
        }
    }
    td {
        classes = setOf("p-4 border-b border-blue-gray-50")
        p {
            classes = setOf("block font-sans text-sm antialiased font-normal leading-normal text-blue-gray-900")
            +user.createdAt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
        }
    }
    td {
        classes = setOf("p-4 border-b border-blue-gray-50")
        button {
            classes =
                setOf("cursor-pointer relative h-10 max-h-[40px] w-10 max-w-[40px] select-none rounded-lg text-center align-middle font-sans text-xs font-medium uppercase text-gray-900 transition-all hover:bg-gray-900/10 active:bg-gray-900/20 disabled:pointer-events-none disabled:opacity-50 disabled:shadow-none")
            attributes["hx-delete"] = "/users/${user.id}"
            attributes["hx-swap"] = "outerHTML"
            attributes["hx-target"] = "closest tr"
            unsafe {
                +"""
                    &lt;span class="absolute transform -translate-x-1/2 -translate-y-1/2 top-1/2 left-1/2">
                        &lt;svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
                             stroke-width="2" stroke="currentColor" class="w-6 h-6">
                            &lt;path stroke-linecap="round" stroke-linejoin="round"
                                  d="M6 18L18 6M6 6l12 12"/>
                        &lt;/svg>
                    &lt;/span>
                """.trimIndent()
            }
        }
    }
}</pre>



<p>And here, apart from the CSS classes, we also added the <code>X</code> icon with the <code>unsafe</code> function from Kotlin HTML DSL:</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="">unsafe {
    +"""
        &lt;span class="absolute transform -translate-x-1/2 -translate-y-1/2 top-1/2 left-1/2">
            &lt;svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
                 stroke-width="2" stroke="currentColor" class="w-6 h-6">
                &lt;path stroke-linecap="round" stroke-linejoin="round"
                      d="M6 18L18 6M6 6l12 12"/>
            &lt;/svg>
        &lt;/span>
    """.trimIndent()
}</pre>



<p>And voila! When we run the application now, we should see a pretty decent-looking UI😉</p>



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



<p>That&#8217;s all for this tutorial on how to work with htmx and Kotlin HTML DSL in Ktor.</p>



<p>Again, if you are tired of wasting your time looking for good Ktor resources, then check out my <a href="https://codersee.com/courses/ktor-server-pro/">Ktor Server Pro course</a>:</p>



<p>▶️ Over 15 hours of video content divided into over 130 lessons</p>



<p>🛠️ Hands-on approach: together, we implement 4 actual services</p>



<p>✅ top technologies: you will learn not only Ktor, but also how to integrate it with modern stack including JWT, PostgreSQL, MySQL, MongoDB, Redis, and Testcontainers.</p>



<p>Lastly, you can find the source code for this lesson in <a href="https://github.com/codersee-blog/ktor-htmx" target="_blank" rel="noreferrer noopener">this GitHub repository</a>.</p>
<p>The post <a href="https://blog.codersee.com/quick-quide-to-htmx-kotlin/">A Quick Guide to htmx in 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/quick-quide-to-htmx-kotlin/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Auto-reload in Ktor</title>
		<link>https://blog.codersee.com/ktor-auto-reload/</link>
					<comments>https://blog.codersee.com/ktor-auto-reload/#respond</comments>
		
		<dc:creator><![CDATA[Piotr]]></dc:creator>
		<pubDate>Tue, 25 Mar 2025 18:48:04 +0000</pubDate>
				<category><![CDATA[Ktor]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=18012802</guid>

					<description><![CDATA[<p>In this, short quide, I will show you how to enable the development mode and auto-reload feature in Ktor application.</p>
<p>The post <a href="https://blog.codersee.com/ktor-auto-reload/">Auto-reload in Ktor</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Together, we will learn how to enable the Ktor<strong> development mode</strong> in several ways and how <strong>auto-reload</strong> can help with the <strong>Kotlin HTML DSL app</strong> example. </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>


<figure class="wp-block-embed-youtube wp-block-embed is-type-video is-provider-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><a href="https://blog.codersee.com/ktor-auto-reload/"><img decoding="async" src="https://blog.codersee.com/wp-content/plugins/wp-youtube-lyte/lyteCache.php?origThumbUrl=%2F%2Fi.ytimg.com%2Fvi%2F0SHrXcyJhRs%2Fhqdefault.jpg" alt="YouTube Video"></a><br /><br /><figcaption></figcaption></figure>


<h2 class="wp-block-heading" id="h-quick-ktor-setup">Quick Ktor Setup</h2>



<p>As the first step, let&#8217;s quickly prepare a basic Ktor application responding with HTML.</p>



<p>To do so, let&#8217;s navigate to the <a href="https://start.ktor.io" target="_blank" rel="noreferrer noopener">https://start.ktor.io</a> page and configure the artifact first:</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="465" src="http://blog.codersee.com/wp-content/uploads/2025/03/start_ktor_io_new_project_artifact_settings-1024x465.png" alt="" class="wp-image-18012803" srcset="https://blog.codersee.com/wp-content/uploads/2025/03/start_ktor_io_new_project_artifact_settings-1024x465.png 1024w, https://blog.codersee.com/wp-content/uploads/2025/03/start_ktor_io_new_project_artifact_settings-300x136.png 300w, https://blog.codersee.com/wp-content/uploads/2025/03/start_ktor_io_new_project_artifact_settings-768x348.png 768w, https://blog.codersee.com/wp-content/uploads/2025/03/start_ktor_io_new_project_artifact_settings.png 1221w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>The choice is up to you here. From my end, I will be using the latest Ktor version with the YAML config and without the version catalog. </p>



<p>Then, let&#8217;s add the Kotlin HTML DSL plugin necessary for our simple example:</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="446" src="http://blog.codersee.com/wp-content/uploads/2025/03/start_ktor_io_new_project_html_dsl_plugin_selection-1024x446.png" alt="" class="wp-image-18012804" srcset="https://blog.codersee.com/wp-content/uploads/2025/03/start_ktor_io_new_project_html_dsl_plugin_selection-1024x446.png 1024w, https://blog.codersee.com/wp-content/uploads/2025/03/start_ktor_io_new_project_html_dsl_plugin_selection-300x131.png 300w, https://blog.codersee.com/wp-content/uploads/2025/03/start_ktor_io_new_project_html_dsl_plugin_selection-768x335.png 768w, https://blog.codersee.com/wp-content/uploads/2025/03/start_ktor_io_new_project_html_dsl_plugin_selection.png 1225w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



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



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>If you are working in IntelliJ IDEA, then please make sure that you use JDK 23, too. </p>



<p>You can do that in two places: </p>



<ul class="wp-block-list">
<li>Settings -&gt; Build, Execution, Deployment -&gt; Build Tools -&gt; Gradle. Here, please set the Gradle JVM to 23</li>



<li>Project Structure -&gt; Project Settings -&gt; Project. On this page, please set the SDK to 23 and Language level to &#8220;SDK default&#8221;</li>
</ul>



<p>After all, please sync the gradle project and wait for the process to finish.</p>
</blockquote>



<h2 class="wp-block-heading" id="h-expose-endpoint-returning-html">Expose Endpoint Returning HTML</h2>



<p>With all of that done, let&#8217;s delete the unwanted files that were generated automatically, like <code>Routing.kt</code>, or <code>Templating.kt</code> and expose the 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="">fun Application.module() {

    routing {
        get("/") {
            call.respondHtml {
                body {
                    + "Hello world!"
                }
            }
        }
    }

}
</pre>



<p>As we can see, whenever we reach the root path (by default, the <code>localhost:8080</code>), we should see the &#8220;Hello world!&#8221; text.</p>



<h2 class="wp-block-heading" id="h-the-problem">The Problem</h2>



<p>Now, let&#8217;s change the text, for example, to contain only a &#8220;Hello!&#8221; message. </p>



<p>What happens if we refresh the page? <strong>Nothing!</strong> The page does not reflect our code change until we restart the server manually! </p>



<p>And, especially when dealing with HTML, it may become a pretty tedious task.</p>



<h2 class="wp-block-heading" id="h-solution-ktor-auto-reload">Solution- Ktor auto-reload</h2>



<p>It won&#8217;t be a big surprise if I say that the <strong>Ktor auto-reload</strong> feature may be a great solution here? 😉 </p>



<p>But what is that?</p>



<p>Well, long story short, it its a feature that we can turn on by <strong>enabling the development mode</strong> in Ktor. And when enabled, it <strong>reloads application classes on code changes</strong>. </p>



<p>To be more specific, according to the documentation, Ktor listens for the changes to the following files:</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="">/build/classes/kotlin/main/META-INF
/build/classes/kotlin/main/com/your-package-namn
/build/classes/kotlin/main/com
/build/classes/kotlin/main
/build/resources/main</pre>



<p>And as we can see, by the <strong>code changes</strong>, we mean the <strong>generated outputs and artifacts.</strong> But the good news is that with gradle, we can easily automate the generation, too.</p>



<p>Lastly, I just wanted to mention that application logs are also pretty clear about what we need to do in order to enable auto-reload: </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="">2025-03-25 06:40:49.929 [main] INFO  Application - Autoreload is disabled because the development mode is off.
2025-03-25 06:40:50.072 [main] INFO  Application - Application started in 0.374 seconds.
2025-03-25 06:40:50.363 [DefaultDispatcher-worker-2] INFO  Application - Responding at http://127.0.0.1:8080</pre>



<p></p>



<h2 class="wp-block-heading" id="h-enable-development-mode">Enable development mode</h2>



<p>With all of that said, let&#8217;s get back to the practice part. </p>



<p>As the first step, we must enable the development mode. And we can do that in a few ways, so let me walk you through them.</p>



<h2 class="wp-block-heading" id="h-hardcoded-approach">Hardcoded Approach</h2>



<p>When we navigate to the <code>build.gradle.kts</code> file, we should see the following: </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="">application {
    mainClass = "io.ktor.server.netty.EngineMain"

    val isDevelopment: Boolean = project.ext.has("development")
    applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment")
}</pre>



<p>So the easy and the dummy approach would be to simply hardcode the value- <code>-Dio.ktor.development=true</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="">application {
    mainClass = "io.ktor.server.netty.EngineMain"
    applicationDefaultJvmArgs = listOf("-Dio.ktor.development=true")
}
</pre>



<p>Now, when we run the app with <code>./gradlew run</code>, we will see that the message disappeared, proving the development mode is enabled:</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="">2025-03-25 07:10:15.883 [main] INFO  Application - Application started in 0.41 seconds.
2025-03-25 07:10:16.142 [main] INFO  Application - Responding at http://127.0.0.1:8080
&lt;==========---> 83% EXECUTING [1m 13s]
</pre>



<p>But first, this approach does not work if you run the server in IntelliJ by using the green icon because, by default, it does not invoke any gradle command.</p>



<p>Secondly, it is hardcoded, meaning we don&#8217;t have too much control over it in various environments. </p>



<h2 class="wp-block-heading" id="h-program-argument">Program Argument</h2>



<p>Another option is to bring back what we already had in the <code>build.gradle.kts</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="">val isDevelopment: Boolean = project.ext.has("development")
applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment")</pre>



<p>And from now on, the only thing we need is the <code>-Pdevelopment</code> flag for the <code>gradlew run</code> command:</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="">./gradlew run -Pdevelopment</pre>



<p>It&#8217;s clearly better and gives us more flexibility. </p>



<h2 class="wp-block-heading" id="h-vm-options">VM options</h2>



<p>Another option that we have on the table is to directly use the VM option we saw above- <code>-Dio.ktor.development=true</code>. </p>



<p>For that purpose, we can even completely get rid of the previous snippet from <code>application</code> in <code>build.gradle.kts</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="">application {
    mainClass = "io.ktor.server.netty.EngineMain"
}</pre>



<p>And now, whenever invoking the <code>gradlew run</code> command, we will simply put the whole arg: </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="">./gradlew "-Dio.ktor.development=true" run</pre>



<p><strong>Moreover</strong>, when working with IntelliJ, we can edit our configuration to pass the same option: </p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="693" src="http://blog.codersee.com/wp-content/uploads/2025/03/image-1024x693.png" alt="" class="wp-image-18012814" srcset="https://blog.codersee.com/wp-content/uploads/2025/03/image-1024x693.png 1024w, https://blog.codersee.com/wp-content/uploads/2025/03/image-300x203.png 300w, https://blog.codersee.com/wp-content/uploads/2025/03/image-768x520.png 768w, https://blog.codersee.com/wp-content/uploads/2025/03/image.png 1158w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>So we can see that this option will work with <strong>both gradle, as well as the IntelliJ IDEA runs.</strong> </p>



<h2 class="wp-block-heading" id="h-config-file">Config File</h2>



<p>Lastly, we can also set that in 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="">ktor:
    development: true
    application:
        modules:
            - com.codersee.ApplicationKt.module
    deployment:
        port: 8080</pre>



<p>It works exactly the same, and with environment variables can give us a lot of flexibility, too. </p>



<h2 class="wp-block-heading" id="h-automate-build">Automate Build</h2>



<p>Alright, so at this point, our app is <strong>up and running</strong> with the <strong>development mode ON.</strong> </p>



<p>However, when we make any code change, <strong>nothing still happens!</strong> </p>



<p>Well, as I mentioned previously, the Ktor auto-reload feature looks for the output files. Meaning we must build the project to see the changes. </p>



<p>And we can do that by either running the <code>./gradlew build</code> or by navigating to the Build tab in IntelliJ: </p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="634" height="95" src="http://blog.codersee.com/wp-content/uploads/2025/03/image-1.png" alt="" class="wp-image-18012818" srcset="https://blog.codersee.com/wp-content/uploads/2025/03/image-1.png 634w, https://blog.codersee.com/wp-content/uploads/2025/03/image-1-300x45.png 300w" sizes="auto, (max-width: 634px) 100vw, 634px" /></figure>



<p>And although this works and we can now see changes without the restart, it is still not too convenient. </p>



<p>Well, the good news we can slightly adjust the gradlew build command and <strong>automate that:</strong></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="">./gradlew -t build -x test</pre>



<p>With the <code>-t</code> flag (or <code>--continuous</code>), we enable the <strong>continuous build mode.</strong> Gradle will watch for changes in the source files, and whenever a relevant change is detected, it will automatically rerun the build task. </p>



<p>Additionally, we exclude the <code>test</code> task using the <code>-x</code> flag to speed up the process a little 🙂 </p>



<p>If we would like to add that as a run configuration to IntelliJ, then it is really easy, too: </p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="319" src="http://blog.codersee.com/wp-content/uploads/2025/03/image-2-1024x319.png" alt="" class="wp-image-18012819" srcset="https://blog.codersee.com/wp-content/uploads/2025/03/image-2-1024x319.png 1024w, https://blog.codersee.com/wp-content/uploads/2025/03/image-2-300x93.png 300w, https://blog.codersee.com/wp-content/uploads/2025/03/image-2-768x239.png 768w, https://blog.codersee.com/wp-content/uploads/2025/03/image-2.png 1146w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>And basically, that&#8217;s all! At this point, we can add the necessary HTML changes <strong>without restarting the Ktor server.</strong> </p>



<h2 class="wp-block-heading" id="h-narrowing-down-watched-files">Narrowing Down Watched Files</h2>



<p>As I mentioned above, Ktor listens for the file updates in the following directories: </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="">/build/classes/kotlin/main/META-INF
/build/classes/kotlin/main/com/your-package-namn
/build/classes/kotlin/main/com
/build/classes/kotlin/main
/build/resources/main</pre>



<p>If we would like to narrow down the list of watched directories, then we can do that, for example, in the YAML config 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="">ktor:
    deployment:
        watch:
            - resources</pre>



<p>The only thing we must specify is the part of the watched patch that we want to keep. </p>



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



<p>And that&#8217;s all for this quick tutorial on how to enable the <strong>auto-reload feature in Ktor.</strong></p>



<p>If you found this content useful, then check out my <a href="https://codersee.com/courses/ktor-server-pro/">Ktor Server Pro course</a>&#8211; the most comprehensive Ktor course on the market. </p>



<p>Thank you for being here, and see you around! 🙂 </p>
<p>The post <a href="https://blog.codersee.com/ktor-auto-reload/">Auto-reload in Ktor</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/ktor-auto-reload/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>MockK with Coroutines [5/5]</title>
		<link>https://blog.codersee.com/mockk-coroutines/</link>
					<comments>https://blog.codersee.com/mockk-coroutines/#respond</comments>
		
		<dc:creator><![CDATA[Piotr]]></dc:creator>
		<pubDate>Tue, 04 Mar 2025 17:55:48 +0000</pubDate>
				<category><![CDATA[Kotlin]]></category>
		<category><![CDATA[MockK]]></category>
		<category><![CDATA[Testing]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=17512712</guid>

					<description><![CDATA[<p>In the last article in a series, we are going to learn everything we need to know to work with MockK and Kotlin coroutines. </p>
<p>The post <a href="https://blog.codersee.com/mockk-coroutines/">MockK with Coroutines [5/5]</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>At this point, we spent a lot of time exploring MockK features and syntax, so at this point, adding coroutines will be trivial, trust me😉</p>



<p>Of course, you can find the rest of the series on my blog, too:</p>



<ul class="wp-block-list">
<li><a href="https://blog.codersee.com/getting-started-with-mockk-kotlin/">Getting Started with MockK in Kotlin [1/5]</a></li>



<li><a href="https://blog.codersee.com/verification-mockk/">Verification in MockK [2/5]</a></li>



<li><a href="https://blog.codersee.com/mockk-objects-top-level-extension-functions/">MockK: Objects, Top-Level, and Extension Functions [3/5]</a></li>



<li><a href="https://blog.codersee.com/mockk-spy-relaxed-mock-partial-mocking/">MockK: Spies, Relaxed Mocks, and Partial Mocking [4/5]</a></li>



<li>MockK with Coroutines [5/5]</li>
</ul>



<h2 class="wp-block-heading" id="h-video-content">Video Content</h2>



<p>As always, if you prefer a video content, then please check out my latest YouTube video:</p>


<figure class="wp-block-embed-youtube wp-block-embed is-type-video is-provider-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><a href="https://blog.codersee.com/mockk-coroutines/"><img decoding="async" src="https://blog.codersee.com/wp-content/plugins/wp-youtube-lyte/lyteCache.php?origThumbUrl=%2F%2Fi.ytimg.com%2Fvi%2FW0nnZl4R6C0%2Fhqdefault.jpg" alt="YouTube Video"></a><br /><br /><figcaption></figcaption></figure>


<h2 class="wp-block-heading" id="h-additional-imports">Additional Imports</h2>



<p>As the first step, let&#8217;s add the necessary import:</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.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.1")</pre>



<p>This package provides utilities for testing coroutines.</p>



<p>And thanks to that, we can mock and test suspend functions.</p>



<h2 class="wp-block-heading">Code To Test</h2>



<p>Following, let&#8217;s introduce the suspended 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="">class Eight(
    private val one: EightOne,
    private val two: EightTwo,
) {
    suspend fun funToTest(): String {
        return "${one.returnInt()} ${two.returnString()}"
    }
}

class EightOne {
    suspend fun returnInt(): Int = 10
}

class EightTwo {
    suspend fun returnString(): String = "Some String"
}</pre>



<p>And I know it does not make sense. But it is not important here😉</p>



<p>The important part is that we have suspended functions so we can start implementing tests.</p>



<h2 class="wp-block-heading">MockK with Coroutines</h2>



<p>As the next step, let&#8217;s try to implement the test &#8220;the old way&#8221;:</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 EightTest {
    private val one: EightOne = mockk()
    private val two: EightTwo = mockk()

    private val eight = Eight(one, two)

    @Test
    fun `should return 1 codersee`() {
        every { one.returnInt() } returns 1
        every { two.returnString() } returns "codersee"

        val result = eight.funToTest()

        assertEquals("1 codersee", result)

        verifySequence {
            one.returnInt()
            two.returnString()
        }
    }
}</pre>



<p>As a result, we can see the compiler complaining about our suspended functions:</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="">Suspend function 'returnInt' should be called only from a coroutine or another suspend function
Suspend function 'returnString' should be called only from a coroutine or another suspend function
Suspend function 'funToTest' should be called only from a coroutine or another suspend function</pre>



<p>So, as the first step, let&#8217;s add the <code>runTest</code> to get rid of the last error:</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 `should return 1 codersee`() = runTest {</pre>



<p>The <code>runTest</code> is not related to the MockK itself. It comes from <code>kotlinx.coroutines</code> and executes our test body in a new coroutine, returning <code>TestResult</code> .</p>



<p>When it comes to MockK, then we can work with coroutines and suspended functions by simply adding the &#8220;co&#8221; suffix for all 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="">class EightTest {
    private val one: EightOne = mockk()
    private val two: EightTwo = mockk()

    private val eight = Eight(one, two)

    @Test
    fun `should return 1 codersee`() = runTest {
        coEvery { one.returnInt() } returns 1
        coEvery { two.returnString() } returns "codersee"

        val result = eight.funToTest()

        assertEquals("1 codersee", result)

        coVerifySequence {
            one.returnInt()
            two.returnString()
        }
    }
}</pre>



<p>And yes, this is that easy!😉</p>



<p>MockK comes with plenty of functions to work with suspend functions, like:</p>



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



<li><code>coEvery</code></li>



<li><code>coJustRun</code></li>



<li><code>coJustAwait</code></li>



<li><code>coAnswers</code></li>



<li>and many more😉</li>
</ul>



<h2 class="wp-block-heading">Issue With Spies</h2>



<p>Lastly, at the moment of writing, there is one issue with spies and coroutines in MockK. You can see all the details here: <a href="https://github.com/mockk/mockk/issues/554">https://github.com/mockk/mockk/issues/554</a></p>



<p>Assuming this is quite an old one, I wouldn&#8217;t expect it to be fixed in the near future.</p>



<p>But if that is the case, then please let me know in the comments below.</p>



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



<p>That&#8217;s all for this series about MockK &#8211; the mocking library for Kotlin.</p>



<p>During our time together, we learned A LOT, and I believe you are ready to use it in your projects!</p>



<p>Let me know your thoughts in the comments below, and if you are looking for the other articles, then here you are:</p>



<ul class="wp-block-list">
<li><a href="https://blog.codersee.com/getting-started-with-mockk-kotlin/">Getting Started with MockK in Kotlin [1/5]</a></li>



<li><a href="https://blog.codersee.com/verification-mockk/">Verification in MockK [2/5]</a></li>



<li><a href="https://blog.codersee.com/mockk-objects-top-level-extension-functions/">MockK: Objects, Top-Level, and Extension Functions [3/5]</a></li>



<li><a href="https://blog.codersee.com/mockk-spy-relaxed-mock-partial-mocking/">MockK: Spies, Relaxed Mocks, and Partial Mocking [4/5]</a></li>



<li>MockK with Coroutines [5/5]</li>
</ul>
<p>The post <a href="https://blog.codersee.com/mockk-coroutines/">MockK with Coroutines [5/5]</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/mockk-coroutines/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>MockK: Spy, Relaxed Mock, and Partial Mocking [4/5]</title>
		<link>https://blog.codersee.com/mockk-spy-relaxed-mock-partial-mocking/</link>
					<comments>https://blog.codersee.com/mockk-spy-relaxed-mock-partial-mocking/#respond</comments>
		
		<dc:creator><![CDATA[Piotr]]></dc:creator>
		<pubDate>Tue, 04 Mar 2025 17:55:42 +0000</pubDate>
				<category><![CDATA[Kotlin]]></category>
		<category><![CDATA[MockK]]></category>
		<category><![CDATA[Testing]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=17512701</guid>

					<description><![CDATA[<p>In the fourth artcile in a series, we are going to learn what are MockK spies, relaxed mocks and how to do a partial mocking.</p>
<p>The post <a href="https://blog.codersee.com/mockk-spy-relaxed-mock-partial-mocking/">MockK: Spy, Relaxed Mock, and Partial Mocking [4/5]</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>At this point, we know quite a lot about mocks, verification, and how to perform stubbings. In this lesson, we are going to expand our horizons and see additional features useful in day-to-day scenarios, like relaxed mocks.</p>



<p>Of course, you can find the rest of the series on my blog, too:</p>



<ul class="wp-block-list">
<li><a href="https://blog.codersee.com/getting-started-with-mockk-kotlin/">Getting Started with MockK in Kotlin [1/5]</a></li>



<li><a href="https://blog.codersee.com/verification-mockk/">Verification in MockK [2/5]</a></li>



<li><a href="MockK: Objects, Top-Level, and Extension Functions [3/5]">MockK: Objects, Top-Level, and Extension Functions [3/5]</a></li>



<li>MockK: Spies, Relaxed Mocks, and Partial Mocking [4/5]</li>



<li><a href="https://blog.codersee.com/mockk-coroutines/">MockK with Coroutines [5/5]</a></li>
</ul>



<h2 class="wp-block-heading" id="h-video-content">Video Content</h2>



<p>As always, if you prefer a video content, then please check out my latest YouTube video:</p>


<figure class="wp-block-embed-youtube wp-block-embed is-type-video is-provider-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><a href="https://blog.codersee.com/mockk-spy-relaxed-mock-partial-mocking/"><img decoding="async" src="https://blog.codersee.com/wp-content/plugins/wp-youtube-lyte/lyteCache.php?origThumbUrl=%2F%2Fi.ytimg.com%2Fvi%2FyukKpPr_iBg%2Fhqdefault.jpg" alt="YouTube Video"></a><br /><br /><figcaption></figcaption></figure>


<h2 class="wp-block-heading" id="h-mockk-relaxed-mock">MockK: Relaxed Mock</h2>



<p>What does a <strong>relaxed mock</strong> mean?</p>



<p>Well, long story short, it is <strong>a mock that returns some value for all functions</strong>. In other words, the value is returned even if we do not provide a stubbing.</p>



<p>To better understand, let&#8217;s take a look at the example:</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 Five(
    private val one: FiveOne,
) {
    fun funToTest(): String {
        one.returnInt()
        return one.returnString()
    }
}

class FiveOne {
    fun returnInt(): Int = 10
    fun returnString(): String = "Some String"
}</pre>



<p>As we can see, the function we want to test invokes two another: <code>returnInt</code> and <code>returnString</code>.</p>



<p>And I am pretty sure that at this point, you know the result without even running the test:</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 FiveTest {

    private val one: FiveOne = mockk()
    private val five = Five(one)

    @Test
    fun `should return mocked string`() {
        every { one.returnString() } returns "mocked"

        val result = five.funToTest()

        assertEquals("mocked", result)
    }
}</pre>



<p>That&#8217;s right, it fails, and MockK complains about missing answer:</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="">no answer found for FiveOne(#1).returnInt() among the configured answers: (FiveOne(#1).returnString()))
io.mockk.MockKException: no answer found for FiveOne(#1).returnInt() among the configured answers: (FiveOne(#1).returnString()))</pre>



<p>And as you may have guessed, a relaxed mock may help us here a bit. But how do we define it in MockK?</p>



<p>It&#8217;s easy; the only thing we need is to set up the <code>relaxed</code> flag on creation:</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 one: FiveOne = mockk(relaxed = true)</pre>



<p>(When working with annotations, we can use <code>@RelaxedMockK</code> instead of <code>@MockK</code>)</p>



<p>So, now, when we repeat our test, <strong>it passes!</strong></p>



<p>Moreover, we could even completely skip the stubbing part:</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 `should return empty string`() {
    val result = five.funToTest()

    assertEquals("", result)
}</pre>



<p>But please don&#8217;t do that in your tests and treat them as a curiosity😉</p>



<p>And before we head to the next part, I just wanted to mention that <strong>for Unit-returning functions, we must set a separate flag- </strong><code><strong>relaxUnitFun</strong></code><strong> &#8211; to true:</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="">private val one: FiveOne = mockk(relaxed = true, relaxUnitFun = true)</pre>



<h2 class="wp-block-heading">Partial Mocking</h2>



<p>Nextly, let&#8217;s focus on the <strong>partial mocking.</strong></p>



<p>This technique, on the other hand, allows us to <strong>invoke the actual function</strong>.</p>



<p>Let&#8217;s take a look at the example of a new class to test:</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 Six(
    private val one: SixOne,
) {
    fun funToTest(): String {
        one.returnInt()
        return one.returnString()
    }
}

class SixOne {
    fun returnInt(): Int = 10
    fun returnString(): String = "Some String"
}</pre>



<p>As we can see, apart from the names, it works exactly the same as previously.</p>



<p>So, let&#8217;s take a look at the test:</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 SixTest {

    private val one: SixOne = mockk()
    private val five = Six(one)

    @Test
    fun `should invoke actual functions`() {
        every { one.returnInt() } answers { callOriginal() }
        every { one.returnString() } answers { callOriginal() }

        val result = five.funToTest()

        assertEquals("Some String", result)
    }
}</pre>



<p>As we can see, in MockK, we can use the <code>answers { callOriginal() }</code>&nbsp; to invoke the actual function. And that&#8217;s why we get <em>&#8220;Some String&#8221;</em> value here.</p>



<h2 class="wp-block-heading">MockK Spy</h2>



<p>As the last step, let&#8217;s take a look at the <strong>spies</strong>. What are they?</p>



<p>Well, they are a mix of real objects with mocks. Whenever we do not specify any stubbing, the actual function is invoked.</p>



<p>So, the main difference between spies and partial mocking is that for a spy, the original function is invoked if we don&#8217;t write anything. In partial mocking, we must be explicit.</p>



<p>Let&#8217;s take a look at the code to test then. Again, this is a 1:1 copy of previous examples:</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 Seven(
    private val one: SevenOne,
) {
    fun funToTest(): String {
        one.returnInt()
        return one.returnString()
    }
}

class SevenOne {
    fun returnInt(): Int = 10
    fun returnString(): String = "Some String"
}</pre>



<p>Following, let&#8217;s implement a simple test with a MockK spy then:</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 SevenTest {

    private val one: SevenOne = spyk(SevenOne())

    private val five = Seven(one)

    @Test
    fun `should invoke actual functions`() {
        val result = five.funToTest()

        assertEquals("Some String", result)
    }
}</pre>



<p>And we can see the interesting difference here- we pass the instance of the dependent object to the <code>spyk</code> . And although underneath a copy of that object will be used rather than the passed instance, this shows that <strong>spies are actual objects with mocking capabilities.</strong></p>



<p>Additionally, we can use a similar notation to <code>mockk</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 val one: SevenOne = spyk()
// or 
private val one = spyk&lt;SevenOne>()</pre>



<p>In this case, the default constructor will be invoked.</p>



<p>And if you are wondering when to use spies, and when to choose partial mocks, then I would say that partial mocks will be better whenever actual calls are rare, almost exceptional. If since the very beginning we want to mix actual resposnes with stubs, then spies will be more optiomal option.</p>



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



<p>That&#8217;s all for the fourth article in a series in which we learned how to deal with MockK spies, relaxed mocks, and partial mocking.</p>



<p>Awesome! And without any further ado, let&#8217;s head to the next lesson:</p>



<ul class="wp-block-list">
<li><a href="https://blog.codersee.com/getting-started-with-mockk-kotlin/">Getting Started with MockK in Kotlin [1/5]</a></li>



<li><a href="https://blog.codersee.com/verification-mockk/">Verification in MockK [2/5]</a></li>



<li><a href="MockK: Objects, Top-Level, and Extension Functions [3/5]">MockK: Objects, Top-Level, and Extension Functions [3/5]</a></li>



<li>MockK: Spies, Relaxed Mocks, and Partial Mocking [4/5]</li>



<li><a href="https://blog.codersee.com/mockk-coroutines/">MockK with Coroutines [5/5]</a></li>
</ul>



<p></p>
<p>The post <a href="https://blog.codersee.com/mockk-spy-relaxed-mock-partial-mocking/">MockK: Spy, Relaxed Mock, and Partial Mocking [4/5]</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/mockk-spy-relaxed-mock-partial-mocking/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>MockK: Objects, Top-Level, and Extension Functions [3/5]</title>
		<link>https://blog.codersee.com/mockk-objects-top-level-extension-functions/</link>
					<comments>https://blog.codersee.com/mockk-objects-top-level-extension-functions/#respond</comments>
		
		<dc:creator><![CDATA[Piotr]]></dc:creator>
		<pubDate>Tue, 04 Mar 2025 17:55:29 +0000</pubDate>
				<category><![CDATA[Kotlin]]></category>
		<category><![CDATA[MockK]]></category>
		<category><![CDATA[Testing]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=17512691</guid>

					<description><![CDATA[<p>In the third artcile in a series, we are going to focus on Kotlin objects, top-level and extension functions in MockK. </p>
<p>The post <a href="https://blog.codersee.com/mockk-objects-top-level-extension-functions/">MockK: Objects, Top-Level, and Extension Functions [3/5]</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>This tutorial is a great example of why MockK is an excellent choice for Kotlin and how easy we can work with Kotlin features, such as the previously mentioned objects.</p>



<p>Of course, you can find the rest of the series on my blog, too:</p>



<ul class="wp-block-list">
<li><a href="https://blog.codersee.com/getting-started-with-mockk-kotlin/">Getting Started with MockK in Kotlin [1/5]</a></li>



<li><a href="https://blog.codersee.com/verification-mockk/">Verification in MockK [2/5]</a></li>



<li>MockK: Objects, Top-Level, and Extension Functions [3/5]</li>



<li><a href="https://blog.codersee.com/mockk-spy-relaxed-mock-partial-mocking/">MockK: Spies, Relaxed Mocks, and Partial Mocking [4/5]</a></li>



<li><a href="https://blog.codersee.com/mockk-coroutines/">MockK with Coroutines [5/5]</a></li>
</ul>



<h2 class="wp-block-heading" id="h-video-content">Video Content</h2>



<p>As always, if you prefer a video content, then please check out my latest YouTube video:</p>


<figure class="wp-block-embed-youtube wp-block-embed is-type-video is-provider-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><a href="https://blog.codersee.com/mockk-objects-top-level-extension-functions/"><img decoding="async" src="https://blog.codersee.com/wp-content/plugins/wp-youtube-lyte/lyteCache.php?origThumbUrl=%2F%2Fi.ytimg.com%2Fvi%2FaesDHmuny_0%2Fhqdefault.jpg" alt="YouTube Video"></a><br /><br /><figcaption></figcaption></figure>


<h2 class="wp-block-heading" id="h-kotlin-objects-and-mockk">Kotlin Objects and MockK</h2>



<p>Let&#8217;s start everything by explaining <strong>how to mock Kotlin objects in MockK</strong>.</p>



<p>And as you know, objects are pretty specific, so we are going to see two, different examples and approaches.</p>



<h3 class="wp-block-heading" id="h-mockkobject"><code>mockkObject</code></h3>



<p>Let&#8217;s introduce a simple example with an object:</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 One {
    fun funToTest(): UUID = SomeObject.generateId()
}

object SomeObject {
    fun generateId(): UUID = UUID.randomUUID()
}</pre>



<p>It is not rocket science, right? We are going to simply test the function that should return the UUID generated by <code>SomeObject</code> function.</p>



<p>So, if we would like to mock that UUID to gain more control over the behavior, then we can use the <code>mockkObject</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 OneTest {

    private val one = One()

    @Test
    fun `should return mocked ID`() {
        val id = UUID.fromString("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa")

        mockkObject(SomeObject)
        every { SomeObject.generateId() } returns id

        val result = one.funToTest()

        assertEquals(id, result)
    }
}</pre>



<p>The above test succeeds, and we can see that we define stubbing just like with every mock.</p>



<p>Moreover, if we add <code>mockkObject</code> inside the function, it will not affect the scope of other tests:</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 OneTest {

    private val one = One()

    @Test
    fun `should return mocked ID`() {
        val id = UUID.fromString("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa")

        mockkObject(SomeObject)
        every { SomeObject.generateId() } returns id

        val result = one.funToTest()

        assertEquals(id, result)
    }

    @Test
    fun `should fail returning mocked ID`() {
        val id = UUID.fromString("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa")

        val result = one.funToTest()

        assertEquals(id, result)
    }
}</pre>



<p>This time, the second test fails because <code>SomeObject</code> returns a <strong>random value.</strong></p>



<p>Additionally, if we use the <code>mockkObject</code> , but we don&#8217;t provide stubbing, <strong>MockK will not throw any exception:</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="">@Test
fun `should fail returning mocked ID`() {
    val id = UUID.fromString("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa")

    mockkObject(SomeObject)
    val result = one.funToTest()

    assertEquals(id, result)
}</pre>



<p>The above test runs just like <code>mockkObject(SomeObject)</code> does not exist. As a result, we get a random UUID.</p>



<p>So, we must be cautious about that.</p>



<h3 class="wp-block-heading"><code>unmockkObject</code></h3>



<p>Although I highly doubt we should ever use that, MockK allows us to &#8220;unmock&#8221; the object with <code>unmockkObject</code> function.</p>



<p>How does it work?</p>



<p>Let&#8217;s analyze the below snippet:</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 `should illustrate unmockkObject`() {
    val id = UUID.fromString("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa")

    mockkObject(SomeObject)
    every { SomeObject.generateId() } returns id

    assertEquals(id, one.funToTest())

    unmockkObject(SomeObject)
    assertEquals(id, one.funToTest())
}</pre>



<p>In this example, the first assertion succeeds. However, the second <code>assertEquals</code> fails due to <code>unmockkObject</code> that reverts our mock.</p>



<p>So, again, during the second call, a random UUID is generated.</p>



<h3 class="wp-block-heading">Create Mock Object Instance</h3>



<p>I know, this title sounds simply weird.</p>



<p>Nevertheless, let&#8217;s take a look at another class- <code>Two</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 Two(
    private val someObject: SomeObject,
) {
    fun funToTest(): UUID = someObject.generateId()
}</pre>



<p>This time, we inject our <code>SomeObject</code> through the constructor.</p>



<p>And for consistency, in MockK, we can mock that just like a simple 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 TwoTest {

    private val someObject = mockk&lt;SomeObject>()
    private val two = Two(someObject)

    @Test
    fun `should return mocked ID`() {
        val id = UUID.fromString("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa")

        every { someObject.generateId() } returns id

        val result = two.funToTest()

        assertEquals(id, result)
    }
}</pre>



<p>But, <strong>we must remember about two, important things in this case.</strong></p>



<p>Firstly, we <strong>must provide a stubbing</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="">@Test
fun `should fail due to missing stubbing`() {
    val id = UUID.fromString("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa")

    val result = two.funToTest()

    assertEquals(id, result)
}</pre>



<p>So, just like with classes, the above test fails due to missing stubbing!</p>



<p>Secondly, when defining stubbing, we <strong>must use the instance</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="">@Test
fun `should fail due to incorrect stubbing`() {
    val id = UUID.fromString("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa")

    every { SomeObject.generateId() } returns id

    val result = two.funToTest()

    assertEquals(id, result)
}</pre>



<p>As we can see, we cannot refer to the <code>SomeObject</code> anymore.</p>



<p>So, as always, the choice is up to you. 🙂</p>



<h2 class="wp-block-heading">MockK and Top-Level Functions</h2>



<p>With all of that said about objects, let&#8217;s take a look at how we can deal with another Kotlin feature, <strong>top-level functions</strong>, in MockK.</p>



<p>As the first step, let&#8217;s take a look at the code to test:</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 Three {
    fun funToTest(): UUID = generateId()
}

fun generateId(): UUID = UUID.randomUUID()</pre>



<p>This time, instead of the object and its function, we&#8217;re dealing with top-level <code>generateId</code> .</p>



<p>And from the MockK perspective, everything looks pretty much the same as with 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="">class ThreeTest {

    private val three = Three()

    @Test
    fun `should return mocked ID`() {
        val id = UUID.fromString("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa")

        mockkStatic(::generateId)
        every { generateId() } returns id

        val result = three.funToTest()

        assertEquals(id, result)
    }
}</pre>



<p>As we can see, we use the <code>mockkStatic</code> and we simply provide our stubbing.</p>



<p>And just like in the first approach in objects, nothing happens if we define the <code>mockkStatic</code> , but we don&#8217;t set the stubbing. The random UUID is generated:</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 `should fail due to missing stubbing`() {
    val id = UUID.fromString("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa")

    mockkStatic(::generateId)

    val result = three.funToTest()

    assertEquals(id, result)
}</pre>



<p>Lastly, I just wanted to mention that we have a similar function- <code>clearStaticMockk</code>&#8211; on the table, too😉</p>



<h2 class="wp-block-heading">Extension Functions Support</h2>



<p>As the last thing in this article, let&#8217;s take a look at the example leveraging the extension function:</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 Four {
    fun funToTest(): String = "abc".withCustomCase()
}

fun String.withCustomCase(): String = this.uppercase()</pre>



<p>It does not make too much sense, but we can clearly see that our extension function should return an uppercase String value.</p>



<p>And to mock this case in MockK, we can use <code>mockkStatic</code> once again:</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 FourTest {

    private val four = Four()

    @Test
    fun `should return codersee`() {
        mockkStatic(String::withCustomCase)
        every { "abc".withCustomCase() } returns "codersee"
        every { "xyz".withCustomCase() } returns "not-codersee"

        val result = four.funToTest()

        assertEquals("codersee", result)
    }
}</pre>



<p>The really interesting part in the above snippet is that the String value <strong>must match!</strong></p>



<p>Additionally, MockK allows us to mock extension functions defined in a class.</p>



<p>Nevertheless, in my opinion this case is so rare, that I will skip it. And if you really need to check it, then take a look at the documentation <a href="https://mockk.io/#extension-functions" target="_blank" rel="noreferrer noopener">here</a>.</p>



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



<p>That&#8217;s all for the third article, in which we learned how to deal with Kotlin objects, top-level and extension functions in MockK.</p>



<p>Great job, and without any further ado, let&#8217;s head to the next lesson:</p>



<ul class="wp-block-list">
<li><a href="https://blog.codersee.com/getting-started-with-mockk-kotlin/">Getting Started with MockK in Kotlin [1/5]</a></li>



<li><a href="https://blog.codersee.com/verification-mockk/">Verification in MockK [2/5]</a></li>



<li>MockK: Objects, Top-Level, and Extension Functions [3/5]</li>



<li><a href="https://blog.codersee.com/mockk-spy-relaxed-mock-partial-mocking/">MockK: Spies, Relaxed Mocks, and Partial Mocking [4/5]</a></li>



<li><a href="https://blog.codersee.com/mockk-coroutines/">MockK with Coroutines [5/5]</a></li>
</ul>
<p>The post <a href="https://blog.codersee.com/mockk-objects-top-level-extension-functions/">MockK: Objects, Top-Level, and Extension Functions [3/5]</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/mockk-objects-top-level-extension-functions/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Verification in MockK [2/5]</title>
		<link>https://blog.codersee.com/verification-mockk/</link>
					<comments>https://blog.codersee.com/verification-mockk/#respond</comments>
		
		<dc:creator><![CDATA[Piotr]]></dc:creator>
		<pubDate>Tue, 04 Mar 2025 17:55:20 +0000</pubDate>
				<category><![CDATA[Kotlin]]></category>
		<category><![CDATA[MockK]]></category>
		<category><![CDATA[Testing]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=17512671</guid>

					<description><![CDATA[<p>In the second article in a MockK Kotlin series, we are going to learn more about various verification techniques.</p>
<p>The post <a href="https://blog.codersee.com/verification-mockk/">Verification in MockK [2/5]</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>To be more specific, we are going to work with both <em>verification</em> and <em>capturing</em>&#8211; MockK Kotlin features, allowing us to perform assertions against our mocks.</p>



<p>To view other articles in this series, please take a look at:</p>



<ul class="wp-block-list">
<li><a href="https://blog.codersee.com/getting-started-with-mockk-kotlin/">Getting Started with MockK in Kotlin [1/5]</a></li>



<li>Verification in MockK [2/5]</li>



<li><a href="https://blog.codersee.com/mockk-objects-top-level-extension-functions/">MockK: Objects, Top-Level and Extension Functions [3/5]</a></li>



<li><a href="https://blog.codersee.com/mockk-spy-relaxed-mock-partial-mocking/">MockK: Spies, Relaxed Mocks, and Partial Mocking [4/5]</a></li>



<li><a href="https://blog.codersee.com/mockk-coroutines/">MockK with Coroutines [5/5]</a></li>
</ul>



<h2 class="wp-block-heading" id="h-video-content">Video Content</h2>



<p>As always, if you prefer a video content, then please check out my latest YouTube video:</p>


<figure class="wp-block-embed-youtube wp-block-embed is-type-video is-provider-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><a href="https://blog.codersee.com/verification-mockk/"><img decoding="async" src="https://blog.codersee.com/wp-content/plugins/wp-youtube-lyte/lyteCache.php?origThumbUrl=%2F%2Fi.ytimg.com%2Fvi%2FTVponpWSKGE%2Fhqdefault.jpg" alt="YouTube Video"></a><br /><br /><figcaption></figcaption></figure>


<h2 class="wp-block-heading" id="h-why-do-we-need-verification">Why Do We Need Verification?</h2>



<p>At this point, you may wonder why we need to do anything else besides what we did previously. We configured mocks, and they worked; we made the necessary assertions. </p>



<p>Then what is the point of anything else?</p>



<p>Well, our logic is pretty clear and <strong>predictable</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="">fun createUser(email: String, password: String): UUID {
    userRepository.findUserByEmail(email)
        ?.let { throw IllegalArgumentException("User with email $email already exists") }

    return userRepository.saveUser(email, password)
        .also { userId ->
            emailService.sendEmail(
                to = email,
                subject = "Welcome to Codersee!",
                body = "Welcome user: $userId."
            )
        }
}</pre>



<p>And by predictable, I mean that we see that every function will be invoked at most 1 time. That no other functions are invoked when we throw the exception. And I do not see any possibility that <code>saveUser</code> somehow would trigger before <code>findUserByEmail</code>.</p>



<p>But sometimes, that&#8217;s not the case.</p>



<p>Sometimes mocks may be invoked a variable number of times depending on some response, right? For example, when we call some user endpoint, we get a list of users, and then we fetch something for each user. </p>



<p>Another time, the order may be affected by the algorithm. And yet another time, it may be simply a spaghetti you inherited from someone else😉</p>



<p>And in a moment, we will learn what exactly we can do in our tests.</p>



<h2 class="wp-block-heading">Verification with <code>eq</code></h2>



<p>Before we head to the actual functions, let me share a first MockK verification tip: <strong>prefer <code>eq</code> matchers wherever possible.</strong></p>



<p>To better visualize, let&#8217;s recap the test example once again:</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 `should throw IllegalArgumentException when user with given e-mail already exists`() {
    val email = "contact@codersee.com"
    val password = "pwd"

    val foundUser = User(UUID.randomUUID(), email, password)
    every { userRepository.findUserByEmail(email) } returns foundUser

    assertThrows&lt;IllegalArgumentException> {
        service.createUser(email, password)
    }
}</pre>



<p>We clearly see that <code>foundUser</code> will be returned only if the <code>findUserByEmail</code> is invoked with <code>contact@codersee.com</code></p>



<p>And this is already a kind of verification. Because if we would use <code>any()</code> , or <code>any&lt;String&gt;()</code> , the test would still pass. However,<strong> it would pass even if our logic sent a password there by mistake!</strong></p>



<p>So I guess you see my point here. For such cases, I would go for <code>eq</code> matcher.</p>



<h2 class="wp-block-heading">Various MockK Verifications</h2>



<p>And with all of that said, we can finally take a look at various, actual verifications in MockK.</p>



<p>For that purpose, let&#8217;s introduce a more dummy example that will help us to focus on the topic without unwanted 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 A (
    private val b: B,
    private val c: C
) {
    fun funToTest(): Int {
        return 5
    }
}

class B {
    fun funFromB(): Int = 10
}

class C {
    fun funFromC(): String = "Hi!"
}</pre>



<h3 class="wp-block-heading"><code>verify</code></h3>



<p>Let&#8217;s start with the simplest one- <code>verify</code> .</p>



<p>And for that, let&#8217;s update the <code>funToTest</code> and write a simple test for it:</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 funToTest(): Int {
    repeat(10) { b.funFromB() }
    repeat(5) { c.funFromC() }
    return 5
}

@Test
fun `should return 5 without any verification`() {
    every { b.funFromB() } returns 1
    every { c.funFromC() } returns ""

    val result = a.funToTest()

    assertEquals(5, result)
}</pre>



<p>When we run the test, it succeeds. Moreover, we can clearly see that we can define stubbing once. Even though functions are invoked multiple times.</p>



<p>With the <code>verify</code> , we can make sure that they were invoked a specific number of times:</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 `should return 5 with verification and confirmation`() {
    every { b.funFromB() } returns 1
    every { c.funFromC() } returns ""

    val result = a.funToTest()

    assertEquals(5, result)

    verify(exactly = 10) { b.funFromB() }
    verify(atLeast = 5) { c.funFromC() }

    confirmVerified(b, c)
}</pre>



<p>We can use <code>exactly</code> , <code>atLeast</code> , <code>atMost</code> &#8211; the choice is up to you.</p>



<p>Additionally, if we would like to set some arbitraty <strong>timeout</strong>, then this is our go-to function.</p>



<h3 class="wp-block-heading"><code>confirmVerified</code></h3>



<p>Moreover, we should use <code>verify</code> in combination with <code>confirmVerified</code> .</p>



<p>This way, we additionally check if our test code verified all invoked functions.</p>



<p>If we get rid of <code>verify(atLeast = 5) { c.funFromC() }</code> and rerun the test, we will see:</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="">Verification acknowledgment failed

Verified call count: 0
Recorded call count: 5


Not verified calls:
1) C(#2).funFromC()
2) C(#2).funFromC()
3) C(#2).funFromC()
4) C(#2).funFromC()
5) C(#2).funFromC()</pre>



<p>So, as we can see, this is a great way to double-check our test assumptions (testing a test?🙂).</p>



<h3 class="wp-block-heading"><code>checkUnnecessaryStub</code></h3>



<p>Speaking of testing the test- we can additionally check if there are no unused stubbings.</p>



<p>For that purpose, let&#8217;s slightly update the example:</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 A(
    private val b: B,
    private val c: C
) {
    fun funToTest(): Int {
        repeat(10) { b.funFromB(7) }
        repeat(5) { c.funFromC() }
        return 5
    }
}

class B {
    fun funFromB(some: Int): Int = 10
}

class C {
    fun funFromC(): String = "Hi!"
}</pre>



<p>So this time, <code>funFromB</code> will always be invoked with <code>7</code> .</p>



<p>And if we make our test this way:</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 `should return 5 with verification and confirmation`() {
    every { b.funFromB(7) } returns 1
    every { b.funFromB(6) } returns 2 // unwanted
    every { c.funFromC() } returns ""

    val result = a.funToTest()

    assertEquals(5, result)

    verify(exactly = 10) { b.funFromB(7) }
    verify(atLeast = 5) { c.funFromC() }

    confirmVerified(b, c)
    checkUnnecessaryStub(b, c)
}</pre>



<p>Then MockK will be complaining a bit:</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="">1) B(#1).funFromB(eq(6)))
java.lang.AssertionError: Unnecessary stubbings detected.
Following stubbings are not used, either because there are unnecessary or because tested code doesn't call them :

1) B(#1).funFromB(eq(6)))</pre>



<h3 class="wp-block-heading"><code>verifyCount</code></h3>



<p>When it comes to counts, we can use the <code>verifyCount</code> function 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="">@Test
fun `should return 5 with verifyCount`() {
    every { b.funFromB(7) } returns 1
    every { c.funFromC() } returns ""

    val result = a.funToTest()

    assertEquals(5, result)

    verifyCount {
        10 * { b.funFromB(7) }
        (4..5) * { c.funFromC() }
    }
    confirmVerified(b, c)
}</pre>



<p>This allows us to achieve an even cleaner test with beautiful Kotlin DSL.</p>



<p>Nevertheless, we still have to invoke <code>confirmVerified</code> separately.</p>



<h3 class="wp-block-heading"><code>verifyAll/verifyOrder/verifySequence</code></h3>



<p>Sometimes, we can use <code>verifyAll</code> , <code>verifyOrder</code> , and <code>verifySequence</code> to make the code slightly cleaner.</p>



<p>The <code>verifyAll</code> checks if all calls happened, but it does not verify the order:</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 `should return 5 with verifyAll`() {
    every { b.funFromB(7) } returns 1
    every { c.funFromC() } returns ""

    val result = a.funToTest()

    assertEquals(5, result)

    verifyAll {
        c.funFromC()
        b.funFromB(7)
    }
}</pre>



<p>So, the above function will work like a charm.</p>



<p>On the other hand, if we use the <code>verifyOrder</code> , it won&#8217;t work anymore, and we will have to provide a valid order:</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 `should return 5 with verifyOrder`() {
    every { b.funFromB(7) } returns 1
    every { c.funFromC() } returns ""

    val result = a.funToTest()

    assertEquals(5, result)

    verifyOrder {
        b.funFromB(7)
        c.funFromC()
    }
}</pre>



<p>Lastly, the <code>verifySequence</code> will work similar to <code>verifyOrder</code> , but it will force us to provide the right amount of times in the right order.</p>



<p>So, to make the test work, we would need to do a small tweak:</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 `should return 5 with verifySequence`() {
    every { b.funFromB(7) } returns 1
    every { c.funFromC() } returns ""

    val result = a.funToTest()

    assertEquals(5, result)

    verifySequence {
        repeat(10) { b.funFromB(7) }
        repeat(5) { c.funFromC() }
    }
}</pre>



<p>The important thing to mention is that if in our <code>verifyAll</code> or <code>verifySequence</code> block we covered all mocks, then we don&#8217;t need to add <code>confirmVerified</code> .</p>



<p>But, <strong>unfortunately</strong>, this will succeed:</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 `should fail due to missing verification`() {
    every { b.funFromB(7) } returns 1
    every { c.funFromC() } returns ""

    val result = a.funToTest()

    assertEquals(5, result)

    verifySequence {
        repeat(10) { b.funFromB(7) }
    }
}</pre>



<p>And we must guard ourselves with <code>confirmVerified(c)</code> .</p>



<p>However, if we do the <code>b</code> and <code>c</code> check inside. Then the <code>confirmedVerified</code> is not needed anymore.</p>



<h2 class="wp-block-heading">MockK capturing</h2>



<p>Lastly, I would like to show you one more interesting MockK feature useful in verification- the <strong>capturing</strong>.</p>



<p>Let&#8217;s imagine a new function inside the <code>B</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 B {
    fun anotherFunFromB(some: Int) {}
}</pre>



<p>Now, the <code>anotherFunToTest</code> invokes it using the random value:</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 anotherFunToTest(): Int {
    b.anotherFunFromB(Random.nextInt())
    return 3
}</pre>



<p>So, if we write a test this way:</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 `should return 3`() {
    justRun { b.anotherFunFromB(any()) }

    val result = a.anotherFunToTest()

    assertEquals(3, result)
}</pre>



<p>We clearly see, that we cannot access this value in any way, right? It is not returned from the function itself. We cannot control it with other mock, too.</p>



<p>Thankfully, we can introduce a <strong>slot </strong>and capture the value on the fly:</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 `should return 3`() {
    val randomSlot = slot&lt;Int>()

    justRun { b.anotherFunFromB(capture(randomSlot)) }

    val result = a.anotherFunToTest()

    println("Random value: ${randomSlot.captured}")
    assertEquals(3, result)
}</pre>



<p>This way, the stubbing works just like <code>any()</code> ,but additionally, we can access the random value with <code>captured</code> .</p>



<p>And although this may not be a clear verification, in some cases, it can be really helpful.</p>



<h3 class="wp-block-heading">Summary</h3>



<p>That&#8217;s all for this article about verification in MockK.</p>



<p>We covered various approaches, techniques, and at this point I am pretty sure you will be able to pick the right one for your test cases.</p>



<p>Without any further ado, let&#8217;s head to the next article in this series:</p>



<ul class="wp-block-list">
<li><a href="https://blog.codersee.com/getting-started-with-mockk-kotlin/">Getting Started with MockK in Kotlin [1/5]</a></li>



<li>Verification in MockK [2/5]</li>



<li><a href="https://blog.codersee.com/mockk-objects-top-level-extension-functions/">MockK: Objects, Top-Level, and Extension Functions [3/5]</a></li>



<li><a href="https://blog.codersee.com/mockk-spy-relaxed-mock-partial-mocking/">MockK: Spies, Relaxed Mocks, and Partial Mocking [4/5]</a></li>



<li><a href="https://blog.codersee.com/mockk-coroutines/">MockK with Coroutines [5/5]</a></li>
</ul>



<p></p>
<p>The post <a href="https://blog.codersee.com/verification-mockk/">Verification in MockK [2/5]</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/verification-mockk/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Getting Started with MockK in Kotlin [1/5]</title>
		<link>https://blog.codersee.com/getting-started-with-mockk-kotlin/</link>
					<comments>https://blog.codersee.com/getting-started-with-mockk-kotlin/#comments</comments>
		
		<dc:creator><![CDATA[Piotr]]></dc:creator>
		<pubDate>Tue, 04 Mar 2025 17:55:01 +0000</pubDate>
				<category><![CDATA[Kotlin]]></category>
		<category><![CDATA[MockK]]></category>
		<category><![CDATA[Testing]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=17512637</guid>

					<description><![CDATA[<p>In this, first article in a series dedicated to MockK and Kotlin, we will learn how to create mocks, define stubbings and utilize matchers.</p>
<p>The post <a href="https://blog.codersee.com/getting-started-with-mockk-kotlin/">Getting Started with MockK in Kotlin [1/5]</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Before we head there, we will take a quick look at what exactly mocking is, and the differences between several terms that often lead to confusion. Thanks to that, we will have a solid foundation before diving into the <strong>MockK and Kotlin </strong>topics.</p>



<ul class="wp-block-list">
<li>Getting Started with MockK in Kotlin [1/5]</li>



<li><a href="https://blog.codersee.com/verification-mockk/">Verification in MockK [2/5]</a></li>



<li><a href="https://blog.codersee.com/mockk-objects-top-level-extension-functions/">MockK: Objects, Top-Level, and Extension Functions [3/5]</a></li>



<li><a href="https://blog.codersee.com/mockk-spy-relaxed-mock-partial-mocking/">MockK: Spies, Relaxed Mocks, and Partial Mocking [4/5]</a></li>



<li><a href="https://blog.codersee.com/mockk-coroutines/">MockK with Coroutines [5/5]</a></li>
</ul>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>If you enjoy this content, then check out my <a href="https://codersee.com/courses/ktor-server-pro/">Ktor Server PRO</a> course- the most comprehensive Ktor guide in the market. You&#8217;re gonna love it! 🙂 </p>
</blockquote>



<h2 class="wp-block-heading" id="h-video-content">Video Content</h2>



<p>As always, if you prefer a video content, then please check out my latest YouTube video:</p>


<figure class="wp-block-embed-youtube wp-block-embed is-type-video is-provider-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><a href="https://blog.codersee.com/getting-started-with-mockk-kotlin/"><img decoding="async" src="https://blog.codersee.com/wp-content/plugins/wp-youtube-lyte/lyteCache.php?origThumbUrl=%2F%2Fi.ytimg.com%2Fvi%2F4prbIk2TuX4%2Fhqdefault.jpg" alt="YouTube Video"></a><br /><br /><figcaption></figcaption></figure>


<h2 class="wp-block-heading" id="h-why-do-we-need-mocking">Why Do We Need Mocking?</h2>



<p>Long story short, we need mocking to isolate the unit / the system under test.</p>



<p>Sounds confusing? No worries.</p>



<p>Let&#8217;s take a look at the typical example of a function:</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="576" src="http://blog.codersee.com/wp-content/uploads/2025/03/mocking_mockk_system_under_test_dependent_on_component-1024x576.png" alt="Image is an explainer for MockK and presents a visualization of system under test / unit and dependend on components" class="wp-image-17512639" srcset="https://blog.codersee.com/wp-content/uploads/2025/03/mocking_mockk_system_under_test_dependent_on_component-1024x576.png 1024w, https://blog.codersee.com/wp-content/uploads/2025/03/mocking_mockk_system_under_test_dependent_on_component-300x169.png 300w, https://blog.codersee.com/wp-content/uploads/2025/03/mocking_mockk_system_under_test_dependent_on_component-768x432.png 768w, https://blog.codersee.com/wp-content/uploads/2025/03/mocking_mockk_system_under_test_dependent_on_component-1536x864.png 1536w, https://blog.codersee.com/wp-content/uploads/2025/03/mocking_mockk_system_under_test_dependent_on_component.png 1920w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>So, our function A- the one we would like to focus on in our test- calls both B and C. It can happen sequentially or simultaneously; it doesn&#8217;t matter.</p>



<p>What matters is that when we want to test A, we want to see its behavior in different cases. How does it behave when B returns (for instance) user data successfully? What happens in case of a null value? Does it handle exceptions gracefully?</p>



<p>And to avoid spending countless hours on manual setup, we use the <strong>mocking </strong>technique to simulate different scenarios. Mostly with the help of libraries, like MockK.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>The important thing to remember is that mocking is not limited to functions. A, B, and C may as well represent services.</p>



<p>And in various sources, A will be referred to as the System Under Test (SUT), whereas B, and C will be Depended On Component (DOC).</p>
</blockquote>



<h2 class="wp-block-heading" id="h-mocking-stubbing-test-doubles">Mocking, Stubbing, Test Doubles</h2>



<p>Frankly, please skip this section if this is your first time with mocking or MockK. I truly believe you will benefit more from focusing on learning MockK-related concepts than slight differences in wording.</p>



<p>Anyway, from the chronicler&#8217;s duty, let me illustrate a few concepts:</p>



<ul class="wp-block-list">
<li><strong>stub </strong>is a fake object that returns hard-coded answers. Typically, we use it to return some value, but we don&#8217;t care about additional info, like how many times it was invoked, etc.</li>



<li><strong>mock</strong> is pretty similar, but this time, we can verify interactions, too. For example, to see if this was invoked with a particular ID value, exactly N times.</li>



<li><strong>stubbing</strong> means setting up a <em>stub</em> or a <em>mock</em> so that a particular method returns some value or throws an exception</li>



<li><strong>mocking</strong> means creating/using a <em>mock</em></li>



<li>and lastly, we use the <strong>test doubles </strong>term for any kind of replacement we use in place of a real object in your tests (that terms comes stund doubles in movies).</li>
</ul>



<p>And if you are looking for a really deep dive, I invite you to <a href="https://martinfowler.com/articles/mocksArentStubs.html" target="_blank" rel="noreferrer noopener">Martin Fowler&#8217;s article</a>.</p>



<h2 class="wp-block-heading" id="h-mockk-imports">MockK Imports</h2>



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



<p>Firstly, let&#8217;s define the necessary imports for MockK.</p>



<p>We will be working with a plain Kotlin / Gradle project with JUnit 5, so our <code>build.gradle.kts</code> dependencies should 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="">dependencies {
    testImplementation("io.mockk:mockk:1.13.17")
    testImplementation(kotlin("test"))
}

tasks.test {
    useJUnitPlatform()
}</pre>



<p>The <code>io.mockk:mockk</code> is the only thing we need when working with MockK (unless we want to work with couroutines- but I will get back to that in the fifth lesson).</p>



<h2 class="wp-block-heading" id="h-tested-code">Tested Code</h2>



<p>Then, let&#8217;s take a look at the code we are supposed to test:</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 User(val id: UUID, val email: String, val passwordHash: String)

class UserRepository {
    fun saveUser(email: String, passwordHash: String): UUID =
        UUID.fromString("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa")

    fun findUserByEmail(email: String): User? =
        User(
            id = UUID.fromString("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"),
            email = "found@codersee.com",
            passwordHash = "foundPwd"
        )
}

class EmailService {
    fun sendEmail(to: String, subject: String, body: String) {
        println("Sending body $body to $to with subject $subject")
    }
}

class UserService(
    private val userRepository: UserRepository,
    private val emailService: EmailService,
) {

    fun createUser(email: String, password: String): UUID {
        userRepository.findUserByEmail(email)
            ?.let { throw IllegalArgumentException("User with email $email already exists") }

        return userRepository.saveUser(email, password)
            .also { userId ->
                emailService.sendEmail(
                    to = email,
                    subject = "Welcome to Codersee!",
                    body = "Welcome user: $userId."
                )
            }
    }
}</pre>



<p>As we can see, we have a simple example of a <code>UserService</code> with one function- <code>createUser</code> . The service and the function that we will focus on in our tests.</p>



<p>And although <code>UserRepository</code> and <code>EmailService</code> look pretty weird, the <code>createUser</code> is more or less something we can find in our real-life code. We check if the given <code>email</code> is taken, and if that&#8217;s the case, we throw an exception. Otherwise, we save the user and send a notification e-mail.</p>



<h2 class="wp-block-heading" id="h-positive-amp-negative-scenario">Positive &amp; Negative Scenario</h2>



<p>Following, let&#8217;s do something different compared to other content about MockK. Let&#8217;s start by taking a look at the final result, and then we will see how we can get there.</p>



<p>So, firstly, let&#8217;s add the negative scenario:</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 AwesomeMockkTest {

    private val userRepository: UserRepository = mockk()
    private val emailService = mockk&lt;EmailService>()

    private val service = UserService(userRepository, emailService)

    @Test
    fun `should throw IllegalArgumentException when user with given e-mail already exists`() {
        val email = "contact@codersee.com"
        val password = "pwd"

        val foundUser = User(UUID.randomUUID(), email, password)
        every { userRepository.findUserByEmail(email) } returns foundUser

        assertThrows&lt;IllegalArgumentException> {
            service.createUser(email, password)
        }

        verifyAll { userRepository.findUserByEmail(email) }
    }
}</pre>



<p>As we can see, we start all by defining dependencies for <code>UserService</code> as MockK mocks, and then we simply inject them through the constructor.</p>



<p>After that, we add our JUnit 5 test that asserts if the <code>createUser</code> function has thrown an exception, nothing unusual. The &#8220;unusual&#8221; part here is the <em>every</em> and <em>verifyAll</em> &#8211; those two blocks come from MockK, and we will get back to them in a moment.</p>



<p>With that done, let&#8217;s add one more test:</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 `should return UUID when user with given e-mail successfully created`() {
    val email = "contact@codersee.com"
    val password = "pwd"
    val createdUserUUID = UUID.randomUUID()

    every { userRepository.findUserByEmail(email) } returns null
    every { userRepository.saveUser(email, password) } returns createdUserUUID
    every {
        emailService.sendEmail(
            to = email,
            subject = "Welcome to Codersee!",
            body = "Welcome user: $createdUserUUID."
        )
    } just runs

    val result = service.createUser(email, password)

    assertEquals(createdUserUUID, result)

    verifyAll {
        userRepository.findUserByEmail(email)
        userRepository.saveUser(email, password)
        emailService.sendEmail(
            to = email,
            subject = "Welcome to Codersee!",
            body = "Welcome user: $createdUserUUID."
        )
    }
}</pre>



<p>This time, we test the positive scenario, in which the user was not found by the <code>e-mail</code> and was created successfully.</p>



<h2 class="wp-block-heading">Defining MockK Mocks</h2>



<p>With all of that done, let&#8217;s start breaking down things here.</p>



<p>Let&#8217;s take a look at what we did first:</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 userRepository: UserRepository = mockk()
private val emailService = mockk&lt;EmailService>()

private val service = UserService(userRepository, emailService)</pre>



<p>So, one of the approaches to define objects as <strong>mocks</strong> with MockK is by using the <code>mockk</code> function. </p>



<p>It is a generic function. So that&#8217;s why I presented both ways to invoke it. But please treat that as an example. In real life, I suggest you stick to either <code>mockk()</code> or <code>mockk&lt;EmailService&gt;()</code> for a cleaner code.</p>



<p>Alternatively, we can achieve exactly the same with MockK annotations:</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="">@ExtendWith(MockKExtension::class)
class AwesomeMockkTest {

  @MockK
  lateinit var userRepository: UserRepository

  @MockK
  lateinit var emailService: EmailService

  @InjectMockKs
  lateinit var service: UserService

}</pre>



<p>The preferred approach is totally up to you. The important thing to mention is that the <code>@ExtendWith(MockKExtension::class)</code> comes from <strong>JUnit 5</strong>.</p>



<p>And for the <strong>JUnit 4</strong>, we would implement a rule:</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 AwesomeMockkTest {
  @get:Rule
  val mockkRule = MockKRule(this)

  @MockK
  lateinit var userRepository: UserRepository

  @MockK
  lateinit var emailService: EmailService

  @InjectMockKs
  lateinit var service: UserService

}</pre>



<h2 class="wp-block-heading">Missing Stubbing</h2>



<p>At this point, we know that we don&#8217;t use real objects, but mocks.</p>



<p>Let&#8217;s try to run our test without defining any behavior:</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 `should throw IllegalArgumentException when user with given e-mail already exists`() {
    val email = "contact@codersee.com"
    val password = "pwd"

    assertThrows&lt;IllegalArgumentException> {
        service.createUser(email, password)
    }

    verifyAll { userRepository.findUserByEmail(email) }
}</pre>



<p>In the console log, we should see the following:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Unexpected exception type thrown, expected: &lt;java.lang.IllegalArgumentException&gt; but was: &lt;io.mockk.MockKException&gt;</p>



<p>Expected :class java.lang.IllegalArgumentException</p>



<p>Actual :class io.mockk.MockKException</p>
</blockquote>



<p>Well, the issue is that when we do not specify a stubbing for a function that was invoked, MockK <strong>throws an exception</strong>.</p>



<p>But our test logic looks already for exception, so that&#8217;s why we got a message that this is simply an unexpected one.</p>



<p>Without the <code>assertThrows</code> , the message would be more obvious:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>no answer found for UserRepository(#1).findUserByEmail(contact@codersee.com) among the configured answers: (UserRepository(#1).saveUser(eq(contact@codersee.com), eq(pwd))))</p>



<p>io.mockk.MockKException: no answer found for UserRepository(#1).findUserByEmail(contact@codersee.com) among the configured answers: (UserRepository(#1).saveUser(eq(contact@codersee.com), eq(pwd))))</p>
</blockquote>



<p>So, <strong>lesson one</strong>: whenever we see a similar message, it means that our mock function was invoked, but we haven&#8217;t defined any stubbing.</p>



<h2 class="wp-block-heading" id="h-stubbing-in-mockk">Stubbing in MockK</h2>



<p>And we already saw how we can define a stubbing, but let&#8217;s take a look once again:</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 foundUser = User(UUID.randomUUID(), email, password)
every { userRepository.findUserByEmail(email) } returns foundUser</pre>



<p>We can read the above function as &#8220;return <code>foundUser</code> every time the <code>findUserByEmail</code> function is invoked with this, specific email value&#8221;.</p>



<p>When we run the test now, everything is working fine. Because in our logic, if the <code>findUserByEmail</code> returns a not null value, an exception is thrown. So, no more functions are invoked. In other words, <strong>no more interactions with our mock</strong> object😉 Also, our email value matches.</p>



<p>And most of the time, this is how I would suggest defining stubbing. This way, we also make sure that the exact value of <code>email</code> is passed.</p>



<p>When it comes to the answer part, <code>returns foundUser</code>, we can also use alternatives, like:</p>



<ul class="wp-block-list">
<li><code>answers { code }</code> &#8211; to define a block of code to run (and/or return a value)</li>



<li><code>throws ex</code> &#8211; to throw exception</li>



<li>and many, many more (I will put a link to the docs at the end of this article)</li>
</ul>



<h2 class="wp-block-heading">MockK Matchers</h2>



<p>The above stubbing expects that the <code>email</code> value will be equal to what we define.</p>



<p>Technically, it is the equivalent of using the <code>eq</code> function:</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=""> every { userRepository.findUserByEmail(eq(email)) } returns foundUser</pre>



<p>And <code>eq</code> uses the <code>deepEquals</code> function to compare the values.</p>



<p>But sometimes, we do not want to, or we cannot define the exact value to match.</p>



<p>Let&#8217;s imagine that some function is invoked 20 times with various values. Technically, we could define all 20 matches.</p>



<p>But instead, we use one of the many matchers available in MockK, like <code>any</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=""> every { userRepository.findUserByEmail(any()) } returns foundUser</pre>



<p>And whereas <code>eq</code> is the most concrete one, <code>any</code> is the most generic one. The <code>foundUser</code> is returned if <code>findUserByEmail</code> is invoked with anything.</p>



<p>And MockK comes with plenty of other matchers, like:</p>



<ul class="wp-block-list">
<li><code>any(Class)</code> &#8211; to match only if an instance of a given Class is passed</li>



<li><code>isNull</code> / <code>isNull(inverse=true)</code> &#8211; for null check</li>



<li><code>cmpEq(value)</code> , <code>more(value)</code> , <code>less(value)</code> &#8211; for the <code>compareTo</code> comparisons</li>



<li>and many more that you can find in the documentation</li>
</ul>



<p>For example, let&#8217;s take a look at the <code>match</code> example:</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="">every {
  userRepository.findUserByEmail(
    match { it.contains("@") }
  )
} returns foundUser</pre>



<p>As we can see, this way the <code>foundUser</code> will be returned only if the passed value contains <code>@</code> sign.</p>



<h2 class="wp-block-heading">Unit Functions</h2>



<p>At this point, we know how to deal with matchers and the <code>returns</code> .</p>



<p>But what if the function does not return anything? We saw that previously, so let&#8217;s take a look once again:</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="">every { emailService.sendEmail(any(), any(), any()) } just runs</pre>



<p>Matchers are irrelevant right now, so I replaced them with <code>any()</code> .</p>



<p>The important thing here is that <code>just runs</code> is one of the approaches.</p>



<p>Alternatively, we can achieve the same:</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="">every { emailService.sendEmail(any(), any(), any()) } returns Unit
every { emailService.sendEmail(any(), any(), any()) } answers { }
justRun { emailService.sendEmail(any(), any(), any()) } </pre>



<p>The choice here is totally up to you.</p>



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



<p>And that&#8217;s all for our first lesson dedicated to MockK and Kotlin. </p>



<p>As I mentioned above, <a href="https://mockk.io/" target="_blank" rel="noreferrer noopener">right here</a> you can find the MockK documentation. And although I strongly encourage you to visit it, I also would suggest doing it after my series- when we cover the most important concepts:</p>



<ul class="wp-block-list">
<li>Getting Started with MockK in Kotlin [1/5]</li>



<li><a href="https://blog.codersee.com/verification-mockk/">Verification in MockK [2/5]</a></li>



<li><a href="https://blog.codersee.com/mockk-objects-top-level-extension-functions/">MockK: Objects, Top-Level, and Extension Functions [3/5]</a></li>



<li><a href="https://blog.codersee.com/mockk-spy-relaxed-mock-partial-mocking/">MockK: Spies, Relaxed Mocks, and Partial Mocking [4/5]</a></li>



<li><a href="https://blog.codersee.com/mockk-coroutines/">MockK with Coroutines [5/5]</a></li>
</ul>



<p></p>
<p>The post <a href="https://blog.codersee.com/getting-started-with-mockk-kotlin/">Getting Started with MockK in Kotlin [1/5]</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/getting-started-with-mockk-kotlin/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			</item>
		<item>
		<title>MongoDB with Kotlin Coroutines in Ktor</title>
		<link>https://blog.codersee.com/mongodb-kotlin-coroutines-ktor/</link>
					<comments>https://blog.codersee.com/mongodb-kotlin-coroutines-ktor/#respond</comments>
		
		<dc:creator><![CDATA[Piotr]]></dc:creator>
		<pubDate>Wed, 26 Feb 2025 16:00:00 +0000</pubDate>
				<category><![CDATA[Ktor]]></category>
		<category><![CDATA[Coroutines]]></category>
		<category><![CDATA[Mongo]]></category>
		<category><![CDATA[MongoDB]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=17012558</guid>

					<description><![CDATA[<p>In this lesson, we are going to learn how to work with MongoDB and coroutines using the MongoDB Kotlin Driver in Ktor.</p>
<p>The post <a href="https://blog.codersee.com/mongodb-kotlin-coroutines-ktor/">MongoDB with Kotlin Coroutines in Ktor</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>In this article, I will show you how to work with MongoDB and Kotlin coroutines in a Ktor application. </p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Note: this article was created based on one of the services we implement in my <a href="https://codersee.com/courses/ktor-server-pro/">Ktor Server Pro course</a>. If you would like to learn how to expose a fully functional REST API, secure the application, and many many more, then don&#8217;t hesitate and secure your spot today😉</p>
</blockquote>



<p>Before we start, I just wanted to mention that at the moment of writing <strong>KMongo is officially deprecated! </strong>Maybe you saw my previous article, maybe you saw some outdated content in some other places. Either way, we should not use that library anymore in favor of the official driver that comes in two flavors:</p>



<ul class="wp-block-list">
<li><strong>MongoDB Kotlin Driver (for applications using coroutines)</strong></li>



<li>MongoDB Kotlin Sync Driver (for apps that require synchronous processing)</li>
</ul>



<h2 class="wp-block-heading" id="h-video-content">Video Content</h2>



<p>As always, if you prefer a video content, then please check out my latest YouTube video:</p>


<figure class="wp-block-embed-youtube wp-block-embed is-type-video is-provider-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><a href="https://blog.codersee.com/mongodb-kotlin-coroutines-ktor/"><img decoding="async" src="https://blog.codersee.com/wp-content/plugins/wp-youtube-lyte/lyteCache.php?origThumbUrl=%2F%2Fi.ytimg.com%2Fvi%2F8NfdOv2R-4U%2Fhqdefault.jpg" alt="YouTube Video"></a><br /><br /><figcaption></figcaption></figure>


<h2 class="wp-block-heading" id="h-create-amp-verify-mongodb-instance">Create &amp; Verify MongoDB Instance</h2>



<p>If you already have a MongoDB instance installed on your machine, feel free to skip this step.</p>



<p>On the other hand, if that is not the case, you can install it using the <a href="https://www.mongodb.com/docs/manual/installation/">official manual</a>. Or, if you just like me have a Docker environment, you can run the following command:</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="">docker run --name my_awesome_mongo_name -d -p 27017:27017 mongo:8.0.4</pre>



<p>This way, we run the Mongo docker container and expose it&#8217;s port <code>27017</code> .</p>



<p>And to verify it running, we can run the <code>docker ps</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="">CONTAINER ID   IMAGE         COMMAND                  CREATED          STATUS          PORTS                      NAMES
9ecd8986ac34   mongo:8.0.4   "docker-entrypoint.s…"   20 seconds ago   Up 19 seconds   0.0.0.0:27017->27017/tcp   my_awesome_mongo_name</pre>



<p>As we can see, it was created successfully and we should be able to connect to it at <code>localhost:27017</code></p>



<p>We can assert that, as well, for example with a free and official tool- <a href="https://www.mongodb.com/try/download/compass">MongoDB Compass</a>:</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="599" src="http://blog.codersee.com/wp-content/uploads/2025/02/ktor_mongodb_coroutines_verify_connection_compass-1024x599.png" alt="Image is a screenshot from MongoDB Compass application and shows how to establish a new connection to mongo instance." class="wp-image-17012563" srcset="https://blog.codersee.com/wp-content/uploads/2025/02/ktor_mongodb_coroutines_verify_connection_compass-1024x599.png 1024w, https://blog.codersee.com/wp-content/uploads/2025/02/ktor_mongodb_coroutines_verify_connection_compass-300x176.png 300w, https://blog.codersee.com/wp-content/uploads/2025/02/ktor_mongodb_coroutines_verify_connection_compass-768x450.png 768w, https://blog.codersee.com/wp-content/uploads/2025/02/ktor_mongodb_coroutines_verify_connection_compass.png 1411w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>And if we are able to connect, it means that we can head to the next step. </p>



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



<p>Nextly, let&#8217;s navigate to the <a href="https://start.ktor.io/" target="_blank" rel="noreferrer noopener">Ktor Project Generator</a> page to create a fresh project from scratch. </p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>If you use the IntelliJ Ultimate Edition, then you can generate it in your IDE</p>
</blockquote>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="468" src="http://blog.codersee.com/wp-content/uploads/2025/02/ktor_mongodb_coroutines_create_project_in_generator-1024x468.png" alt="Image is a screenshot from Ktor Generator Page and shows necessary settings for our project." class="wp-image-17012564" srcset="https://blog.codersee.com/wp-content/uploads/2025/02/ktor_mongodb_coroutines_create_project_in_generator-1024x468.png 1024w, https://blog.codersee.com/wp-content/uploads/2025/02/ktor_mongodb_coroutines_create_project_in_generator-300x137.png 300w, https://blog.codersee.com/wp-content/uploads/2025/02/ktor_mongodb_coroutines_create_project_in_generator-768x351.png 768w, https://blog.codersee.com/wp-content/uploads/2025/02/ktor_mongodb_coroutines_create_project_in_generator.png 1230w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>As we can see, we will be using <strong>Ktor 3.1.0</strong> with <strong>Netty</strong> and configuration in the <strong>YAML</strong> file.</p>



<p>The <strong>important </strong>thing to mention here is that if we want to work with Kotlin coroutines, <strong>we should not select the below MongoDB plugin:</strong></p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="373" src="http://blog.codersee.com/wp-content/uploads/2025/02/ktor_mongodb_coroutines_project_generator_sync_mongo-1024x373.png" alt="Image shows MongoDB plugin for Ktor that we should not use when working with coroutines. " class="wp-image-17012565" srcset="https://blog.codersee.com/wp-content/uploads/2025/02/ktor_mongodb_coroutines_project_generator_sync_mongo-1024x373.png 1024w, https://blog.codersee.com/wp-content/uploads/2025/02/ktor_mongodb_coroutines_project_generator_sync_mongo-300x109.png 300w, https://blog.codersee.com/wp-content/uploads/2025/02/ktor_mongodb_coroutines_project_generator_sync_mongo-768x279.png 768w, https://blog.codersee.com/wp-content/uploads/2025/02/ktor_mongodb_coroutines_project_generator_sync_mongo.png 1223w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>Because as we can see, this adds the MongoDB Kotlin Sync Driver for synchronous processing:</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="828" src="http://blog.codersee.com/wp-content/uploads/2025/02/ktor_mongodb_coroutines_project_generator_sync_mongo_preview-1024x828.png" alt="" class="wp-image-17012566" srcset="https://blog.codersee.com/wp-content/uploads/2025/02/ktor_mongodb_coroutines_project_generator_sync_mongo_preview-1024x828.png 1024w, https://blog.codersee.com/wp-content/uploads/2025/02/ktor_mongodb_coroutines_project_generator_sync_mongo_preview-300x243.png 300w, https://blog.codersee.com/wp-content/uploads/2025/02/ktor_mongodb_coroutines_project_generator_sync_mongo_preview-768x621.png 768w, https://blog.codersee.com/wp-content/uploads/2025/02/ktor_mongodb_coroutines_project_generator_sync_mongo_preview.png 1224w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>So, in our case, to keep our Ktor application as vanilla as possible, we will not add any additional plugins. </p>



<p>With that done, let&#8217;s hit the <em>Download</em> button and let&#8217;s import the project to our IDE.</p>



<h2 class="wp-block-heading" id="h-add-mongodb-async-driver-to-ktor">Add MongoDB Async Driver to Ktor</h2>



<p>Before adding the necessary import, let&#8217;s perform a small cleanup. </p>



<p>Firstly, let&#8217;s remove the <code>Routing.kt</code> file- we don&#8217;t need it for this tutorial.</p>



<p>Then, let&#8217;s navigate to the <code>Application.kt</code> and let&#8217;s get rid of routing config, as well. Eventually, we should have something like that: </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

import io.ktor.server.application.*

fun main(args: Array&lt;String>) {
    io.ktor.server.netty.EngineMain.main(args)
}

fun Application.module() {}</pre>



<p>With that done, let&#8217;s add the MongoDB Kotlin Driver to work with coroutines. </p>



<p>During the configuration, we decided to use the Gradle version catalog. So now, we must navigate to the <code>libs.versions.toml</code> inside the <code>gradle</code> directory and add the <code>mongodb-version</code> along with <code>mongodb-driver-kotlin</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="">[versions]
kotlin-version = "2.1.10"
ktor-version = "3.1.0"
logback-version = "1.4.14"
mongodb-version = "5.3.1"

[libraries]
ktor-server-core = { module = "io.ktor:ktor-server-core-jvm", version.ref = "ktor-version" }
ktor-server-netty = { module = "io.ktor:ktor-server-netty", version.ref = "ktor-version" }
logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback-version" }
ktor-server-config-yaml = { module = "io.ktor:ktor-server-config-yaml", version.ref = "ktor-version" }
ktor-server-test-host = { module = "io.ktor:ktor-server-test-host", version.ref = "ktor-version" }
kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin-version" }
mongodb-driver-kotlin = { module = "org.mongodb:mongodb-driver-kotlin-coroutine", version.ref = "mongodb-version" }

[plugins]
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin-version" }
ktor = { id = "io.ktor.plugin", version.ref = "ktor-version" }</pre>



<p>With that done, let&#8217;s navigate to <code>build.gradle.kts</code> and add the following line: </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(libs.mongodb.driver.kotlin)</pre>



<p>Lastly, let&#8217;s sync the gradle project so that the necessary libraries are fetched. </p>



<h2 class="wp-block-heading" id="h-introduce-model-classes">Introduce Model Classes</h2>



<p>Before we can start working with coroutines, we must prepare classes that will be translated into Mongo documents and vice versa. </p>



<p>To do so, let&#8217;s create the <code>Product.kt</code> class and put the following:</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.bson.codecs.pojo.annotations.BsonId
import org.bson.types.ObjectId

data class Product(
    @BsonId
    val id: ObjectId? = null,
    val name: String,
    val description: String,
    val price: Double,
    val category: ProductCategory,
    val tags: List&lt;ProductTag>,
)

enum class ProductCategory {
    VIDEO_GAMES, TOOLS, HOME_AND_KITCHEN, FOOD
}

enum class ProductTag {
    EXCLUSIVE, HANDMADE, ORGANIC, BESTSELLER
}</pre>



<p>As we can see, all fields except the <em>id</em> field are plain Kotlin classes. And later, they will be serialized/deserialized 1:1 when saving and retrieving from the database. </p>



<p>When it comes to the <code>id</code> field, we mark it using the <code>@BsonId</code> annotation. Thanks to that, it will be serialized to the <code>_id</code> BSON field that represents a primary key of our document. Moreover, we assign a default null value to it. This way, the value will be generated automatically. </p>



<h2 class="wp-block-heading" id="h-configure-ktor-connection-to-mongodb">Configure Ktor Connection to MongoDB</h2>



<p>As the next step, let&#8217;s connect our Ktor application to the Mongo instance. </p>



<p>And for that purpose, let&#8217;s navigate to <code>Application.kt</code> and implement the following 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="">fun Application.module() {
    val settings = MongoClientSettings.builder()
        .applyConnectionString(
            ConnectionString("mongodb://localhost:27017")
        )
        .build()

    val client = MongoClient.create(settings)
    val database = client.getDatabase("application")
    val productCollection = database.getCollection&lt;Product>("products")
}</pre>



<p>Firstly, we instantiate the <em>MongoClientSettings</em> builder. A builder that allows us to configure the connection string for our database. Additionally, if you are looking for more configuration options, like reads or writes repetition, then you should start in there. </p>



<p>Then, we create a new client and pass our settings to it. If you would like to, then you could use another variant of the <code>create</code> function that takes the connection String as an argument:</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="">public fun create(connectionString: String): MongoClient</pre>



<p>But, in my opinion, using the builder approach is a better choice in terms of extensibility. </p>



<p>With that done, we get the instance of our database, by passing its name. Lastly, we obtain the MongoCollection that we will be injecting later into the product repository. Again, we pass the name of our collection, too.</p>



<p>To verify, let&#8217;s run our application.</p>



<p>As a result, we should see the following text in the logs indicating that everything is perfectly fine: </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="">[cluster-ClusterId{value='67bd5f4689251d25d5077fb7', description='null'}-localhost:27017] INFO  org.mongodb.driver.cluster - Monitor thread successfully connected to server with description ServerDescription{address=localhost:27017, type=STANDALONE, cryptd=false, state=CONNECTED, ok=true, minWireVersion=0, maxWireVersion=25, maxDocumentSize=16777216, logicalSessionTimeoutMinutes=30, roundTripTimeNanos=26289200, minRoundTripTimeNanos=0}</pre>



<h2 class="wp-block-heading" id="h-mongodb-coroutines-crud-operations">MongoDB Coroutines CRUD Operations</h2>



<p>After we did all of that preparation, we can finally create the <code>ProductRepository</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 ProductRepository(
    private val productCollection: MongoCollection&lt;Product>,
) { }</pre>



<p>And inject the collection in <code>Application.kt</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="">val productRepository = ProductRepository(productCollection)</pre>



<p>The constructor injection is a great way to make our code easier to test in the future. </p>



<p>Anyway, coming back to the topic, let&#8217;s learn how we can how we can perform basic CRUD operations with coroutines. </p>



<h3 class="wp-block-heading" id="h-persits-products">Persits Products</h3>



<p>Initially, our <code>products</code> collection is empty, so let&#8217;s add the following code to start populating it: </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="">suspend fun save(product: Product): Product? {
    val result = productCollection.insertOne(product)

    return result.insertedId
        ?.let { product.copy(id = it.asObjectId().value) }
}</pre>



<p>First of all, we must make our function <em>suspend</em>. Why? Because the <em>insertOne</em> we use is a suspend function, so we must invoke it either from the coroutine or another suspend function. </p>



<p>When it comes to the persisting- the function that we use returns the <code>InsertOneResult</code> that allows us to either get a boolean informing if the write was acknowledged or the generated identifier of the saved product. And in my opinion, reading that and returning a Product instance with the updated field is a quite nice approach. </p>



<p>After that, let&#8217;s get back to the <code>Application.kt</code> and test this function: </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 saved = runBlocking {
    productRepository.save(
        Product(
            name = "Product 1",
            description = "Description 1",
            price = 19.99,
            category = ProductCategory.TOOLS,
            tags = listOf(ProductTag.EXCLUSIVE, ProductTag.BESTSELLER)
        )
    )
}

println(saved)</pre>



<p>I know, the good, old println 🤠 </p>



<p>Anyway, if we run our application, we should see the following in the logs: </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="">Product(id=67bd62f974edd96bbd59874a, name=Product 1, description=Description 1, price=19.99, category=TOOLS, tags=[EXCLUSIVE, BESTSELLER])</pre>



<p>Additionally, when we hit the <em>refresh</em> button in MongoDB Compass, we should see the following: </p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="358" src="http://blog.codersee.com/wp-content/uploads/2025/02/ktor_mongodb_created_product_atlas_view-1024x358.png" alt="Image shows a screenshot from MongoDB Compass and persisted product with coroutines." class="wp-image-17012580" srcset="https://blog.codersee.com/wp-content/uploads/2025/02/ktor_mongodb_created_product_atlas_view-1024x358.png 1024w, https://blog.codersee.com/wp-content/uploads/2025/02/ktor_mongodb_created_product_atlas_view-300x105.png 300w, https://blog.codersee.com/wp-content/uploads/2025/02/ktor_mongodb_created_product_atlas_view-768x269.png 768w, https://blog.codersee.com/wp-content/uploads/2025/02/ktor_mongodb_created_product_atlas_view.png 1410w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>And this proves that not only the Product was saved. But also, the <code>application</code> database and <code>products</code> collection was created by our client automatically. </p>



<h2 class="wp-block-heading" id="h-find-by-id">Find By ID</h2>



<p> Nextly, let&#8217;s implement the function to fetch product by identifier: </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="">suspend fun findById(id: String): Product? {
    val objectId = ObjectId(id)

    return productCollection.find(
        eq("_id", objectId)
    ).firstOrNull()
}</pre>



<p>This time, we use the <code>find</code> method. And this function returns the <em>FindFlow</em> which is the <em>Flow </em>implementation for find operations. </p>



<p>This function allows us to pass filters as arguments to it. And to get the particular product, we use the <strong>eq </strong>filter. One of the many filters that we can find in <code>com.mongodb.client.model.Filters</code>. We must remember that our identifier is of the <em>ObjectId</em> type, so we create a new instance from our String value. </p>



<p>Lastly, we invoke the <code>firstOrNull</code>&#8211; the terminal operator that returns the first element emitted by the flow and then cancels flow&#8217;s. We leverage the fact that only one element with such an identifier can be found in our database.</p>



<p>So with all of that done, let&#8217;s get back to the <code>Application.kt</code> and test this function: </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 found = runBlocking {
    productRepository.findById("67bd62f974edd96bbd59874a")
}

val notFound = runBlocking {
    productRepository.findById("67bd62f974edd96bbd59874b")
}

// Logs: 

// Product(id=67bd62f974edd96bbd59874a, name=Product 1, description=Description 1, price=19.99, category=TOOLS, tags=[EXCLUSIVE, BESTSELLER])

// null</pre>



<p>As we can see, our test proves that <em>findById</em> not only works but also it simply returns null when nothing was found. No unexpected exceptions, etc. </p>



<h3 class="wp-block-heading" id="h-updating-products">Updating Products</h3>



<p>As the next step, let&#8217;s add the function responsible for updating products: </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="">suspend fun update(id: String, product: Product): Product? {
    val objectId = ObjectId(id)

    return productCollection.findOneAndReplace(
        filter = eq("_id", objectId),
        replacement = product,
        options = FindOneAndReplaceOptions().returnDocument(ReturnDocument.AFTER)
    )
}</pre>



<p>Again, this function is a suspended function, and again we use the same combination of the <em>eq</em> filter and <em>ObjectId</em> to find the item we are interested in. </p>



<p>The function that we use- <code>findOneAndReplace</code>&#8211; allows us to simply replace the existing document by passing a new version. Nevertheless, by default, this function returns the <strong>object before the update!</strong> And in my opinion, it makes more sense to return the updated versions in this case. And that&#8217;s why we specify the additional option. </p>



<p>As a note: if you would like to update just some fields, then the <code>findOneAndUpdate</code> may be a better choice. </p>



<p>With all of that done, let&#8217;s test our functionality: </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 updated = runBlocking {
    productRepository.update(
        id = "67bd62f974edd96bbd59874a",
        product = Product(
            name = "Updated Product 1",
            description = "Updated Description 1",
            price = 20.11,
            category = ProductCategory.FOOD,
            tags = listOf(ProductTag.BESTSELLER)
        )
    )
}

val notUpdated = runBlocking {
    productRepository.update(
        id = "67bd62f974edd96bbd59874b",
        product = Product(
            name = "Updated Product 2",
            description = "Updated Description 3",
            price = 20.11,
            category = ProductCategory.FOOD,
            tags = listOf(ProductTag.BESTSELLER)
        )
    )
}

// Logs: 

// Product(id=67bd62f974edd96bbd59874a, name=Updated Product 1, description=Updated Description 1, price=20.11, category=FOOD, tags=[BESTSELLER])

// null</pre>



<p>As we can see, everything works as expected. Our function returns the <strong>updated</strong> product. And moreover, it does not throw any exceptions when a product is not found! </p>



<h3 class="wp-block-heading" id="h-delete-products">Delete Products</h3>



<p>Nextly, let&#8217;s take a look at how we can remove products from our database: </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="">suspend fun deleteById(id: String): Boolean {
    val objectId = ObjectId(id)

    val deleteResult = productCollection.deleteOne(
        eq("_id", objectId)
    )

    return deleteResult.deletedCount == 1L
}</pre>



<p>At this point of our MongoDB coroutines tutorial, I think the code is quite descriptive. We use the same pattern we did previously to find by ID and we utilize the function from <code>DeleteResult</code> to get the count of deleted items. </p>



<p>Of course, given we have only one item with a particular <code>_id</code>, the function returns <em>true</em> only if the count is equal to one. </p>



<p>And again, a small note from my end if you would like to return the deleted product instead, then you can use the <code>findOneAndDelete</code> instead.</p>



<p>I will skip the testing part here, you must trust me 😀</p>



<h3 class="wp-block-heading" id="h-case-insensitive-search-in-mongodb">Case-insensitive Search In MongoDB</h3>



<p>As the last step, I will show you how to utilize the function that we already know (<code>find</code>) to perform a case-insensitive search in MongoDB: </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 find(
    title: String,
): Flow&lt;Product> {
    return productCollection.find(
        regex(Product::name.name, title, "i")
    )
}</pre>



<p>As we can see, this time we make use of the <code>regex</code> function, that will add the&#8230; regex filter 😀 And thanks to the <strong>&#8220;i&#8221;</strong> option, the whole search will be case-insensitive.</p>



<p>Additionally, we make use of the <em>name</em> field reference. We could use a simple String value- &#8220;name&#8221;- but thanks to our approach we won&#8217;t need to remember to manually update the name in case of the update. </p>



<p>Lastly, I just wanted to mention here that this is one of the approaches to tackle this issue. According to the Mongo docs, we have also two more options: </p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>1. Create a case-insensitive index with a collation strength of 1 or 2, and specify that your query uses the same collation.<br>2. Set the default collation strength of your collection to 1 or 2 when you create it, and do not specify a different collation in your queries and indexes.</p>
</blockquote>



<h3 class="wp-block-heading" id="h-adding-sorting-and-pagination-to-search-results">Adding Sorting and Pagination to Search Results</h3>



<p>Lastly, let&#8217;s make a small adjustment to add pagination and sorting to our logic.</p>



<p>To do so, let&#8217;s implement the <code>Order</code> Enum first:</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="">enum class Order {
    ASC, DESC
}</pre>



<p>And with that done, let&#8217;s get back to our repository: </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 find(
    title: String,
    sortBy: String,
    order: Order,
    limit: Int,
    skip: Int,
): Flow&lt;Product> {
    val sort = when (order) {
        Order.ASC -> ascending(sortBy)
        Order.DESC -> descending(sortBy)
    }

    return productCollection.find(
        regex(Product::name.name, title, "i")
    )
        .sort(sort)
        .limit(limit)
        .skip(skip)
}</pre>



<p>As we can see, our function allows us to pass 4 more arguments during the invocation: <em>sortBy</em>, <em>order</em>, <em>limit</em>, and <em>skip</em>.</p>



<p>So from now on, we can not only perform the search but also set the limit of results, ordering, as well as the order of items.</p>



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



<p>And that&#8217;s all for this tutorial on how to work with MongoDB and Kotlin coroutines in Ktor. </p>



<p>I hope you enjoyed it, and if you would like to learn more Ktor concepts in a fully hands-on manner, then check out my <a href="https://codersee.com/courses/ktor-server-pro/">course</a>. </p>



<p>Lastly, if you would like to get the whole codebase, then you can find it in <a href="https://github.com/codersee-blog/ktor-mongodb-coroutines" target="_blank" rel="noreferrer noopener">this GitHub repository</a>.</p>



<p></p>
<p>The post <a href="https://blog.codersee.com/mongodb-kotlin-coroutines-ktor/">MongoDB with Kotlin Coroutines in Ktor</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/mongodb-kotlin-coroutines-ktor/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-04-30 02:28:03 by W3 Total Cache
-->