<?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>WebClient Archives - Codersee blog- Kotlin on the backend</title>
	<atom:link href="https://blog.codersee.com/tag/webclient/feed/" rel="self" type="application/rss+xml" />
	<link></link>
	<description>Kotlin &#38; Backend Tutorials - Learn Through Practice.</description>
	<lastBuildDate>Wed, 16 Apr 2025 04:49:52 +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>WebClient Archives - Codersee blog- Kotlin on the backend</title>
	<link></link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Integration Tests for WebClient with WireMock</title>
		<link>https://blog.codersee.com/integration-tests-webclient-wiremock/</link>
					<comments>https://blog.codersee.com/integration-tests-webclient-wiremock/#comments</comments>
		
		<dc:creator><![CDATA[Piotr]]></dc:creator>
		<pubDate>Wed, 28 Feb 2024 10:56:57 +0000</pubDate>
				<category><![CDATA[Spring]]></category>
		<category><![CDATA[JUnit 5]]></category>
		<category><![CDATA[Spring Boot]]></category>
		<category><![CDATA[Testing]]></category>
		<category><![CDATA[WebClient]]></category>
		<category><![CDATA[WireMock]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=9508747</guid>

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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



<p></p>



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



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



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



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



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



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



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



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



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



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



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



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



<pre class="EnlighterJSRAW" data-enlighter-language="yaml" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">api:
  github:
    key: ${GITHUB_API_TOKEN:123}
    url: ${GITHUB_API_BASE_URL:http://localhost:8090}
    version: ${GITHUB_API_VERSION:2022-11-28}</pre>



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



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



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



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



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



<p>Firstly, let&#8217;s add the <code>config</code> package and put the <code>GitHubApiProperties</code> data class in there:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@ConfigurationProperties("api.github")
data class GitHubApiProperties(
    val url: String,
    val key: String,
    val version: String,
)</pre>



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



<p>With that done, let&#8217;s add the <code>GitHubApiConfig</code> in the same package: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@Configuration
@EnableConfigurationProperties(GitHubApiProperties::class)
class GitHubApiConfig(
    private val gitHubApiProperties: GitHubApiProperties,
) {
    @Bean
    fun webClient(builder: WebClient.Builder): WebClient =
        builder
            .baseUrl(gitHubApiProperties.url)
            .defaultHeader("Authorization", "Bearer ${gitHubApiProperties.key}")
            .defaultHeader("X-GitHub-Api-Version", gitHubApiProperties.version)
            .defaultHeader("Accept", "application/vnd.github+json")
            .build()
}</pre>



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



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



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



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



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



<p>As the first step, let&#8217;s add the <code>model</code> package and the <code>PageableGitHubResponse</code>: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">data class PageableGitHubResponse&lt;T>(
    val items: List&lt;T>,
    val hasMoreItems: Boolean,
)</pre>



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



<p>As the next step, let&#8217;s implement the <code>GitHubRepoResponse</code> along with <code>GitHubOwnerResponse</code>: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">data class GitHubRepoResponse(
    val fork: Boolean,
    val name: String,
    val owner: GitHubOwnerResponse,
)

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



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



<p>Lastly, let&#8217;s add the <code>UpstreamApiException</code>: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import org.springframework.http.HttpStatusCode

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



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



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



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



<p>So, let&#8217;s add the <code>api</code> package and implement the <code>GitHubApi</code> class:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@Component
class GitHubApi(
  private val webClient: WebClient,
) {

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



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



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



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



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



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



<p>Lastly, let&#8217;s add the missing code:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">private suspend inline fun &lt;reified T> mapToPageableResponse(clientResponse: ClientResponse): PageableGitHubResponse&lt;T>? {
  val hasNext = checkIfMorePagesToFetch(clientResponse)

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

    HttpStatus.NOT_FOUND -> null

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

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



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



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



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



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



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



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



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



<li>404 Not Found, </li>



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



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



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



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



<p>So, let&#8217;s start by adding the necessary dependencies: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">testImplementation("org.springframework.cloud:spring-cloud-contract-wiremock:4.1.1")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0")</pre>



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



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



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



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



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



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



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



<li>list_github_repositories_200_OK_page_1.json</li>



<li>list_github_repositories_401_UNAUTHORIZED.json</li>



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



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



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



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



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



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



<p>This function will be responsible for reading JSON files inside the test resources as String values:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import org.springframework.core.io.ClassPathResource

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



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



<p>Finally, let&#8217;s start implementing the <code>GitHubApiTest</code> class: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">private const val TEST_KEY = "TEST_KEY"
private const val TEST_PORT = 8082
private const val TEST_VERSION = "2022-11-28"

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

  @Autowired
  private lateinit var gitHubApi: GitHubApi

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

}</pre>



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



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



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



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



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



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



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



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



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



