Skip to content
Merged
3 changes: 3 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## 2026-07-09 - Reuse HexFormat instance to reduce allocations and loops
**Learning:** Using `String.format("%02x", b)` in a loop or calling `HexFormat.of()` repeatedly for hex conversions creates unnecessary intermediate object allocations and reduces performance.
**Action:** Always use a single, reusable `private static final java.util.HexFormat HEX_FORMAT = java.util.HexFormat.of();` instance and its `formatHex` method for byte-to-hex string conversions.
## 2026-07-09 - 16진수 문자열 변환 성능 최적화
**Learning:** 루프 내에서 `String.format("%02x", b)`를 사용하는 것은 지속적인 객체 할당과 파싱으로 인해 Java에서 매우 비효율적입니다. Java 17 이상에서는 이 사용 사례에 최적화된 `java.util.HexFormat`을 제공합니다.
**Action:** 바이트 배열을 16진수 문자열로 변환할 때 성능을 향상시키고 메모리 할당을 줄이기 위해 `java.util.HexFormat.of().formatHex(bytes)`를 사용해야 합니다.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import java.time.Instant;
import java.util.Arrays;
import java.util.Base64;
import java.util.HexFormat;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
Expand Down Expand Up @@ -39,6 +40,8 @@
@Service
public class ArtifactLinkService {

private static final HexFormat HEX_FORMAT = HexFormat.of();

/**
* Query parameter used by PDF.js-compatible artifact URLs.
*/
Expand Down Expand Up @@ -426,9 +429,8 @@ private static String sha256Hex(byte[] bytes) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] raw = digest.digest(bytes);
// Optimization: java.util.HexFormat.of().formatHex() is faster
// and allocates less memory than String.format.
return java.util.HexFormat.of().formatHex(raw);
// Reused HexFormat for performance
return HEX_FORMAT.formatHex(raw);
} catch (GeneralSecurityException ex) {
throw new IllegalStateException("SHA-256 digest unavailable", ex);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HexFormat;
import java.util.Locale;
import java.util.Optional;
import java.util.UUID;
Expand Down Expand Up @@ -33,12 +34,12 @@
@Service
public class DefaultDocumentConversionService implements DocumentConversionService {

private static final HexFormat HEX_FORMAT = HexFormat.of();
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(
DefaultDocumentConversionService.class);
private static final String PDF_CONTENT_TYPE = "application/pdf";
private static final String PDF_EXTENSION_SUFFIX = ".pdf";
private static final byte[] PDF_MAGIC_HEADER = {'%', 'P', 'D', 'F', '-'};

private final ConversionJobRepository repository;
private final ConversionJobStateStore stateStore;
private final DocumentValidationService validationService;
Expand Down Expand Up @@ -331,9 +332,8 @@ private String contentHash(MultipartFile file) {
}

byte[] raw = digest.digest();
// Optimization: java.util.HexFormat.of().formatHex() is faster
// and allocates less memory than String.format.
return java.util.HexFormat.of().formatHex(raw);
// Reused HexFormat for performance
return HEX_FORMAT.formatHex(raw);
} catch (NoSuchAlgorithmException ex) {
throw new IllegalStateException("SHA-256 digest unavailable", ex);
} catch (IOException ex) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
@Service
public class DefaultDocumentValidationService implements DocumentValidationService {

private static final HexFormat HEX_FORMAT = HexFormat.of();
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultDocumentValidationService.class);
private static final int FINGERPRINT_TRUNCATE_BYTES = 8;

Expand Down Expand Up @@ -201,7 +202,8 @@ 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);
// Reused HexFormat for performance
return HEX_FORMAT.formatHex(hashed, 0, FINGERPRINT_TRUNCATE_BYTES);
} catch (NoSuchAlgorithmException ex) {
throw new IllegalStateException("SHA-256 digest unavailable", ex);
}
Expand Down
Loading