From 09d60533368d601d7d56fed03894e95794a6255c Mon Sep 17 00:00:00 2001 From: qiuyucheng Date: Fri, 20 Feb 2026 17:50:15 +0800 Subject: [PATCH 1/2] Fix memory leak in SpotifyApi by replacing ThreadLocal with DateTimeFormatter (Resolves #450) --- .../se/michaelthelin/spotify/SpotifyApi.java | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/main/java/se/michaelthelin/spotify/SpotifyApi.java b/src/main/java/se/michaelthelin/spotify/SpotifyApi.java index 489f3fb1f..f388903aa 100644 --- a/src/main/java/se/michaelthelin/spotify/SpotifyApi.java +++ b/src/main/java/se/michaelthelin/spotify/SpotifyApi.java @@ -45,6 +45,10 @@ import java.util.Date; import java.util.TimeZone; import java.util.logging.Logger; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.time.LocalDateTime; +import java.time.format.DateTimeParseException; /** * Instances of the SpotifyApi class provide access to the Spotify Web API. @@ -93,7 +97,10 @@ public class SpotifyApi { * The date format used by the Spotify Web API. It uses the {@code GMT} timezone and the following pattern: * {@code yyyy-MM-dd'T'HH:mm:ss} */ - private static final ThreadLocal SIMPLE_DATE_FORMAT = ThreadLocal.withInitial(() -> makeSimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", "GMT")); +// private static final ThreadLocal SIMPLE_DATE_FORMAT = ThreadLocal.withInitial(() -> makeSimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", "GMT")); + private static final DateTimeFormatter SIMPLE_DATE_FORMATTER = DateTimeFormatter + .ofPattern("yyyy-MM-dd'T'HH:mm:ss") + .withZone(ZoneId.of("GMT")); private final IHttpManager httpManager; private final String scheme; @@ -163,7 +170,13 @@ public static String concat(String[] parts, char character) { * @throws ParseException if the date is not in a valid format */ public static Date parseDefaultDate(String date) throws ParseException { - return SIMPLE_DATE_FORMAT.get().parse(date); +// return SIMPLE_DATE_FORMAT.get().parse(date); + // + String parsedDate = (date != null && date.length() > 19) ? date.substring(0, 19) : date; + + return Date.from(LocalDateTime.parse(parsedDate, SIMPLE_DATE_FORMATTER) + .atZone(ZoneId.of("GMT")) + .toInstant()); } /** @@ -173,7 +186,8 @@ public static Date parseDefaultDate(String date) throws ParseException { * @return the formatted date */ public static String formatDefaultDate(Date date) { - return SIMPLE_DATE_FORMAT.get().format(date); +// return SIMPLE_DATE_FORMAT.get().format(date); + return SIMPLE_DATE_FORMATTER.format(date.toInstant()); } /** From ffe93e3026690811d74e1722cb6807b1244b578e Mon Sep 17 00:00:00 2001 From: QiuYucheng2003 <2847117296@qq.com> Date: Sat, 21 Feb 2026 14:59:32 +0800 Subject: [PATCH 2/2] Address code review suggestions for date parsing - Add explicit null check in parseDefaultDate - Wrap DateTimeParseException into ParseException for backward compatibility - Reuse ZoneId from the formatter to optimize performance - Remove commented-out legacy code --- .../se/michaelthelin/spotify/SpotifyApi.java | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/main/java/se/michaelthelin/spotify/SpotifyApi.java b/src/main/java/se/michaelthelin/spotify/SpotifyApi.java index f388903aa..23c21b05c 100644 --- a/src/main/java/se/michaelthelin/spotify/SpotifyApi.java +++ b/src/main/java/se/michaelthelin/spotify/SpotifyApi.java @@ -97,7 +97,6 @@ public class SpotifyApi { * The date format used by the Spotify Web API. It uses the {@code GMT} timezone and the following pattern: * {@code yyyy-MM-dd'T'HH:mm:ss} */ -// private static final ThreadLocal SIMPLE_DATE_FORMAT = ThreadLocal.withInitial(() -> makeSimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", "GMT")); private static final DateTimeFormatter SIMPLE_DATE_FORMATTER = DateTimeFormatter .ofPattern("yyyy-MM-dd'T'HH:mm:ss") .withZone(ZoneId.of("GMT")); @@ -170,13 +169,23 @@ public static String concat(String[] parts, char character) { * @throws ParseException if the date is not in a valid format */ public static Date parseDefaultDate(String date) throws ParseException { -// return SIMPLE_DATE_FORMAT.get().parse(date); - // - String parsedDate = (date != null && date.length() > 19) ? date.substring(0, 19) : date; + if (date == null) { + throw new ParseException("Date string is null", 0); + } - return Date.from(LocalDateTime.parse(parsedDate, SIMPLE_DATE_FORMATTER) - .atZone(ZoneId.of("GMT")) - .toInstant()); + String parsedDate = date.length() > 19 ? date.substring(0, 19) : date; + + try { + return Date.from(LocalDateTime.parse(parsedDate, SIMPLE_DATE_FORMATTER) + .atZone(SIMPLE_DATE_FORMATTER.getZone()) + .toInstant()); + } catch (DateTimeParseException e) { + int errorIndex = e.getErrorIndex(); + if (errorIndex < 0) { + errorIndex = 0; + } + throw new ParseException(e.getMessage(), errorIndex); + } } /** @@ -186,7 +195,6 @@ public static Date parseDefaultDate(String date) throws ParseException { * @return the formatted date */ public static String formatDefaultDate(Date date) { -// return SIMPLE_DATE_FORMAT.get().format(date); return SIMPLE_DATE_FORMATTER.format(date.toInstant()); }