From 4dba1dd7f071fb65e4090b8a2854e5f9ff1b4999 Mon Sep 17 00:00:00 2001
From: Jun Luo <4catcode@gmail.com>
Date: Sun, 1 Feb 2026 09:55:24 +0800
Subject: [PATCH] refactor: use static initialization for `GsonSingleton`.
Replace lazy initialization with static initialization to ensure thread safety. The previous implementation had a race condition where multiple threads could create separate Gson instances.
---
CHANGELOG.md | 1 +
.../sdk/responses/gson/GsonSingleton.java | 94 +++++++++----------
2 files changed, 46 insertions(+), 49 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 15b6c0871..566f55671 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@
- fix: add overflow check in `TimeBounds.expiresAfter()` to prevent integer overflow when timeout is too large.
- fix: add validation for `ManageDataOperation` value length to ensure it does not exceed 64 bytes.
- fix: use `StandardCharsets.UTF_8` explicitly when converting byte arrays to strings to ensure consistent behavior across different platforms.
+- refactor: use static initialization for `GsonSingleton` to ensure thread safety.
## 2.2.1
diff --git a/src/main/java/org/stellar/sdk/responses/gson/GsonSingleton.java b/src/main/java/org/stellar/sdk/responses/gson/GsonSingleton.java
index 3e8ecee54..bc9f6ea1f 100644
--- a/src/main/java/org/stellar/sdk/responses/gson/GsonSingleton.java
+++ b/src/main/java/org/stellar/sdk/responses/gson/GsonSingleton.java
@@ -26,58 +26,54 @@
*
Usually, you shouldn't need to use this class directly.
*/
public class GsonSingleton {
- private static Gson instance = null;
+ private static final Gson INSTANCE = createInstance();
- protected GsonSingleton() {}
+ private GsonSingleton() {}
public static Gson getInstance() {
- if (instance == null) {
- TypeToken> accountPageType = new TypeToken>() {};
- TypeToken> assetPageType = new TypeToken>() {};
- TypeToken> effectPageType = new TypeToken>() {};
- TypeToken> ledgerPageType = new TypeToken>() {};
- TypeToken> liquidityPoolPageType =
- new TypeToken>() {};
- TypeToken> offerPageType = new TypeToken>() {};
- TypeToken> operationPageType =
- new TypeToken>() {};
- TypeToken> pathPageType = new TypeToken>() {};
- TypeToken> tradePageType = new TypeToken>() {};
- TypeToken> tradeAggregationPageType =
- new TypeToken>() {};
- TypeToken> transactionPageType =
- new TypeToken>() {};
- TypeToken> claimableBalancePageType =
- new TypeToken>() {};
+ return INSTANCE;
+ }
+
+ private static Gson createInstance() {
+ TypeToken> accountPageType = new TypeToken>() {};
+ TypeToken> assetPageType = new TypeToken>() {};
+ TypeToken> effectPageType = new TypeToken>() {};
+ TypeToken> ledgerPageType = new TypeToken>() {};
+ TypeToken> liquidityPoolPageType =
+ new TypeToken>() {};
+ TypeToken> offerPageType = new TypeToken>() {};
+ TypeToken> operationPageType =
+ new TypeToken>() {};
+ TypeToken> pathPageType = new TypeToken>() {};
+ TypeToken> tradePageType = new TypeToken>() {};
+ TypeToken> tradeAggregationPageType =
+ new TypeToken>() {};
+ TypeToken> transactionPageType =
+ new TypeToken>() {};
+ TypeToken> claimableBalancePageType =
+ new TypeToken>() {};
- instance =
- new GsonBuilder()
- .registerTypeAdapter(Asset.class, new AssetDeserializer())
- .registerTypeAdapter(Predicate.class, new PredicateDeserializer())
- .registerTypeAdapter(OperationResponse.class, new OperationDeserializer())
- .registerTypeAdapter(EffectResponse.class, new EffectDeserializer())
- .registerTypeAdapter(
- accountPageType.getType(), new PageDeserializer<>(accountPageType))
- .registerTypeAdapter(assetPageType.getType(), new PageDeserializer<>(assetPageType))
- .registerTypeAdapter(effectPageType.getType(), new PageDeserializer<>(effectPageType))
- .registerTypeAdapter(ledgerPageType.getType(), new PageDeserializer<>(ledgerPageType))
- .registerTypeAdapter(
- liquidityPoolPageType.getType(), new PageDeserializer<>(liquidityPoolPageType))
- .registerTypeAdapter(offerPageType.getType(), new PageDeserializer<>(offerPageType))
- .registerTypeAdapter(
- operationPageType.getType(), new PageDeserializer<>(operationPageType))
- .registerTypeAdapter(pathPageType.getType(), new PageDeserializer<>(pathPageType))
- .registerTypeAdapter(tradePageType.getType(), new PageDeserializer<>(tradePageType))
- .registerTypeAdapter(
- tradeAggregationPageType.getType(),
- new PageDeserializer<>(tradeAggregationPageType))
- .registerTypeAdapter(
- transactionPageType.getType(), new PageDeserializer<>(transactionPageType))
- .registerTypeAdapter(
- claimableBalancePageType.getType(),
- new PageDeserializer<>(claimableBalancePageType))
- .create();
- }
- return instance;
+ return new GsonBuilder()
+ .registerTypeAdapter(Asset.class, new AssetDeserializer())
+ .registerTypeAdapter(Predicate.class, new PredicateDeserializer())
+ .registerTypeAdapter(OperationResponse.class, new OperationDeserializer())
+ .registerTypeAdapter(EffectResponse.class, new EffectDeserializer())
+ .registerTypeAdapter(accountPageType.getType(), new PageDeserializer<>(accountPageType))
+ .registerTypeAdapter(assetPageType.getType(), new PageDeserializer<>(assetPageType))
+ .registerTypeAdapter(effectPageType.getType(), new PageDeserializer<>(effectPageType))
+ .registerTypeAdapter(ledgerPageType.getType(), new PageDeserializer<>(ledgerPageType))
+ .registerTypeAdapter(
+ liquidityPoolPageType.getType(), new PageDeserializer<>(liquidityPoolPageType))
+ .registerTypeAdapter(offerPageType.getType(), new PageDeserializer<>(offerPageType))
+ .registerTypeAdapter(operationPageType.getType(), new PageDeserializer<>(operationPageType))
+ .registerTypeAdapter(pathPageType.getType(), new PageDeserializer<>(pathPageType))
+ .registerTypeAdapter(tradePageType.getType(), new PageDeserializer<>(tradePageType))
+ .registerTypeAdapter(
+ tradeAggregationPageType.getType(), new PageDeserializer<>(tradeAggregationPageType))
+ .registerTypeAdapter(
+ transactionPageType.getType(), new PageDeserializer<>(transactionPageType))
+ .registerTypeAdapter(
+ claimableBalancePageType.getType(), new PageDeserializer<>(claimableBalancePageType))
+ .create();
}
}