<p>Wonderful! </p>



<p>As the first test, let&#8217;s start with the <code>404 Not Found</code> case. Usually, we will start with happy path cases, but for learning purposes, this one is the shortest case 😉 </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@Test
fun `Given 404 NOT FOUND response When fetching repository by username Then should return null`() = runTest {
  // Given
  wireMockServer.stubFor(
    WireMock.get(WireMock.urlEqualTo("/users/$username/repos?page=$page&amp;per_page=$perPage"))
      .withHeader("Authorization", WireMock.equalTo("Bearer $TEST_KEY"))
      .withHeader("X-GitHub-Api-Version", WireMock.equalTo(TEST_VERSION))
      .withHeader("Accept", WireMock.equalTo("application/vnd.github+json"))
      .willReturn(
        WireMock.aResponse()
          .withStatus(HttpStatus.NOT_FOUND.value())
          .withHeader(HttpHeaders.CONTENT_TYPE, "application/json; charset=utf-8")
          .withBody(
            getResponseBodyAsString("/responses/external/github/list_github_repositories_404_NOT_FOUND.json"),
          ),
      ),
  )

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

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



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



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



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



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



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



<li>URL matches, </li>



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



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



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



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



<p>As we already started with error cases, let&#8217;s verify that whenever the endpoint returns <code>401 Unauthorized</code>, the <code>UpstreamApiException</code> is thrown:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@Test
fun `Given 401 UAUTHORIZED response When fetching repository by username Then should throw UpstreamApiException`() = runTest {
  // Given
  wireMockServer.stubFor(
    WireMock.get(WireMock.urlEqualTo("/users/$username/repos?page=$page&amp;per_page=$perPage"))
      .withHeader("Authorization", WireMock.equalTo("Bearer $TEST_KEY"))
      .withHeader("X-GitHub-Api-Version", WireMock.equalTo(TEST_VERSION))
      .withHeader("Accept", WireMock.equalTo("application/vnd.github+json"))
      .willReturn(
        WireMock.aResponse()
          .withStatus(HttpStatus.UNAUTHORIZED.value())
          .withHeader(HttpHeaders.CONTENT_TYPE, "application/json; charset=utf-8")
          .withBody(
            getResponseBodyAsString("/responses/external/github/list_github_repositories_401_UNAUTHORIZED.json"),
          ),
      ),
  )

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

  // Then
  assertNotNull(exception)

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



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



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



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



<p>Following, let&#8217;s cover the empty list case:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@Test
fun `Given 200 OK response with empty list When fetching repository by username Then should return repository with correct properties and has next false`() =
  runTest {
    // Given
    val linkHeader = """&lt;https://api.github.com/user/64011387/repos?page=3&amp;per_page=2>; rel="prev","""

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

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

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

    assertEquals(expected, result)
  }</pre>



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



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



<p>Last before least, let&#8217;s make sure that we got the expected objects in a list and that the logic responsible for the <code>link</code> header check returns true:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@Test
fun `Given 200 OK response with payload When fetching repository by username Then should return repository with correct properties and has next true`() =
  runTest {
    // Given
    val linkHeader = """&lt;https://api.github.com/user/64011387/repos?page=3&amp;per_page=2>; rel="next","""

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

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

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

    assertEquals(expected, result)
  }</pre>



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



<p>And finally, let&#8217;s double-check the flag logic:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@Test
fun `Given 200 OK response with payload When fetching repository by username Then should return repository with correct properties and has next false`() =
  runTest {
    // Given
    val linkHeader = """&lt;https://api.github.com/user/64011387/repos?page=3&amp;per_page=2>; rel="prev","""

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

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

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

    assertEquals(expected, result)
  }</pre>



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



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



<p>I hope you enjoyed this one and I recommend you check out my other articles related to <a href="https://blog.codersee.com/category/spring/">Spring Framework</a>.</p>
<p>The post <a href="https://blog.codersee.com/integration-tests-webclient-wiremock/">Integration Tests for WebClient with WireMock</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.codersee.com/integration-tests-webclient-wiremock/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>Spring WebClient With Kotlin Coroutines</title>
		<link>https://blog.codersee.com/spring-webclient-with-kotlin-coroutines/</link>
					<comments>https://blog.codersee.com/spring-webclient-with-kotlin-coroutines/#respond</comments>
		
		<dc:creator><![CDATA[Piotr]]></dc:creator>
		<pubDate>Tue, 07 Mar 2023 07:00:42 +0000</pubDate>
				<category><![CDATA[Spring]]></category>
		<category><![CDATA[Coroutines]]></category>
		<category><![CDATA[Spring WebFlux]]></category>
		<category><![CDATA[WebClient]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=6004406</guid>

					<description><![CDATA[<p>In this, revisited article I will show you everything you need to know when working with Spring WebClient and Kotlin coroutines.</p>
<p>The post <a href="https://blog.codersee.com/spring-webclient-with-kotlin-coroutines/">Spring WebClient 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[
<h2 class="wp-block-heading" id="h-1-introduction">1. Introduction</h2>



<p>Welcome to my next article, in which I will show you everything you need to know when working with <strong>Spring WebClient and Kotlin coroutines</strong>.</p>



<p>To be on the same page- this article will be a coroutine-focused extension of my other blog post about <a href="https://blog.codersee.com/spring-5-webclient-with-spring-boot/" target="_blank" rel="noopener">Spring 5 WebClient with Spring Boot</a>. So, instead of duplicating the content, which works exactly the same regardless of whether we pick the Reactor or coroutines implementation, I will focus on response handling. Nevertheless, if you are interested in other features, like request bodies, headers, and filter implementation, then I recommend checking out that article, as well.</p>



<p>With that being said, let&#8217;s get to work 🙂</p>



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



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



<div style="text-align: center; width: 90%; margin-left: 5%;">
<p></p></div>


<a href="https://blog.codersee.com/spring-webclient-with-kotlin-coroutines/"><img decoding="async" src="https://blog.codersee.com/wp-content/plugins/wp-youtube-lyte/lyteCache.php?origThumbUrl=%2F%2Fi.ytimg.com%2Fvi%2FS8ie6zyJHsw%2Fhqdefault.jpg" alt="YouTube Video"></a><br /><br /></p>



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



<h2 class="wp-block-heading" id="h-2-api-specification">2. API Specification</h2>



<p>Before I show you how to implement WebClient with Kotlin coroutines, let me quickly describe the API we are about to query.</p>



<p>Basically, we will be using 3 endpoints in order to:</p>



<ul class="wp-block-list">
<li>fetch a list of all users,</li>



<li>get user by its identifier,</li>



<li>and delete a particular user by id.</li>
</ul>



<p>And to better visualize, let&#8217;s take a look at the API &#8220;contract&#8221;:</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="">GET http://localhost:8090/users 

# Example response:
[
  {
    "id": 1,
    "first_name": "Robert",
    "last_name": "Smith"
  },
  {
    "id": 2,
    "first_name": "Mary",
    "last_name": "Jones"
  }
...
]

GET http://localhost:8090/users/1

# Example successful response body:
{
  "id": 1,
  "first_name": "Robert",
  "last_name": "Smith"
}

# If the user is not found, then the API returns 404 NOT FOUND

DELETE http://localhost:8090/users/1

# Responds 204 No Content with empty body</pre>


<p>[elementor-template id=&#8221;9007393&#8243;]</p>



<h2 class="wp-block-heading" id="h-3-configure-spring-webclient">3. Configure Spring WebClient</h2>



<p>And although the Spring WebClient configuration does not differ when working with coroutines, let&#8217;s take a look at the config class:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@Configuration
class Config {

  @Bean
  fun webClient(builder: WebClient.Builder): WebClient =
      builder
        .baseUrl("http://localhost:8090")
        .build()
}
</pre>



<p>As we can see, with this config we define a new WebClient bean and instruct that a base URL for our requests is <code class="EnlighterJSRAW" data-enlighter-language="raw">http://localhost:8090</code>.</p>



<h2 class="wp-block-heading" id="h-4-implement-userresponse">4. Implement UserResponse</h2>



<p>Nextly, let&#8217;s add the UserResponse class to our codebase:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">data class UserResponse(
  val id: Long,
  @JsonProperty("first_name") val firstName: String,
  @JsonProperty("last_name") val lastName: String
)
</pre>



<p>And this class will be responsible for serializing responses from the external API.</p>



<h2 class="wp-block-heading" id="h-5-spring-webclient-with-kotlin-coroutines">5. Spring WebClient With Kotlin Coroutines</h2>



<p>As the next step, we can finally implement the UserApiService and start querying.</p>



<p>Of course, let&#8217;s inject the WebClient before heading to the next steps:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@Service
class UserApiService(
  private val webClient: WebClient
) {

}
</pre>



<h3 class="wp-block-heading" id="h-5-1-obtaining-response-body-with-awaitbody">5.1 Obtaining Response Body With awaitBody()</h3>



<p>Firstly, let&#8217;s learn how to fetch a list of users:</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 findAllUsers(): List&lt;UserResponse> =
  webClient.get()
    .uri("/users")
    .retrieve()
    .awaitBody&lt;List&lt;UserResponse>>()</pre>



<p>Well, we have to remember that the <code class="EnlighterJSRAW" data-enlighter-language="generic">awaitBody()</code> is a suspend function- and that&#8217;s the reason why our function is defined as a suspend, as well. Moreover, we have to define the type of response we are expecting (<code class="EnlighterJSRAW" data-enlighter-language="generic">List&lt;UserResponse&gt;</code> in our case). Of course, it&#8217;s optional if we already defined a return type explicitly for our function.</p>



<h3 class="wp-block-heading" id="h-5-2-awaitbodyornull">5.2 awaitBodyOrNull()</h3>



<p>Additionally, we can make use of another variant, <code class="EnlighterJSRAW" data-enlighter-language="raw">awaitBodyOrNull()</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="">suspend fun findUserById(id: Long): UserResponse? =
  webClient.get()
    .uri("/users/$id")
    .retrieve()
    .awaitBodyOrNull&lt;UserResponse>()</pre>



<p>This time, if the Mono completes without a value, a null is returned.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Nevertheless, please keep in mind that by default when using retrieve() 4xx and 5xx responses result in a WebClientResponseException. Later, I will show you how to customize this behavior using onStatus().</p>
</blockquote>



<h3 class="wp-block-heading" id="h-5-3-return-user-list-as-a-flow">5.3 Return User List As A Flow</h3>



<p>As the next step, let&#8217;s learn how to transform our publisher into Flow:</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 findAllUsersFlow(): Flow&lt;UserResponse> =
  webClient.get()
    .uri("/users")
    .retrieve()
    .bodyToFlow&lt;UserResponse>()</pre>



<p>This time, instead of converting users&#8217; responses into a List, we invoke the <code class="EnlighterJSRAW" data-enlighter-language="raw">.bodyToFlow&lt;UserResponse&gt;()</code>, which underneath transforms a <code class="EnlighterJSRAW" data-enlighter-language="raw">Flux&lt;UserResponse&gt;</code> into the Flow.</p>



<h3 class="wp-block-heading" id="h-5-4-webclient-retrieve-vs-exchange">5.4 WebClient retrieve vs exchange</h3>



<p>Well, although I promised not to cover things twice, I believe it&#8217;s important to say a couple of words here.</p>



<p>The main difference between <code class="EnlighterJSRAW" data-enlighter-language="raw">retrieve()</code> and <code class="EnlighterJSRAW" data-enlighter-language="raw">exchange()</code> methods is that the <code class="EnlighterJSRAW" data-enlighter-language="raw">exchange()</code> returns additional HTTP information, like headers and status. <strong>Nevertheless</strong>, when using it, <strong>it is our responsibility to handle all response cases to avoid memory leaks!&nbsp;</strong>And because of that, the <code class="EnlighterJSRAW" data-enlighter-language="raw">exchange()</code> was deprecated in Spring 5.3 and we should rather use <code class="EnlighterJSRAW" data-enlighter-language="raw">exchangeToMono(Function)</code> and <code class="EnlighterJSRAW" data-enlighter-language="raw">exchangeToFlux(Function)</code>(of course, if the <code class="EnlighterJSRAW" data-enlighter-language="raw">retrieve()</code> is not sufficient for our needs).</p>



<p>When working with Spring WebClient and Kotlin coroutines, we can make use of the <code class="EnlighterJSRAW" data-enlighter-language="raw">awaitExchange</code> and <code class="EnlighterJSRAW" data-enlighter-language="raw">exchangeToFlow</code> functions.</p>



<h3 class="wp-block-heading" id="h-5-5-awaitexchange">5.5 awaitExchange()</h3>



<p>With that being said, let&#8217;s implement another function using <strong>awaitExchange</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="">suspend fun findAllUsersUsingExchange(): List&lt;UserResponse> =
  webClient.get()
    .uri("/users")
    .awaitExchange { clientResponse ->
      val headers = clientResponse.headers().asHttpHeaders()
      logger.info("Received response from users API. Headers: $headers")
      clientResponse.awaitBody&lt;List&lt;UserResponse>>()
    }</pre>



<p>As we can see, this function can be a good choice if we would like to access additional response information (like headers in our case) and perform additional logic based on them.</p>



<h3 class="wp-block-heading" id="h-5-6-exchangetoflow">5.6 exchangeToFlow()</h3>



<p>On the other hand, if we would like to return the Flow, then we must choose <strong>exchangeToFlow</strong> variant:</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 findAllUsersFlowUsingExchange(): Flow&lt;UserResponse> =
  webClient.get()
    .uri("/users")
    .exchangeToFlow { clientResponse ->
      val headers = clientResponse.headers().asHttpHeaders()
      logger.info("Received response from users API. Headers: $headers")
      clientResponse.bodyToFlow&lt;UserResponse>()
    }</pre>



<h3 class="wp-block-heading" id="h-5-7-work-with-bodiless">5.7 Work With Bodiless</h3>



<p>Sometimes, the REST API endpoints do not return any response body, which is usually indicated by the 204 No Content status code.</p>



<p>In such a case we can either choose awaitBody and pass the Unit type, or awaitBodilessEntity:</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 deleteUserById(id: Long): Unit =
  webClient.delete()
    .uri("/users/$id")
    .retrieve()
    .awaitBody&lt;Unit>()

suspend fun deleteUserById(id: Long): ResponseEntity&lt;Void> =
  webClient.delete()
    .uri("/users/$id")
    .retrieve()
    .awaitBodilessEntity()</pre>



<p>As we can see, depending on our needs we can either simply return the Unit or the ResponseEntity instance.</p>



<h3 class="wp-block-heading" id="h-5-8-handling-errors">5.8 Handling Errors</h3>



<p>And although the last example does not differ when working with WebClient and Kotlin coroutines, I feel obliged to show how we can handle API error status codes.</p>



<p>As I mentioned previously, by default all 4xx and 5xx response status codes cause WebClientResponseException to be thrown. And if we would like to alter this behavior, Spring WebFlux comes with a useful <code class="EnlighterJSRAW" data-enlighter-language="raw">onStatus</code> method for 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="">suspend fun findUserByIdNotFoundHandling(id: Long): UserResponse =
  webClient.get()
    .uri("/users/$id")
    .retrieve()
    .onStatus({ responseStatus ->
      responseStatus == HttpStatus.NOT_FOUND
    }) { throw ResponseStatusException(HttpStatus.NOT_FOUND) }
    .awaitBody&lt;UserResponse>()</pre>



<p>The <code class="EnlighterJSRAW" data-enlighter-language="raw">onStatus</code> takes two parameters: the predicate and a function. In our example, each 404 Not Found response from the external API will be translated to ResponseStatusException with the same HttpStatus.</p>



<h2 class="wp-block-heading" id="h-6-spring-webclient-with-kotlin-coroutines-summary">6. Spring WebClient With Kotlin Coroutines Summary</h2>



<p>And that&#8217;s all for this article. Together, we&#8217;ve learned how easily we can implement a Spring WebClient with Kotlin coroutines using built-in features.</p>



<p>As always, you can find the example source code in this <a href="https://github.com/codersee-blog/spring-webclient-kotlin-coroutines" target="_blank" rel="noopener">GitHub repository</a> and I will be thankful if you would like to share some feedback with me right here, in the comments section.</p>
<p>The post <a href="https://blog.codersee.com/spring-webclient-with-kotlin-coroutines/">Spring WebClient 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-webclient-with-kotlin-coroutines/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-02-24 04:54:19 by W3 Total Cache
-->