{"id":3088,"date":"2023-12-04T11:52:27","date_gmt":"2023-12-04T19:52:27","guid":{"rendered":"https:\/\/www.couchbase.com\/blog\/spring-webclient-429-ratelimit-errors\/"},"modified":"2023-12-04T11:52:27","modified_gmt":"2023-12-04T19:52:27","slug":"spring-webclient-429-ratelimit-errors","status":"publish","type":"post","link":"https:\/\/www.couchbase.com\/blog\/spring-webclient-429-ratelimit-errors\/","title":{"rendered":"Manage 429 RateLimit Errors with Spring WebClient"},"content":{"rendered":"\n<p><span>I have recently taken over the maintenance of a <a href=\"https:\/\/spring.io\/projects\/spring-boot\/\">Spring Boot<\/a> project. This project has some <em>RateLimit <\/em>errors in the logs when the app was contacting a remote REST API. Turns out that this app was also using the synchronous, blocking <em>RestTemplate<\/em> client to make the API calls, instead of the newer Spring <em>WebClient<\/em>, that happens to be using the Reactor API under the hood.<\/span><\/p>\n\n\n\n<p><span>And you know what is great about Reactor and using Reactive APIs in general? It makes programming with Data Stream so easy. Which also means it makes retry strategies implementation easier.<\/span><\/p>\n\n\n\n<p><span>Let&#8217;s talk about the error in the log first. What I got looked like this:<\/span><\/p>\n\n\n<p>[crayon nums=&#8221;false&#8221; nums-toggle=&#8221;false&#8221; lang=&#8221;default&#8221; decode=&#8221;true&#8221;]org.springframework.web.client.HttpClientErrorException$TooManyRequests: 429 Too Many Requests: [{&#8220;response_type&#8221;:&#8221;ERROR&#8221;,&#8221;message&#8221;:&#8221;Number of requests has exceeded the 1 minute limit&#8221;}]<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0at org.springframework.web.client.HttpClientErrorException.create(HttpClientErrorException.java:137) ~[spring-web-5.2.9.RELEASE.jar!\/:5.2.9.RELEASE]<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:184) ~[spring-web-5.2.9.RELEASE.jar!\/:5.2.9.RELEASE]<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:125) ~[spring-web-5.2.9.RELEASE.jar!\/:5.2.9.RELEASE]<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0at org.springframework.web.client.ResponseErrorHandler.handleError(ResponseErrorHandler.java:63) ~[spring-web-5.2.9.RELEASE.jar!\/:5.2.9.RELEASE]<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:782) ~[spring-web-5.2.9.RELEASE.jar!\/:5.2.9.RELEASE]<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:740) ~[spring-web-5.2.9.RELEASE.jar!\/:5.2.9.RELEASE]<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:674) ~[spring-web-5.2.9.RELEASE.jar!\/:5.2.9.RELEASE]<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0at org.springframework.web.client.RestTemplate.getForObject(RestTemplate.java:315) ~[spring-web-5.2.9.RELEASE.jar!\/:5.2.9.RELEASE]<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0at com.couchbase.training.couchlms.repository.LmsRepository.getCourseModules(LmsRepository.java:102) ~[classes!\/:0.0.40-SNAPSHOT]<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0at com.couchbase.training.couchlms.services.LmsProcessor.processCourseModules(LmsProcessor.java:147) [classes!\/:0.0.40-SNAPSHOT]<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0at com.couchbase.training.couchlms.services.LmsProcessor.processCourses(LmsProcessor.java:91) [classes!\/:0.0.40-SNAPSHOT]<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0at com.couchbase.training.couchlms.config.SchedulingConfig.scheduledCoursesPuller(SchedulingConfig.java:45) [classes!\/:0.0.40-SNAPSHOT]<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_252]<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_252]<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_252]<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_252]<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:84) [spring-context-5.2.9.RELEASE.jar!\/:5.2.9.RELEASE]<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54) [spring-context-5.2.9.RELEASE.jar!\/:5.2.9.RELEASE]<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [na:1.8.0_252]<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308) [na:1.8.0_252]<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:180) [na:1.8.0_252]<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294) [na:1.8.0_252]<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_252]<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_252]<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0at java.lang.Thread.run(Thread.java:748) [na:1.8.0_252][\/crayon]<\/p>\n\n\n\n<p><span>As usual, the interesting bit in stack traces is at the top. The error is <\/span><strong>429 Too Many Requests<\/strong><span>, and the message says there is a <strong>1 minute rate limit<\/strong>. Decomposing this, the HTTP status<\/span>\u00a0<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Status\/429\"><span>code returned is 429<\/span><\/a><span>. It&#8217;s a Rate Limit error, meaning the API tells the caller it has sent too many request. This can usually be solved by waiting a bit, and you might even have a <em>Retry-after<\/em>\u00a0header in the response telling you how long you have to wait. <\/span><\/p>\n\n\n\n<p><span>Let&#8217;s see how we can get that information with Spring&#8217;s WebClient:<\/span><\/p>\n\n\n<p>[crayon nums=&#8221;false&#8221; lang=&#8221;java&#8221; decode=&#8221;true&#8221;].uri(ub -&gt; ub.pathSegment(uri).queryParams(queryParams).build())<br \/>\n.retrieve()<br \/>\n.onStatus(<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0HttpStatus.TOO_MANY_REQUESTS::equals,<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0response -&gt; {<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0List&lt;String&gt; header = response.headers().header(&#8220;Retry-After&#8221;);<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0Integer delayInSeconds;<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0if (!header.isEmpty()) {<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0delayInSeconds = Integer.valueOf(header.get(0));<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0} else {<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0delayInSeconds = 60;<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0return response.bodyToMono(String.class).map(msg -&gt; new RateLimitException(msg, delayInSeconds));<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0})<br \/>\n.bodyToMono(String.class)[\/crayon]<\/p>\n\n\n\n<p>This code is sending a <em>GET<\/em> request. The WebClient allows us to take a look at the request&#8217;s response and react appropriately thanks to the <em>onStatus<\/em> method. The first parameter is a boolean used to filter on returned HTTP Status code. Here, when the status code is 429, we do something.<\/p>\n\n\n\n<p><span>We take a look at the <em>Response<\/em> headers, look if there is a <em>Retry-After<\/em> header, if so, we initialize the <em>delayInSeconds<\/em> variable with it, if not, we set a 60 default value. Then we can send back the <em>Response<\/em> body mapped to a <em>RateLimitException<\/em>. The body content will be used as an error message and the delayInSeconds will be available in a separate field. Take a look at the Exception code for more details:<\/span><\/p>\n\n\n<p>[crayon nums=&#8221;false&#8221; lang=&#8221;default&#8221; decode=&#8221;true&#8221;]import java.time.Duration;<br \/>\npublic class RateLimitException extends Throwable {<br \/>\n\u00a0\u00a0\u00a0\u00a0private int retryAfterDelay = 60;<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0public RateLimitException(String message) {<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0super(message);<br \/>\n\u00a0\u00a0\u00a0\u00a0}<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0public RateLimitException(String message, int retryAfterDelay) {<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0super(message); this.retryAfterDelay = retryAfterDelay;<br \/>\n\u00a0\u00a0\u00a0\u00a0}<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0public int getRetryAfterDelay() {<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0return retryAfterDelay;<br \/>\n\u00a0\u00a0\u00a0\u00a0}<\/p>\n<p>\u00a0\u00a0\u00a0\u00a0public Duration getRetryAfterDelayDuration() {<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0return Duration.ofSeconds(retryAfterDelay);<br \/>\n\u00a0\u00a0\u00a0\u00a0}<br \/>\n}[\/crayon]<\/p>\n\n\n\n<p><span>So what needs to be done is catching this specific error and then retrying after the given duration. Reactor makes that easy by providing <em>Retries<\/em> strategies. All you need to do is call the <em>retryWhen<\/em> method:<\/span><\/p>\n\n\n<p>[crayon nums=&#8221;false&#8221; lang=&#8221;java&#8221; decode=&#8221;true&#8221;].bodyToMono(String.class)<br \/>\n.retryWhen(Retry.withThrowable(throwableFlux -&gt; {<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0return throwableFlux.filter(t -&gt; t instanceof RateLimitException).map(t -&gt; {<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0RateLimitException rle = (RateLimitException) t;<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0return Retry.fixedDelay(1, rle.getRetryAfterDelayDuration());<br \/>\n\u00a0\u00a0\u00a0\u00a0});[\/crayon]<\/p>\n\n\n\n<p><span>There are different <em>Retry<\/em> methods, here we can use the <em>withThrowable()<\/em> builder. It gives a Flux that should contain the <em>RateLimitException<\/em>. So we start by applying a filter to make sure of that. Then we map that exception to the actual Retry object. Here it&#8217;s the <em>Retry.fixedDelay<\/em> strategy, taking a maximum attempts and duration as parameters. The duration comes from the <em>RateLimitException<\/em> that was thrown earlier.<\/span><\/p>\n\n\n\n<p><span>With that, each time a request returns a 429, the client will wait the appropriate time until it retries. And it was much easier to implement with Reactor than using <em>try\/catch<\/em> with Spring&#8217;s <em>RestTemplate<\/em>. I know Reactive programming might be a little intimidating at first, but it&#8217;s a great way to manage data streams like HTTP requests and responses, or to manage connections to databases that support reactive programming, like <a href=\"https:\/\/couchbase.com\">Couchbase<\/a>.<\/span><\/p>\n\n\n\n<p>Want some more help and ideas?<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/www.couchbase.com\/blog\/couchbase-on-discord\/\">Join our Discord server to chat<\/a><\/li>\n\n\n<li><a href=\"https:\/\/www.couchbase.com\/blog\/introducing-the-couchbase-community-hub\/\">Join our Community Hub to connect<\/a><\/li>\n\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>I have recently taken over the maintenance of a Spring Boot project. This project has some RateLimit errors in the logs when the app was contacting a remote REST API. Turns out that this app was also using the synchronous, blocking RestTemplate client to make the API calls, instead of the newer Spring WebClient, that [&hellip;]<\/p>\n","protected":false},"author":49,"featured_media":3087,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"inline_featured_image":false,"footnotes":""},"categories":[127,695,64],"tags":[48,254],"ppma_author":[110],"class_list":["post-3088","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-application-design","category-spring-boot","category-tools-sdks","tag-error-handling","tag-spring-boot"],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v27.6 (Yoast SEO v27.6) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Manage 429 RateLimit Errors with Spring WebClient - The Couchbase Blog<\/title>\n<meta name=\"description\" content=\"As usual, the interesting bit in stack traces is at the top. The error is 429 Too Many Requests, and the message says there is a 1 minute rate limit.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.couchbase.com\/blog\/spring-webclient-429-ratelimit-errors\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Manage 429 RateLimit Errors with Spring WebClient\" \/>\n<meta property=\"og:description\" content=\"As usual, the interesting bit in stack traces is at the top. The error is 429 Too Many Requests, and the message says there is a 1 minute rate limit.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.couchbase.com\/blog\/spring-webclient-429-ratelimit-errors\/\" \/>\n<meta property=\"og:site_name\" content=\"The Couchbase Blog\" \/>\n<meta property=\"article:published_time\" content=\"2023-12-04T19:52:27+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/5\/2026\/05\/spring-boot-ratelimit-error.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"628\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Laurent Doguin\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@ldoguin\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Laurent Doguin\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"3 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/spring-webclient-429-ratelimit-errors\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/spring-webclient-429-ratelimit-errors\\\/\"},\"author\":{\"name\":\"Laurent Doguin\",\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/#\\\/schema\\\/person\\\/c0aa9b8f1ed51b7a9e2f7cb755994a5e\"},\"headline\":\"Manage 429 RateLimit Errors with Spring WebClient\",\"datePublished\":\"2023-12-04T19:52:27+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/spring-webclient-429-ratelimit-errors\\\/\"},\"wordCount\":993,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/spring-webclient-429-ratelimit-errors\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/wp-content\\\/uploads\\\/sites\\\/5\\\/2026\\\/05\\\/spring-boot-ratelimit-error.jpg\",\"keywords\":[\"Error handling\",\"spring-boot\"],\"articleSection\":[\"Application Design\",\"Spring Boot\",\"Tools &amp; SDKs\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/spring-webclient-429-ratelimit-errors\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/spring-webclient-429-ratelimit-errors\\\/\",\"url\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/spring-webclient-429-ratelimit-errors\\\/\",\"name\":\"Manage 429 RateLimit Errors with Spring WebClient - The Couchbase Blog\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/spring-webclient-429-ratelimit-errors\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/spring-webclient-429-ratelimit-errors\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/wp-content\\\/uploads\\\/sites\\\/5\\\/2026\\\/05\\\/spring-boot-ratelimit-error.jpg\",\"datePublished\":\"2023-12-04T19:52:27+00:00\",\"description\":\"As usual, the interesting bit in stack traces is at the top. The error is 429 Too Many Requests, and the message says there is a 1 minute rate limit.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/spring-webclient-429-ratelimit-errors\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/spring-webclient-429-ratelimit-errors\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/spring-webclient-429-ratelimit-errors\\\/#primaryimage\",\"url\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/wp-content\\\/uploads\\\/sites\\\/5\\\/2026\\\/05\\\/spring-boot-ratelimit-error.jpg\",\"contentUrl\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/wp-content\\\/uploads\\\/sites\\\/5\\\/2026\\\/05\\\/spring-boot-ratelimit-error.jpg\",\"width\":1200,\"height\":628},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/spring-webclient-429-ratelimit-errors\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Manage 429 RateLimit Errors with Spring WebClient\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/\",\"name\":\"The Couchbase Blog\",\"description\":\"Couchbase, the NoSQL Database\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/#organization\",\"name\":\"The Couchbase Blog\",\"url\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/wp-content\\\/uploads\\\/sites\\\/5\\\/2026\\\/06\\\/logo.svg\",\"contentUrl\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/wp-content\\\/uploads\\\/sites\\\/5\\\/2026\\\/06\\\/logo.svg\",\"width\":\"1024\",\"height\":\"1024\",\"caption\":\"The Couchbase Blog\"},\"image\":{\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/#\\\/schema\\\/logo\\\/image\\\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/#\\\/schema\\\/person\\\/c0aa9b8f1ed51b7a9e2f7cb755994a5e\",\"name\":\"Laurent Doguin\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/b8c466908092b46634af916b6921f30187a051e4367ded7ac9b1a3f2c5692fd2?s=96&d=mm&r=g12929ce99397769f362b7a90d6b85071\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/b8c466908092b46634af916b6921f30187a051e4367ded7ac9b1a3f2c5692fd2?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/b8c466908092b46634af916b6921f30187a051e4367ded7ac9b1a3f2c5692fd2?s=96&d=mm&r=g\",\"caption\":\"Laurent Doguin\"},\"description\":\"Laurent is a nerdy metal head who lives in Paris. He mostly writes code in Java and structured text in AsciiDoc, and often talks about data, reactive programming and other buzzwordy stuff. He is also a former Developer Advocate for Clever Cloud and Nuxeo where he devoted his time and expertise to helping those communities grow bigger and stronger. He now runs Developer Relations at Couchbase.\",\"sameAs\":[\"https:\\\/\\\/x.com\\\/ldoguin\"],\"honorificPrefix\":\"Mr\",\"birthDate\":\"1985-06-07\",\"gender\":\"male\",\"award\":[\"Devoxx Champion\",\"Couchbase Legend\"],\"knowsAbout\":[\"Java\"],\"knowsLanguage\":[\"English\",\"French\"],\"jobTitle\":\"Director Developer Relation & Strategy\",\"worksFor\":\"Couchbase\",\"url\":\"https:\\\/\\\/www.couchbase.com\\\/blog\\\/author\\\/laurent-doguin\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Manage 429 RateLimit Errors with Spring WebClient - The Couchbase Blog","description":"As usual, the interesting bit in stack traces is at the top. The error is 429 Too Many Requests, and the message says there is a 1 minute rate limit.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.couchbase.com\/blog\/spring-webclient-429-ratelimit-errors\/","og_locale":"en_US","og_type":"article","og_title":"Manage 429 RateLimit Errors with Spring WebClient","og_description":"As usual, the interesting bit in stack traces is at the top. The error is 429 Too Many Requests, and the message says there is a 1 minute rate limit.","og_url":"https:\/\/www.couchbase.com\/blog\/spring-webclient-429-ratelimit-errors\/","og_site_name":"The Couchbase Blog","article_published_time":"2023-12-04T19:52:27+00:00","og_image":[{"width":1200,"height":628,"url":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/5\/2026\/05\/spring-boot-ratelimit-error.jpg","type":"image\/jpeg"}],"author":"Laurent Doguin","twitter_card":"summary_large_image","twitter_creator":"@ldoguin","twitter_misc":{"Written by":"Laurent Doguin","Est. reading time":"3 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.couchbase.com\/blog\/spring-webclient-429-ratelimit-errors\/#article","isPartOf":{"@id":"https:\/\/www.couchbase.com\/blog\/spring-webclient-429-ratelimit-errors\/"},"author":{"name":"Laurent Doguin","@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/c0aa9b8f1ed51b7a9e2f7cb755994a5e"},"headline":"Manage 429 RateLimit Errors with Spring WebClient","datePublished":"2023-12-04T19:52:27+00:00","mainEntityOfPage":{"@id":"https:\/\/www.couchbase.com\/blog\/spring-webclient-429-ratelimit-errors\/"},"wordCount":993,"commentCount":0,"publisher":{"@id":"https:\/\/www.couchbase.com\/blog\/#organization"},"image":{"@id":"https:\/\/www.couchbase.com\/blog\/spring-webclient-429-ratelimit-errors\/#primaryimage"},"thumbnailUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/5\/2026\/05\/spring-boot-ratelimit-error.jpg","keywords":["Error handling","spring-boot"],"articleSection":["Application Design","Spring Boot","Tools &amp; SDKs"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.couchbase.com\/blog\/spring-webclient-429-ratelimit-errors\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.couchbase.com\/blog\/spring-webclient-429-ratelimit-errors\/","url":"https:\/\/www.couchbase.com\/blog\/spring-webclient-429-ratelimit-errors\/","name":"Manage 429 RateLimit Errors with Spring WebClient - The Couchbase Blog","isPartOf":{"@id":"https:\/\/www.couchbase.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.couchbase.com\/blog\/spring-webclient-429-ratelimit-errors\/#primaryimage"},"image":{"@id":"https:\/\/www.couchbase.com\/blog\/spring-webclient-429-ratelimit-errors\/#primaryimage"},"thumbnailUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/5\/2026\/05\/spring-boot-ratelimit-error.jpg","datePublished":"2023-12-04T19:52:27+00:00","description":"As usual, the interesting bit in stack traces is at the top. The error is 429 Too Many Requests, and the message says there is a 1 minute rate limit.","breadcrumb":{"@id":"https:\/\/www.couchbase.com\/blog\/spring-webclient-429-ratelimit-errors\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.couchbase.com\/blog\/spring-webclient-429-ratelimit-errors\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.couchbase.com\/blog\/spring-webclient-429-ratelimit-errors\/#primaryimage","url":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/5\/2026\/05\/spring-boot-ratelimit-error.jpg","contentUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/5\/2026\/05\/spring-boot-ratelimit-error.jpg","width":1200,"height":628},{"@type":"BreadcrumbList","@id":"https:\/\/www.couchbase.com\/blog\/spring-webclient-429-ratelimit-errors\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.couchbase.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Manage 429 RateLimit Errors with Spring WebClient"}]},{"@type":"WebSite","@id":"https:\/\/www.couchbase.com\/blog\/#website","url":"https:\/\/www.couchbase.com\/blog\/","name":"The Couchbase Blog","description":"Couchbase, the NoSQL Database","publisher":{"@id":"https:\/\/www.couchbase.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.couchbase.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/www.couchbase.com\/blog\/#organization","name":"The Couchbase Blog","url":"https:\/\/www.couchbase.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/5\/2026\/06\/logo.svg","contentUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/5\/2026\/06\/logo.svg","width":"1024","height":"1024","caption":"The Couchbase Blog"},"image":{"@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/c0aa9b8f1ed51b7a9e2f7cb755994a5e","name":"Laurent Doguin","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/b8c466908092b46634af916b6921f30187a051e4367ded7ac9b1a3f2c5692fd2?s=96&d=mm&r=g12929ce99397769f362b7a90d6b85071","url":"https:\/\/secure.gravatar.com\/avatar\/b8c466908092b46634af916b6921f30187a051e4367ded7ac9b1a3f2c5692fd2?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/b8c466908092b46634af916b6921f30187a051e4367ded7ac9b1a3f2c5692fd2?s=96&d=mm&r=g","caption":"Laurent Doguin"},"description":"Laurent is a nerdy metal head who lives in Paris. He mostly writes code in Java and structured text in AsciiDoc, and often talks about data, reactive programming and other buzzwordy stuff. He is also a former Developer Advocate for Clever Cloud and Nuxeo where he devoted his time and expertise to helping those communities grow bigger and stronger. He now runs Developer Relations at Couchbase.","sameAs":["https:\/\/x.com\/ldoguin"],"honorificPrefix":"Mr","birthDate":"1985-06-07","gender":"male","award":["Devoxx Champion","Couchbase Legend"],"knowsAbout":["Java"],"knowsLanguage":["English","French"],"jobTitle":"Director Developer Relation & Strategy","worksFor":"Couchbase","url":"https:\/\/www.couchbase.com\/blog\/author\/laurent-doguin\/"}]}},"acf":[],"authors":[{"term_id":110,"user_id":49,"is_guest":0,"slug":"laurent-doguin","display_name":"Laurent Doguin","avatar_url":"https:\/\/secure.gravatar.com\/avatar\/b8c466908092b46634af916b6921f30187a051e4367ded7ac9b1a3f2c5692fd2?s=96&d=mm&r=g","0":null,"1":"","2":"","3":"","4":"","5":"","6":"","7":"","8":""}],"_links":{"self":[{"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/posts\/3088","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/users\/49"}],"replies":[{"embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/comments?post=3088"}],"version-history":[{"count":0,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/posts\/3088\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/media\/3087"}],"wp:attachment":[{"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/media?parent=3088"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/categories?post=3088"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/tags?post=3088"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/ppma_author?post=3088"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}