PageSourceSearch

https://armeria.dev/assets/js/63f88017.455d294d.js

js armeria.dev collected 2026-10-03 23:22:34 UTC 36,415 bytes, 1 lines download raw bytes

1"use strict";(self.webpackChunkarmeria_site=self.webpackChunkarmeria_site||[]).push([["1997"],{30244(e,t,r){r.r(t),r.d(t,{metadata:()=>i,default:()=>d,frontMatter:()=>o,contentTitle:()=>c,toc:()=>s,assets:()=>l});var i=JSON.parse('{"id":"client/retry","title":"Automatic retry","description":"When a client gets an error response, it might want to retry the request depending on the response.","source":"@site/src/content/docs/client/retry.mdx","sourceDirName":"client","slug":"/client/retry","permalink":"/docs/client/retry","draft":false,"unlisted":false,"editUrl":"https://github.com/line/armeria/edit/main/site/src/content/docs/client/retry.mdx","tags":[],"version":"current","frontMatter":{},"sidebar":"docsSidebar","previous":{"title":"Overriding client timeouts","permalink":"/docs/client/timeouts"},"next":{"title":"Circuit breaker","permalink":"/docs/client/circuit-breaker"}}'),a=r(74848),n=r(28453);let o={},c="Automatic retry",l={},s=[{value:"<code>RetryingClient</code>",id:"retryingclient",level:2},{value:"<code>RetryRule</code>",id:"retryrule",level:2},{value:"<code>Backoff</code>",id:"backoff",level:2},{value:"<code>maxTotalAttempts</code> vs per-Backoff <code>maxAttempts</code>",id:"maxtotalattempts-vs-per-backoff-maxattempts",level:2},{value:"Per-attempt timeout",id:"per-attempt-timeout",level:2},{value:"<code>RetryingClient</code> with logging",id:"retryingclient-with-logging",level:2},{value:"<code>RetryingClient</code> with circuit breaker",id:"retryingclient-with-circuit-breaker",level:2},{value:"See also",id:"see-also",level:2}];function m(e){let t={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",header:"header",li:"li",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,n.R)(),...e.components};return(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(t.header,{children:(0,a.jsx)(t.h1,{id:"automatic-retry",children:"Automatic retry"})}),"\n",(0,a.jsxs)(t.p,{children:["When a client gets an error response, it might want to retry the request depending on the response.\nThis can be accomplished using a ",(0,a.jsx)(t.a,{href:"/docs/client/decorator",children:"decorator"}),", and Armeria provides the following\nimplementations out-of-the box."]}),"\n",(0,a.jsxs)(t.ul,{children:["\n",(0,a.jsx)(t.li,{children:(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry/RetryingClient.html",children:"RetryingClient"})}),"\n",(0,a.jsx)(t.li,{children:(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry/RetryingRpcClient.html",children:"RetryingRpcClient"})}),"\n"]}),"\n",(0,a.jsxs)(t.p,{children:["Both behave the same except for the different request and response types.\nSo, let's find out what we can do with ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry/RetryingClient.html",children:"RetryingClient"}),"."]}),"\n",(0,a.jsx)(t.h2,{id:"retryingclient",children:(0,a.jsx)(t.code,{children:"RetryingClient"})}),"\n",(0,a.jsxs)(t.p,{children:["You can just use the ",(0,a.jsx)(t.code,{children:"decorator()"})," method in ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/ClientBuilder.html",children:"ClientBuilder"})," or ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/WebClientBuilder.html",children:"WebClientBuilder"})," to build a\n",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry/RetryingClient.html",children:"RetryingClient"}),". For example:"]}),"\n",(0,a.jsx)(t.pre,{children:(0,a.jsx)(t.code,{className:"language-java",children:'import com.linecorp.armeria.client.WebClient;\nimport com.linecorp.armeria.client.retry.RetryingClient;\nimport com.linecorp.armeria.client.retry.RetryRule;\nimport com.linecorp.armeria.common.AggregatedHttpResponse;\n\nRetryRule rule = RetryRule.failsafe();\nWebClient client = WebClient.builder("http://example.com/hello")\n                            .decorator(
1RetryingClient.newDecorator(rule))\n                            .build();\n\nAggregatedHttpResponse res = client.execute(...).aggregate().join();\n'})}),"\n",(0,a.jsxs)(t.p,{children:["That's it. The client will keep attempting until it succeeds or the number of attempts exceeds the maximum\nnumber of total attempts. You can configure the ",(0,a.jsx)(t.code,{children:"maxTotalAttempts"})," when making the decorator using\n",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry/RetryingClient.html#newDecorator(com.linecorp.armeria.client.retry.RetryRule,int)",children:"RetryingClient#newDecorator(RetryRule,int)"}),". Meanwhile, the ",(0,a.jsx)(t.code,{children:"rule"})," will decide to\nretry depending on the response. In this case, the client retries when it receives ",(0,a.jsx)(t.code,{children:"5xx"})," response error or\nan exception is raised."]}),"\n",(0,a.jsx)(t.h2,{id:"retryrule",children:(0,a.jsx)(t.code,{children:"RetryRule"})}),"\n",(0,a.jsxs)(t.p,{children:["You can fluently build your own ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry/RetryRule.html",children:"RetryRule"}),"."]}),"\n",(0,a.jsx)(t.pre,{children:(0,a.jsx)(t.code,{className:"language-java",children:"import com.linecorp.armeria.client.ResponseTimeoutException;\nimport com.linecorp.armeria.common.HttpStatus;\n\nBackoff myBackoff = ...;\nRetryRule.of(RetryRule.builder().onUnProcessed().thenBackoff(myBackoff),\n             RetryRule.builder().onException(ResponseTimeoutException.class).thenBackoff(),\n             RetryRule.builder().onStatus(HttpStatus.TOO_MANY_REQUESTS).thenNoRetry())\n"})}),"\n",(0,a.jsxs)(t.p,{children:["Or you can customize the ",(0,a.jsx)(t.strong,{children:"rule"})," by implementing ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry/RetryRule.html",children:"RetryRule"}),"."]}),"\n",(0,a.jsx)(t.pre,{children:(0,a.jsx)(t.code,{className:"language-java",children:"import com.linecorp.armeria.client.ClientRequestContext;\nimport com.linecorp.armeria.client.UnprocessedRequestException;\nimport com.linecorp.armeria.client.retry.Backoff;\nimport com.linecorp.armeria.client.retry.RetryDecision;\nimport com.linecorp.armeria.common.ResponseHeaders;\nimport com.linecorp.armeria.common.logging.RequestLogProperty;\n\nnew RetryRule() {\n    Backoff backoff = Backoff.ofDefault();\n\n    @Override\n    public CompletionStage<RetryDecision> shouldRetry(ClientRequestContext ctx,\n                                                      @Nullable Throwable cause) {\n        if (cause != null) {\n            if (cause instanceof ResponseTimeoutException ||\n                cause instanceof UnprocessedRequestException) {\n                // The response timed out or the request has not been handled\n                // by the server.\n                return UnmodifiableFuture.completedFuture(RetryDecision.retry(backoff));\n            }\n        }\n\n        ResponseHeaders responseHeaders = ctx.log().ensureAvailable(RequestLogProperty.RESPONSE_HEADERS)\n                                             .responseHeaders();\n        if (responseHeaders.status() == HttpStatus.TOO_MANY_REQUESTS) {\n            return UnmodifiableFuture.completedFuture(RetryDecision.stop());\n        }\n\n        // Return 'next()' to lookup other rules.\n        return UnmodifiableFuture.completedFuture(RetryDecision.next());\n    }\n};\n"})}),"\n",(0,a.jsxs)(t.p,{children:["This will retry when one of ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/ResponseTimeoutException.html",children:"ResponseTimeoutException"})," and ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/UnprocessedRequestException.html",children:"UnprocessedRequestException"})," is raised.\nHowever, if the response's status is ",(0,a.jsx)(t.code,{children:"429 Too Many Requests"}),", it will stop retrying.\nFor all other cases, it will defer to the next rule if one exists."]}),"\n",(0,a.jsx)(t.admonition,{type:"warning",children:(0,a.jsxs)(t.p,{children:["We declare a ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry
1/Backoff.html",children:"Backoff"})," as a member and reuse it when a ",(0,a.jsx)(t.code,{children:"rule"})," returns it, so that we do not\nreturn a different ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry/Backoff.html",children:"Backoff"})," instance for each ",(0,a.jsx)(t.code,{children:"shouldRetry()"}),". ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry/RetryingClient.html",children:"RetryingClient"}),"\ninternally tracks the reference of the returned ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry/Backoff.html",children:"Backoff"})," and increases the counter that keeps\nthe number of attempts made so far, and resets it to 0 when the ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry/Backoff.html",children:"Backoff"})," returned by the retry rule\nis not the same as before. Therefore, it is important to return the same ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry/Backoff.html",children:"Backoff"})," instance unless\nyou decided to change your ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry/Backoff.html",children:"Backoff"})," strategy. If you do not return the same one, when the\n",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry/Backoff.html",children:"Backoff"})," yields a different delay based on the number of retries, such as an exponential backoff,\nit will not work as expected. We will take a close look into a ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry/Backoff.html",children:"Backoff"})," at the next section."]})}),"\n",(0,a.jsx)(t.admonition,{type:"tip",children:(0,a.jsxs)(t.p,{children:[(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/UnprocessedRequestException.html",children:"UnprocessedRequestException"})," literally means that the request has not been processed by the server.\nTherefore, you can safely retry the request without worrying about the idempotency of the request.\nFor more information about idempotency, please refer to\n",(0,a.jsx)(t.a,{href:"http://restcookbook.com/HTTP%20Methods/idempotency/",children:"What are idempotent and/or safe methods?"}),"."]})}),"\n",(0,a.jsxs)(t.p,{children:["You can return a different ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry/Backoff.html",children:"Backoff"})," according to the response status."]}),"\n",(0,a.jsx)(t.pre,{children:(0,a.jsx)(t.code,{className:"language-java",children:"import com.linecorp.armeria.common.HttpStatusClass;\n\nBackoff backoffOnServerErrorOrTimeout = Backoff.ofDefault();\nBackoff backoffOnConflict = Backoff.fixed(100);\nRetryRule.builder()\n         .onException(ex -> ex instanceof ResponseTimeoutException ||\n                            ex instanceof UnprocessedRequestException)\n         .thenBackoff(backoffOnServerErrorOrTimeout)\n         .orElse(RetryRule.builder()\n                          .onStatusClass(HttpStatusClass.SERVER_ERROR)\n                          .thenBackoff(backoffOnServerErrorOrTimeout))\n         .orElse(RetryRule.builder()\n                          .onStatus(HttpStatus.CONFLICT)\n                          .thenBackoff(backoffOnConflict));\n"})}),"\n",(0,a.jsxs)(t.p,{children:["If you need to determine whether you need to retry by looking into the response content, you should implement\n",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry
1/RetryRuleWithContent.html",children:"RetryRuleWithContent"})," and specify it when you create a ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/WebClient.html",children:"WebClient"}),"\nusing ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry/RetryingClientBuilder.html",children:"RetryingClientBuilder"}),":"]}),"\n",(0,a.jsx)(t.pre,{children:(0,a.jsx)(t.code,{className:"language-java",children:'import com.linecorp.armeria.client.retry.RetryRuleWithContent;\n\nRetryRuleWithContent<HttpResponse> retryRule =\n        RetryRuleWithContent\n                .<HttpResponse>builder()\n                .onException(ex -> ex instanceof ResponseTimeoutException ||\n                                   ex instanceof UnprocessedRequestException)\n                .onResponse(response -> {\n                    return response.aggregate()\n                                   .thenApply(content -> "Should I retry?".equals(content.contentUtf8()));\n                })\n                .thenBackoff(backoff);\n\n// Create a WebClient with a retry rule.\nWebClient client = WebClient\n        .builder(...)\n        .decorator(RetryingClient.builder(retryRule)\n                                 .newDecorator())\n        .build();\n\nAggregatedHttpResponse res = client.execute(...).aggregate().join();\n'})}),"\n",(0,a.jsxs)(t.admonition,{type:"tip",children:[(0,a.jsxs)(t.p,{children:["You might find the ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/common/util/Exceptions.html#peel(java.lang.Throwable)",children:"Exceptions#peel(Throwable)"})," method useful when the exception you are trying to\nhandle is wrapped by exceptions like ",(0,a.jsx)(t.code,{children:"CompletionException"})," and ",(0,a.jsx)(t.code,{children:"ExecutionException"}),":"]}),(0,a.jsx)(t.pre,{children:(0,a.jsx)(t.code,{className:"language-java",children:"import com.linecorp.armeria.common.Exceptions;\n\n@Override\npublic CompletionStage<RetryDecision> shouldRetry(ClientRequestContext ctx,\n                                                  @Nullable Throwable cause) {\n    if (cause != null) {\n        if (cause instanceof ResponseTimeoutException ||\n            cause instanceof UnprocessedRequestException) {\n            // The response timed out or the request has not been handled\n            // by the server.\n            return UnmodifiableFuture.completedFuture(backoff);\n        }\n\n        Throwable peeled = Exceptions.peel(cause);\n        if (peeled instanceof MyException) { ... }\n    }\n    ...\n}\n"})})]}),"\n",(0,a.jsx)(t.h2,{id:"backoff",children:(0,a.jsx)(t.code,{children:"Backoff"})}),"\n",(0,a.jsxs)(t.p,{children:["You can use a ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry/Backoff.html",children:"Backoff"})," to determine the delay between attempts. Armeria provides ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry/Backoff.html",children:"Backoff"}),"\nimplementations which produce the following delays out of the box:"]}),"\n",(0,a.jsxs)(t.ul,{children:["\n",(0,a.jsxs)(t.li,{children:["Fixed delay, created with ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry/Backoff.html#fixed(long)",children:"Backoff#fixed(long)"})]}),"\n",(0,a.jsxs)(t.li,{children:["Random delay, created with ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry/Backoff.html#random(long,long)",children:"Backoff#random(long,long)"})]}),"\n",(0,a.jsxs)(t.li,{children:["Exponential delay which is multiplied on each attempt, created with ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry
1/Backoff.html#exponential(long,long)",children:"Backoff#exponential(long,long)"})]}),"\n"]}),"\n",(0,a.jsxs)(t.p,{children:["Armeria provides ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry/Backoff.html#ofDefault()",children:"Backoff#ofDefault()"})," that you might use by default. It is exactly the same as:"]}),"\n",(0,a.jsx)(t.pre,{children:(0,a.jsx)(t.code,{className:"language-java",children:"Backoff.exponential(200   /* minDelayMillis */,\n                    10000 /* maxDelayMillis */,\n                    2.0   /* multiplier     */)\n       .withJitter(0.2 /* jitterRate */);\n"})}),"\n",(0,a.jsxs)(t.p,{children:["The delay starts from ",(0,a.jsx)(t.code,{children:"minDelayMillis"})," until it reaches ",(0,a.jsx)(t.code,{children:"maxDelayMillis"})," multiplying by multiplier every\nretry. Please note that the ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry/Backoff.html#withJitter(double)",children:"Backoff#withJitter(double)"})," will add jitter value to the calculated delay."]}),"\n",(0,a.jsxs)(t.p,{children:["For more information, please refer to the API documentation of the\n",(0,a.jsx)(t.a,{href:"https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry/package-summary.html",children:"com.linecorp.armeria.client.retry"})," package."]}),"\n",(0,a.jsxs)(t.h2,{id:"maxtotalattempts-vs-per-backoff-maxattempts",children:[(0,a.jsx)(t.code,{children:"maxTotalAttempts"})," vs per-Backoff ",(0,a.jsx)(t.code,{children:"maxAttempts"})]}),"\n",(0,a.jsxs)(t.p,{children:["If you create a ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry/Backoff.html",children:"Backoff"})," using ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry/Backoff.html#withMaxAttempts(int)",children:"Backoff#withMaxAttempts(int)"})," in a ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry/RetryRule.html",children:"RetryRule"}),",\nthe ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry/RetryingClient.html",children:"RetryingClient"})," which uses the ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry/RetryRule.html",children:"RetryRule"})," will stop retrying when the number of\nattempts passed ",(0,a.jsx)(t.code,{children:"maxAttempts"}),". However, if you have more than one ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry/Backoff.html",children:"Backoff"})," and return one after\nthe other continuously, it will keep retrying over and over again because the counter that\n",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry/RetryingClient.html",children:"RetryingClient"})," internally tracks is initialized every time the different ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry/Backoff.html",children:"Backoff"})," is\nreturned. To limit the number of attempts in a whole retry session, ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry/RetryingClient.html",children:"RetryingClient"})," limits\nthe maximum number of total attempts to 10 by default. You can change this value by specifying\n",(0,a.jsx)(t.code,{children:"maxTotalAttempts"})," when you build a ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry/RetryingClient.html",children:"RetryingClient"}),":"]}),"\n",(0,a.jsx)(t.pre,{children:(0,a.jsx)(t.code,{className:"language-java",children:"RetryConfig config = RetryConfig.builder(rule)\n    .maxTotalAttempts(maxTotalAttempts)\n    .build();\nRetryingClient.newDecorator(config);\n"})}),"\n",(0,a.jsxs)(t.p,{children:["Or, you can override the default value of 10 using the JVM system property\n",(0,a.jsx)(t.code,{children:"-Dcom.linecorp.armeria.defaultMaxTotalAttempts=<integer>"}),"."]}),"\n",(0,a.jsxs)(t.p,{children:["Note that when a ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry
1/RetryingClient.html",children:"RetryingClient"})," stops due to the attempts limit, the client will get the last received\n",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/common/Response.html",children:"Response"})," from the server."]}),"\n",(0,a.jsx)(t.h2,{id:"per-attempt-timeout",children:"Per-attempt timeout"}),"\n",(0,a.jsxs)(t.p,{children:[(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/ResponseTimeoutException.html",children:"ResponseTimeoutException"})," can occur in two different situations while retrying. First, it occurs\nwhen the time of whole retry session has passed the time previously configured using:"]}),"\n",(0,a.jsx)(t.pre,{children:(0,a.jsx)(t.code,{className:"language-java",children:"ClientBuilder.responseTimeoutMillis(millis);\n// or..\nClientRequestContext.setResponseTimeoutAfterMillis(millis);\n"})}),"\n",(0,a.jsxs)(t.p,{children:["You cannot retry on this ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/ResponseTimeoutException.html",children:"ResponseTimeoutException"}),".\nSecond, it occurs when the time of individual attempt in retry has passed the time which is per-attempt timeout.\nYou can configure it when you create the decorator:"]}),"\n",(0,a.jsx)(t.pre,{children:(0,a.jsx)(t.code,{className:"language-java",children:"RetryConfig config = RetryConfig.builder(rule)\n    .maxTotalAttempts(maxTotalAttempts)\n    .responseTimeoutMillisForEachAttempt(responseTimeoutMillisForEachAttempt)\n    .build();\nRetryingClient.newDecorator(config);\n"})}),"\n",(0,a.jsxs)(t.p,{children:["You can retry on this ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/ResponseTimeoutException.html",children:"ResponseTimeoutException"}),"."]}),"\n",(0,a.jsxs)(t.p,{children:["For example, when making a retrying request to an unresponsive service\nwith ",(0,a.jsx)(t.code,{children:"responseTimeoutMillis = 10,000"}),", ",(0,a.jsx)(t.code,{children:"responseTimeoutMillisForEachAttempt = 3,000"})," and disabled\n",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry/Backoff.html",children:"Backoff"}),", the first three attempts will be timed out by the per-attempt timeout (3,000ms).\nThe 4th one will be aborted after 1,000ms since the request session has reached at 10,000ms before\nit is timed out by the per-attempt timeout."]}),"\n",(0,a.jsx)(t.pre,{children:(0,a.jsx)(t.code,{className:"language-bob-svg",children:"0ms         3,000ms     6,000ms     9,000ms\n|           |           |           |\n+-----------+-----------+-----------+----+\n| Attempt 1 | Attempt 2 | Attempt 3 | A4 |\n+-----------+-----------+-----------+----+\n                                         |\n                                       10,000ms (ResponseTimeoutException)\n"})}),"\n",(0,a.jsxs)(t.p,{children:["In the example above, every attempt is made before it is timed out because the ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry/Backoff.html",children:"Backoff"})," is disabled.\nHowever, what if a ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry/Backoff.html",children:"Backoff"})," is enabled and the moment of trying next attempt is after the point of\n",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/ResponseTimeoutException.html",children:"ResponseTimeoutException"}),"? In such a case, the ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry/RetryingClient.html",children:"RetryingClient"})," does not schedule for the\nnext attempt, but finishes the retry session immediately with the last received ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/common/Response.html",children:"Response"}),".\nConsider the following example:"]}),"\n",(0,a.jsx)(t.pre,{children:(0,a.jsx)(t.code,{className:"language-bob-svg",children:"0ms         3,000ms     6,000ms     9,000ms     12,000ms\n|           |           |           |           |\n+-----------+-----------+-----------+-----------+-----------------------+\n| Attempt 1 |           | Attempt 2 |           | Attempt 3 is not made |\n+-----------+-----------+-----------+----+------+-----------------------+\n                                    |    |\n                                    | 10,000ms (retry session deadline)\n                                    |\n                                stops retrying at this point\
1n"})}),"\n",(0,a.jsxs)(t.p,{children:["Unlike the example above, the ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry/Backoff.html",children:"Backoff"})," is enabled and it makes the ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry/RetryingClient.html",children:"RetryingClient"})," perform\nretries with 3-second delay. When the second attempt is finished at 9,000ms, the next attempt will be\nat 12,000ms exceeding the response timeout of 10,000ms.\nThe ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry/RetryingClient.html",children:"RetryingClient"}),", at this point, stops retrying and finished the retry session with the last\nreceived ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/common/Response.html",children:"Response"}),", retrieved at 9,000ms from the attempt 2."]}),"\n",(0,a.jsxs)(t.h2,{id:"retryingclient-with-logging",children:[(0,a.jsx)(t.code,{children:"RetryingClient"})," with logging"]}),"\n",(0,a.jsxs)(t.p,{children:["You can use ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry/RetryingClient.html",children:"RetryingClient"})," with ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/logging/LoggingClient.html",children:"LoggingClient"})," to log. If you want to log all of the\nrequests and responses, decorate ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/logging/LoggingClient.html",children:"LoggingClient"})," with ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry/RetryingClient.html",children:"RetryingClient"}),". That is:"]}),"\n",(0,a.jsx)(t.pre,{children:(0,a.jsx)(t.code,{className:"language-java",children:"RetryRule rule = RetryRule.failsafe();\nWebClient client = WebClient.builder(...)\n                            .decorator(LoggingClient.newDecorator())\n                            .decorator(RetryingClient.newDecorator(rule))\n                            .build();\n"})}),"\n",(0,a.jsx)(t.p,{children:"This will produce following logs when there are three attempts:"}),"\n",(0,a.jsx)(t.pre,{children:(0,a.jsx)(t.code,{children:"Request: {startTime=..., length=..., duration=..., scheme=..., host=..., headers=[...]\nResponse: {startTime=..., length=..., duration=..., headers=[:status=500, ...]\nRequest: {startTime=..., ..., headers=[..., armeria-retry-count=1, ...]\nResponse: {startTime=..., length=..., duration=..., headers=[:status=500, ...]\nRequest: {startTime=..., ..., headers=[..., armeria-retry-count=2, ...]\nResponse: {startTime=..., length=..., duration=..., headers=[:status=200, ...]\n"})}),"\n",(0,a.jsx)(t.admonition,{type:"tip",children:(0,a.jsxs)(t.p,{children:["Did you notice that the ",(0,a.jsx)(t.code,{children:"armeria-retry-count"})," header is inserted from the second request?\n",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry/RetryingClient.html",children:"RetryingClient"})," inserts it to indicate the retry count of a request.\nThe server might use this value to reject excessive retries, etc."]})}),"\n",(0,a.jsx)(t.p,{children:"If you want to log the first request and the last response, no matter if it's successful or not,\ndo the reverse:"}),"\n",(0,a.jsx)(t.pre,{children:(0,a.jsx)(t.code,{className:"language-java",children:"import com.linecorp.armeria.client.logging.LoggingClient;\n\nRetryRule rule = RetryRule.failsafe();\n// Note the order of decoration.\nWebClient client = WebClient.builder(...)\n                            .decorator(RetryingClient.newDecorator(rule))\n                            .decorator(LoggingClient.newDecorator())\n                            .build();\n"})}),"\n",(0,a.jsx)(t.p,{children:"This will produce single request and response log pair and the total number of attempts only, regardless\nhow many attempts are made:"}),"\n",(0,a.jsx)(t.pre,{children:(0,a.jsx)(t.code,{children:"Request: {startTime=..., length=..., duration=..., scheme=..., host=..., headers=[...]\nResponse: {startTime=..., length=..., headers=[:status=200, ...]}, {totalAttempts=3}\n"})}),"\n",(0,a.jsx)(t.admonition,{type:"tip",children:(0,a.jsxs)(t.p,{children:["Please refer to ",(0,a.jsx)(t.a,{href:"/docs/advanced/structured-logging#nested-log",children:"Nested log"}),",\nif you are curious about how this works internally."]})}),"\n",(0,a.jsxs)(t.h2,{id:"retryingclient-with-circuit-breaker",children:[(0,a.jsx)(t.code,{children:"RetryingClient"})," with circuit breaker"]}),"\n",(0,a.jsxs)(t.p,{children:["You might want to use ",(0,a.jsx)(t.a,{href:"/docs/client/circuit-breaker",children:"Circuit breaker"})," with ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry
1/RetryingClient.html",children:"RetryingClient"})," using\n",(0,a.jsx)(t.a,{href:"/docs/client/decorator",children:"Decorating a client"}),":"]}),"\n",(0,a.jsx)(t.pre,{children:(0,a.jsx)(t.code,{className:"language-java",children:"import com.linecorp.armeria.client.circuitbreaker.CircuitBreakerRule;\nimport com.linecorp.armeria.client.circuitbreaker.CircuitBreakerClientBuilder;\n\nCircuitBreakerRule cbRule = CircuitBreakerRule.onServerErrorStatus();\nRetryRule myRetryRule = RetryRule.builder()\n                                 ...\n                                 .build();\n\nWebClient client = WebClient.builder(...)\n                            .decorator(CircuitBreakerClient.builder(cbRule)\n                                                           .newDecorator())\n                            .decorator(RetryingClient.builder(myRetryRule)\n                                                     .newDecorator())\n                            .build();\n\nAggregatedHttpResponse res = client.execute(...).aggregate().join();\n"})}),"\n",(0,a.jsxs)(t.p,{children:["This decorates ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/circuitbreaker/CircuitBreakerClient.html",children:"CircuitBreakerClient"})," with ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry/RetryingClient.html",children:"RetryingClient"})," so that the ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/circuitbreaker/CircuitBreaker.html",children:"CircuitBreaker"}),"\njudges every request and retried request as successful or failed. If the failure rate exceeds a certain\nthreshold, it raises a ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/circuitbreaker/FailFastException.html",children:"FailFastException"}),". When using both clients, you need to build a custom\n",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry/RetryRule.html",children:"RetryRule"})," to handle this exception so that the ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/retry/RetryingClient.html",children:"RetryingClient"})," does not attempt\na retry unnecessarily when the circuit is open, e.g."]}),"\n",(0,a.jsx)(t.pre,{children:(0,a.jsx)(t.code,{className:"language-java",children:"import com.linecorp.armeria.client.circuitbreaker.FailFastException;\n\nRetryRule.of(RetryRule.builder()\n                      // The circuit is already open so stops retrying.\n                      .onException(FailFastException.class)\n                      .thenNoRetry(),\n             RetryRule.builder()\n                      .onException(ex -> ex instanceof ResponseTimeoutException ||\n                                         ex instanceof UnprocessedRequestException)\n                      .thenBackoff(),\n             // Implement the rest of your own rule.\n             ...);\n"})}),"\n",(0,a.jsx)(t.admonition,{type:"tip",children:(0,a.jsxs)(t.p,{children:["You may want to allow retrying even on ",(0,a.jsx)(t.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/client/circuitbreaker/FailFastException.html",children:"FailFastException"})," when your endpoint is configured with\nclient-side load balancing because the next attempt might be sent to the next available endpoint.\nSee ",(0,a.jsx)(t.a,{href:"/docs/client/service-discovery",children:"Client-side load balancing and service discovery"}),"\nfor more information."]})}),"\n",(0,a.jsx)(t.h2,{id:"see-also",children:"See also"}),"\n",(0,a.jsxs)(t.ul,{children:["\n",(0,a.jsx)(t.li,{children:(0,a.jsx)(t.a,{href:"/docs/advanced/structured-logging",children:"Structured logging"})}),"\n"]})]})}function d(e={}){let{wrapper:t}={...(0,n.R)(),...e.components};return t?(0,a.jsx)(t,{...e,children:(0,a.jsx)(m,{...e})}):m(e)}},28453(e,t,r){r.d(t,{R:()=>o,x:()=>c});var i=r(96540);let a={},n=i.createContext(a);function o(e){let t=i.useContext(n);return i.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function c(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(a):e.components||a:o(e.components),i.createElement(n.Provider,{value:t},e.children)}}}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.