From 905c7c50f8cbfb95d846601e4d1f581d3c053f44 Mon Sep 17 00:00:00 2001 From: Roberto Cella Date: Sat, 4 Jul 2026 10:52:02 +0200 Subject: [PATCH 1/7] Add logic to wait 15 seconds and then retry one time when Telegram API calls fail --- .../stickerifier/stickerify/bot/Stickerify.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/main/java/com/github/stickerifier/stickerify/bot/Stickerify.java b/src/main/java/com/github/stickerifier/stickerify/bot/Stickerify.java index 7bb441aa..8b5bde19 100644 --- a/src/main/java/com/github/stickerifier/stickerify/bot/Stickerify.java +++ b/src/main/java/com/github/stickerifier/stickerify/bot/Stickerify.java @@ -39,6 +39,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.time.Duration; import java.util.List; import java.util.Set; import java.util.concurrent.Executor; @@ -230,9 +231,25 @@ private , R extends BaseResponse> R execute(BaseRequ return response; } + waitFor(Duration.ofSeconds(15)); + + response = bot.execute(request); + + if (response.isOk()) { + return response; + } + throw new TelegramApiException(request.getMethod(), response.description()); } + private static void waitFor(Duration duration) { + try { + Thread.sleep(duration); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + private static void deleteTempFiles(Set pathsToDelete) { for (var path : pathsToDelete) { try { From fa74aa87639972145f97638aae4ef06d4c07d815 Mon Sep 17 00:00:00 2001 From: Roberto Cella Date: Sat, 4 Jul 2026 11:02:07 +0200 Subject: [PATCH 2/7] Improve exception handling in retry logic --- .../stickerify/bot/Stickerify.java | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/github/stickerifier/stickerify/bot/Stickerify.java b/src/main/java/com/github/stickerifier/stickerify/bot/Stickerify.java index 8b5bde19..eee73dec 100644 --- a/src/main/java/com/github/stickerifier/stickerify/bot/Stickerify.java +++ b/src/main/java/com/github/stickerifier/stickerify/bot/Stickerify.java @@ -231,25 +231,21 @@ private , R extends BaseResponse> R execute(BaseRequ return response; } - waitFor(Duration.ofSeconds(15)); + try { + Thread.sleep(Duration.ofSeconds(15)); - response = bot.execute(request); + response = bot.execute(request); - if (response.isOk()) { - return response; + if (response.isOk()) { + return response; + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); } throw new TelegramApiException(request.getMethod(), response.description()); } - private static void waitFor(Duration duration) { - try { - Thread.sleep(duration); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } - private static void deleteTempFiles(Set pathsToDelete) { for (var path : pathsToDelete) { try { From 6b7667d2e21552cd9598d1f52384bd1ca0895c62 Mon Sep 17 00:00:00 2001 From: Roberto Cella Date: Sat, 4 Jul 2026 13:17:45 +0200 Subject: [PATCH 3/7] Strengthen retry logic to only be executed when retry after value is returned from telegram --- .../stickerify/bot/Stickerify.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/github/stickerifier/stickerify/bot/Stickerify.java b/src/main/java/com/github/stickerifier/stickerify/bot/Stickerify.java index eee73dec..3a139150 100644 --- a/src/main/java/com/github/stickerifier/stickerify/bot/Stickerify.java +++ b/src/main/java/com/github/stickerifier/stickerify/bot/Stickerify.java @@ -231,16 +231,20 @@ private , R extends BaseResponse> R execute(BaseRequ return response; } - try { - Thread.sleep(Duration.ofSeconds(15)); + if (response.parameters() != null && response.parameters().retryAfter() > 0) { + var retryDelay = Duration.ofSeconds(response.parameters().retryAfter()); + + try { + Thread.sleep(retryDelay); - response = bot.execute(request); + response = bot.execute(request); - if (response.isOk()) { - return response; + if (response.isOk()) { + return response; + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); } throw new TelegramApiException(request.getMethod(), response.description()); From 099bb679a3528b0fed92ddc362e34f2b5a333848 Mon Sep 17 00:00:00 2001 From: Roberto Cella Date: Sat, 4 Jul 2026 16:43:41 +0200 Subject: [PATCH 4/7] Rework retry logic to retry at most 3 times before failing --- .../stickerify/bot/Stickerify.java | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/github/stickerifier/stickerify/bot/Stickerify.java b/src/main/java/com/github/stickerifier/stickerify/bot/Stickerify.java index 3a139150..1734f49c 100644 --- a/src/main/java/com/github/stickerifier/stickerify/bot/Stickerify.java +++ b/src/main/java/com/github/stickerifier/stickerify/bot/Stickerify.java @@ -232,18 +232,20 @@ private , R extends BaseResponse> R execute(BaseRequ } if (response.parameters() != null && response.parameters().retryAfter() > 0) { - var retryDelay = Duration.ofSeconds(response.parameters().retryAfter()); - - try { - Thread.sleep(retryDelay); - - response = bot.execute(request); - - if (response.isOk()) { - return response; + for (int retry = 1; retry <= 3; retry++) { + var retryDelay = Duration.ofSeconds(response.parameters().retryAfter()); + try { + Thread.sleep(retryDelay); + + response = bot.execute(request); + + if (response.isOk()) { + return response; + } + } catch (InterruptedException _) { + Thread.currentThread().interrupt(); + break; } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); } } From d6edf51fb09f56e2169befb2570adf5db5a33370 Mon Sep 17 00:00:00 2001 From: Roberto Cella Date: Sat, 4 Jul 2026 16:53:11 +0200 Subject: [PATCH 5/7] Fix retryAfter check in loop --- .../stickerify/bot/Stickerify.java | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/github/stickerifier/stickerify/bot/Stickerify.java b/src/main/java/com/github/stickerifier/stickerify/bot/Stickerify.java index 1734f49c..7131ba88 100644 --- a/src/main/java/com/github/stickerifier/stickerify/bot/Stickerify.java +++ b/src/main/java/com/github/stickerifier/stickerify/bot/Stickerify.java @@ -231,21 +231,19 @@ private , R extends BaseResponse> R execute(BaseRequ return response; } - if (response.parameters() != null && response.parameters().retryAfter() > 0) { - for (int retry = 1; retry <= 3; retry++) { - var retryDelay = Duration.ofSeconds(response.parameters().retryAfter()); - try { - Thread.sleep(retryDelay); - - response = bot.execute(request); - - if (response.isOk()) { - return response; - } - } catch (InterruptedException _) { - Thread.currentThread().interrupt(); - break; + for (int retry = 1; retry <= 3 && response.parameters() != null && response.parameters().retryAfter() > 0; retry++) { + var retryDelay = Duration.ofSeconds(response.parameters().retryAfter()); + try { + Thread.sleep(retryDelay); + + response = bot.execute(request); + + if (response.isOk()) { + return response; } + } catch (InterruptedException _) { + Thread.currentThread().interrupt(); + break; } } From 0e1ff52b1e872b11aca198ebfb91abe31e025709 Mon Sep 17 00:00:00 2001 From: Roberto Cella Date: Sat, 4 Jul 2026 17:01:51 +0200 Subject: [PATCH 6/7] Prevent null pointers checking on retryAfter value --- .../java/com/github/stickerifier/stickerify/bot/Stickerify.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/github/stickerifier/stickerify/bot/Stickerify.java b/src/main/java/com/github/stickerifier/stickerify/bot/Stickerify.java index 7131ba88..e19e79ef 100644 --- a/src/main/java/com/github/stickerifier/stickerify/bot/Stickerify.java +++ b/src/main/java/com/github/stickerifier/stickerify/bot/Stickerify.java @@ -231,7 +231,7 @@ private , R extends BaseResponse> R execute(BaseRequ return response; } - for (int retry = 1; retry <= 3 && response.parameters() != null && response.parameters().retryAfter() > 0; retry++) { + for (int retry = 1; retry <= 3 && response.parameters() != null && response.parameters().retryAfter() != null; retry++) { var retryDelay = Duration.ofSeconds(response.parameters().retryAfter()); try { Thread.sleep(retryDelay); From ef68805663a355e08bb27b0a02b2fb6e902144dd Mon Sep 17 00:00:00 2001 From: Roberto Cella Date: Sat, 4 Jul 2026 19:42:36 +0200 Subject: [PATCH 7/7] Improve code readability, add log to determine when a retry is needed, and added test to check on the new logic --- .../stickerify/bot/Stickerify.java | 14 ++++++++++--- .../stickerify/bot/MockResponses.java | 11 ++++++++++ .../stickerify/bot/StickerifyTest.java | 20 +++++++++++++++++++ 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/github/stickerifier/stickerify/bot/Stickerify.java b/src/main/java/com/github/stickerifier/stickerify/bot/Stickerify.java index e19e79ef..3826f4dc 100644 --- a/src/main/java/com/github/stickerifier/stickerify/bot/Stickerify.java +++ b/src/main/java/com/github/stickerifier/stickerify/bot/Stickerify.java @@ -23,6 +23,7 @@ import com.pengrad.telegrambot.TelegramBot; import com.pengrad.telegrambot.TelegramException; import com.pengrad.telegrambot.UpdatesListener; +import com.pengrad.telegrambot.model.ResponseParameters; import com.pengrad.telegrambot.model.Update; import com.pengrad.telegrambot.model.request.ReplyParameters; import com.pengrad.telegrambot.model.request.richmessages.InputRichMessage; @@ -33,6 +34,7 @@ import com.pengrad.telegrambot.request.richmessages.SendRichMessage; import com.pengrad.telegrambot.request.richmessages.SendRichMessageDraft; import com.pengrad.telegrambot.response.BaseResponse; +import org.jspecify.annotations.Nullable; import org.slf4j.event.Level; import java.io.File; @@ -231,10 +233,12 @@ private , R extends BaseResponse> R execute(BaseRequ return response; } - for (int retry = 1; retry <= 3 && response.parameters() != null && response.parameters().retryAfter() != null; retry++) { - var retryDelay = Duration.ofSeconds(response.parameters().retryAfter()); + for (int retry = 1; retry <= 3 && isRetriable(response.parameters()); retry++) { + var retryDelay = response.parameters().retryAfter(); + LOGGER.at(Level.WARN).log("The {} request failed, retrying in {} seconds", request.getMethod(), retryDelay); + try { - Thread.sleep(retryDelay); + Thread.sleep(Duration.ofSeconds(retryDelay)); response = bot.execute(request); @@ -250,6 +254,10 @@ private , R extends BaseResponse> R execute(BaseRequ throw new TelegramApiException(request.getMethod(), response.description()); } + private static boolean isRetriable(@Nullable ResponseParameters parameters) { + return parameters != null && parameters.retryAfter() != null && parameters.retryAfter() > 0; + } + private static void deleteTempFiles(Set pathsToDelete) { for (var path : pathsToDelete) { try { diff --git a/src/test/java/com/github/stickerifier/stickerify/bot/MockResponses.java b/src/test/java/com/github/stickerifier/stickerify/bot/MockResponses.java index 2b3cc8a0..6ee30545 100644 --- a/src/test/java/com/github/stickerifier/stickerify/bot/MockResponses.java +++ b/src/test/java/com/github/stickerifier/stickerify/bot/MockResponses.java @@ -13,6 +13,17 @@ public final class MockResponses { } """).build(); + static final MockResponse FAILURE_RESPONSE = new MockResponse.Builder().body(""" + { + ok: false, + error_code: 429, + description: "Too Many Requests: retry after 3", + parameters: { + retry_after: 3 + } + } + """).build(); + static final MockResponse START_MESSAGE = new MockResponse.Builder().body(""" { ok: true, diff --git a/src/test/java/com/github/stickerifier/stickerify/bot/StickerifyTest.java b/src/test/java/com/github/stickerifier/stickerify/bot/StickerifyTest.java index 00a7483f..aae19dad 100644 --- a/src/test/java/com/github/stickerifier/stickerify/bot/StickerifyTest.java +++ b/src/test/java/com/github/stickerifier/stickerify/bot/StickerifyTest.java @@ -404,4 +404,24 @@ void documentNotSupported() throws Exception { assertResponseContainsMarkdownMessage(sendRichMessage, Answer.ERROR); } } + + @Test + void retryApiCall() throws Exception { + server.enqueue(MockResponses.HELP_MESSAGE); + server.enqueue(MockResponses.FAILURE_RESPONSE); + server.enqueue(MockResponses.SUCCESS_RESPONSE); + + try (var _ = runBot()) { + var getUpdates = server.takeRequest(); + assertEquals("/api/token/getUpdates", getUpdates.getTarget()); + + var firstSendRichMessage = server.takeRequest(); + assertEquals("/api/token/sendRichMessage", firstSendRichMessage.getTarget()); + + var secondSendRichMessage = server.takeRequest(); + assertEquals("/api/token/sendRichMessage", secondSendRichMessage.getTarget()); + + assertResponseContainsMarkdownMessage(secondSendRichMessage, Answer.HELP); + } + } }