diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 00000000..b8fc7701 --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## 2025-02-12 - Reusable HexFormat optimization +**Learning:** Found multiple instances of byte-to-hex conversion using `String.format("%02x", b)` in a loop or calling `HexFormat.of()` repeatedly. This creates unnecessary intermediate `String` and `Object[]` allocations, decreasing performance. +**Action:** Use a static final reusable `java.util.HexFormat.of()` instance for byte array to hex string conversions in the codebase to eliminate redundant memory allocations and reduce GC overhead. diff --git a/pom.xml b/pom.xml index 6b7e40df..2ada4be2 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ org.springframework.boot spring-boot-starter-parent - 3.5.0 + 3.5.14 diff --git a/src/main/java/com/clearfolio/viewer/artifact/ArtifactLinkService.java b/src/main/java/com/clearfolio/viewer/artifact/ArtifactLinkService.java index 454b0448..1977d7b0 100644 --- a/src/main/java/com/clearfolio/viewer/artifact/ArtifactLinkService.java +++ b/src/main/java/com/clearfolio/viewer/artifact/ArtifactLinkService.java @@ -12,6 +12,7 @@ import java.util.List; import java.util.Optional; import java.util.UUID; +import java.util.HexFormat; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; @@ -57,6 +58,7 @@ public class ArtifactLinkService { private static final int TOKEN_FIELD_COUNT = 10; private static final Base64.Encoder URL_ENCODER = Base64.getUrlEncoder().withoutPadding(); private static final Base64.Decoder URL_DECODER = Base64.getUrlDecoder(); + private static final HexFormat HEX_FORMAT = HexFormat.of(); private final ArtifactStore artifactStore; private final ArtifactLinkLedger artifactLinkLedger; @@ -423,11 +425,7 @@ private static String sha256Hex(byte[] bytes) { try { MessageDigest digest = MessageDigest.getInstance("SHA-256"); byte[] raw = digest.digest(bytes); - StringBuilder hex = new StringBuilder(raw.length * 2); - for (byte b : raw) { - hex.append(String.format("%02x", b)); - } - return hex.toString(); + return HEX_FORMAT.formatHex(raw); } catch (GeneralSecurityException ex) { throw new IllegalStateException("SHA-256 digest unavailable", ex); } diff --git a/src/main/java/com/clearfolio/viewer/service/DefaultDocumentConversionService.java b/src/main/java/com/clearfolio/viewer/service/DefaultDocumentConversionService.java index b8787f35..7a729522 100644 --- a/src/main/java/com/clearfolio/viewer/service/DefaultDocumentConversionService.java +++ b/src/main/java/com/clearfolio/viewer/service/DefaultDocumentConversionService.java @@ -13,6 +13,8 @@ import org.springframework.web.multipart.MultipartFile; import com.clearfolio.viewer.auth.TenantContext; +import java.util.HexFormat; + import com.clearfolio.viewer.model.ConversionJob; import com.clearfolio.viewer.repository.ConversionJobRepository; import com.clearfolio.viewer.repository.ConversionJobStateStore; @@ -26,6 +28,8 @@ @Service public class DefaultDocumentConversionService implements DocumentConversionService { + private static final HexFormat HEX_FORMAT = HexFormat.of(); + private final ConversionJobRepository repository; private final ConversionJobStateStore stateStore; private final DocumentValidationService validationService; @@ -160,12 +164,7 @@ private String contentHash(MultipartFile file) { } byte[] raw = digest.digest(); - StringBuilder hex = new StringBuilder(raw.length * 2); - for (byte b : raw) { - hex.append(String.format("%02x", b)); - } - - return hex.toString(); + return HEX_FORMAT.formatHex(raw); } catch (NoSuchAlgorithmException ex) { throw new IllegalStateException("SHA-256 digest unavailable", ex); } catch (IOException ex) { diff --git a/src/main/java/com/clearfolio/viewer/service/DefaultDocumentValidationService.java b/src/main/java/com/clearfolio/viewer/service/DefaultDocumentValidationService.java index b898f7ac..b4975ed4 100644 --- a/src/main/java/com/clearfolio/viewer/service/DefaultDocumentValidationService.java +++ b/src/main/java/com/clearfolio/viewer/service/DefaultDocumentValidationService.java @@ -23,6 +23,7 @@ public class DefaultDocumentValidationService implements DocumentValidationServi private static final Logger LOGGER = LoggerFactory.getLogger(DefaultDocumentValidationService.class); private static final int FINGERPRINT_TRUNCATE_BYTES = 8; + private static final HexFormat HEX_FORMAT = HexFormat.of(); private final Set blockedExtensions; private final long maxUploadSizeBytes; @@ -140,7 +141,7 @@ private String tokenFingerprint(String approvalToken) { try { MessageDigest digest = MessageDigest.getInstance("SHA-256"); byte[] hashed = digest.digest(approvalToken.getBytes(StandardCharsets.UTF_8)); - return HexFormat.of().formatHex(hashed, 0, FINGERPRINT_TRUNCATE_BYTES); + return HEX_FORMAT.formatHex(hashed, 0, FINGERPRINT_TRUNCATE_BYTES); } catch (NoSuchAlgorithmException ex) { throw new IllegalStateException("SHA-256 digest unavailable", ex); }