@pilllesss/yorn 1.0.182 → 1.0.183

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of @pilllesss/yorn might be problematic. Click here for more details.

Files changed (45) hide show
  1. package/README.md +1 -1
  2. package/dist/providers/data/.manifest.json +1 -1
  3. package/dist/skills/code-review/LICENSE +21 -0
  4. package/dist/skills/code-review/SKILL.md +233 -0
  5. package/dist/skills/code-review/assets/pr-review-template.md +137 -0
  6. package/dist/skills/code-review/assets/review-checklist.md +123 -0
  7. package/dist/skills/code-review/reference/angular.md +768 -0
  8. package/dist/skills/code-review/reference/architecture-review-guide.md +472 -0
  9. package/dist/skills/code-review/reference/c.md +890 -0
  10. package/dist/skills/code-review/reference/code-quality-universal.md +488 -0
  11. package/dist/skills/code-review/reference/code-review-best-practices.md +136 -0
  12. package/dist/skills/code-review/reference/common-bugs-checklist.md +302 -0
  13. package/dist/skills/code-review/reference/cpp.md +893 -0
  14. package/dist/skills/code-review/reference/cross-cutting/async-concurrency-patterns.md +515 -0
  15. package/dist/skills/code-review/reference/cross-cutting/error-handling-principles.md +492 -0
  16. package/dist/skills/code-review/reference/cross-cutting/n-plus-one-queries.md +309 -0
  17. package/dist/skills/code-review/reference/cross-cutting/sql-injection-prevention.md +308 -0
  18. package/dist/skills/code-review/reference/cross-cutting/xss-prevention.md +264 -0
  19. package/dist/skills/code-review/reference/csharp.md +525 -0
  20. package/dist/skills/code-review/reference/css-less-sass.md +661 -0
  21. package/dist/skills/code-review/reference/dart.md +670 -0
  22. package/dist/skills/code-review/reference/django.md +985 -0
  23. package/dist/skills/code-review/reference/fastapi.md +580 -0
  24. package/dist/skills/code-review/reference/go.md +993 -0
  25. package/dist/skills/code-review/reference/java.md +409 -0
  26. package/dist/skills/code-review/reference/java8.md +586 -0
  27. package/dist/skills/code-review/reference/kotlin.md +1018 -0
  28. package/dist/skills/code-review/reference/nestjs.md +593 -0
  29. package/dist/skills/code-review/reference/performance-review-guide.md +816 -0
  30. package/dist/skills/code-review/reference/php.md +684 -0
  31. package/dist/skills/code-review/reference/python.md +1073 -0
  32. package/dist/skills/code-review/reference/qt.md +757 -0
  33. package/dist/skills/code-review/reference/react.md +871 -0
  34. package/dist/skills/code-review/reference/ruby.md +964 -0
  35. package/dist/skills/code-review/reference/rust.md +846 -0
  36. package/dist/skills/code-review/reference/security-review-guide.md +494 -0
  37. package/dist/skills/code-review/reference/svelte.md +1064 -0
  38. package/dist/skills/code-review/reference/swift.md +936 -0
  39. package/dist/skills/code-review/reference/typescript.md +1016 -0
  40. package/dist/skills/code-review/reference/vue.md +924 -0
  41. package/dist/skills/code-review/reference/zig.md +440 -0
  42. package/dist/skills/code-review/scripts/pr-analyzer.py +435 -0
  43. package/dist/skills/code-review/scripts/test_pr_analyzer.py +380 -0
  44. package/dist/yorn.cjs +628 -628
  45. package/package.json +2 -2
@@ -0,0 +1,586 @@
1
+ # Java 8 / Legacy Stack Code Review Guide
2
+
3
+ Review guidance for codebases still on **Java 8** (and nearby legacy stacks: Spring Boot 2.x, `javax.*`, Hibernate 5). Do **not** require Java 17/21 features (records, text blocks, virtual threads, `ProblemDetail`, etc.) on these PRs.
4
+
5
+ > For modern stacks (Java 17/21 + Spring Boot 3), use the [Java Guide](java.md).
6
+
7
+ ## Table of Contents
8
+
9
+ - [When to Use This Guide](#when-to-use-this-guide)
10
+ - [Lambdas & Functional Interfaces](#lambdas--functional-interfaces)
11
+ - [Stream API](#stream-api)
12
+ - [Optional](#optional)
13
+ - [Date/Time API (`java.time`)](#datetime-api-javatime)
14
+ - [Concurrency: Thread Pools & CompletableFuture](#concurrency-thread-pools--completablefuture)
15
+ - [Spring Boot 2](#spring-boot-2)
16
+ - [JPA / Hibernate 5](#jpa--hibernate-5)
17
+ - [Exception Handling](#exception-handling)
18
+ - [Testing](#testing)
19
+ - [Review Checklist](#review-checklist)
20
+ - [References](#references)
21
+
22
+ ---
23
+
24
+ ## When to Use This Guide
25
+
26
+ | Scenario | Load |
27
+ |----------|------|
28
+ | Java 8 / 11 (no modern syntax), Spring Boot 2.x, `javax.persistence` | **This file** |
29
+ | Java 17/21, Spring Boot 3, `jakarta.*`, virtual threads | [java.md](java.md) |
30
+ | Migrating Boot 2 → Boot 3 | Use both: this guide for legacy pitfalls, `java.md` for the target |
31
+
32
+ Confirm `java.version`, Spring Boot version, and `javax` vs `jakarta` package names in `pom.xml` / `build.gradle` before choosing a guide.
33
+
34
+ ---
35
+
36
+ ## Lambdas & Functional Interfaces
37
+
38
+ ### Keep them short; prefer method references
39
+
40
+ ```java
41
+ // ❌ Long lambdas are hard to read, test, and debug
42
+ users.stream().forEach(u -> {
43
+ // dozens of lines of business logic...
44
+ });
45
+
46
+ // ✅ Extract a method, or use a method reference
47
+ users.forEach(this::processUser);
48
+ ```
49
+
50
+ ### Prefer JDK functional interfaces
51
+
52
+ ```java
53
+ // ❌ Unnecessary custom functional interface
54
+ @FunctionalInterface
55
+ interface UserCallback {
56
+ void accept(User u);
57
+ }
58
+
59
+ // ✅ Use Consumer / Function / Predicate / Supplier / BiFunction, etc.
60
+ void process(Consumer<User> callback) { ... }
61
+ ```
62
+
63
+ ### Captured variables must be effectively final
64
+
65
+ ```java
66
+ // ❌ Mutating a captured variable → compile error (or Atomic/array hacks)
67
+ int sum = 0;
68
+ list.forEach(n -> sum += n); // does not compile
69
+
70
+ // ✅ Reduce with a stream, or use an explicit mutable accumulator type
71
+ int sum = list.stream().mapToInt(Integer::intValue).sum();
72
+ ```
73
+
74
+ ---
75
+
76
+ ## Stream API
77
+
78
+ ### Do not force Streams for simple loops
79
+
80
+ ```java
81
+ // ❌ Side effects only — Stream adds no value
82
+ items.stream().forEach(item -> process(item));
83
+
84
+ // ✅ Plain for-each is clearer
85
+ for (Item item : items) {
86
+ process(item);
87
+ }
88
+ ```
89
+
90
+ ### Collect with `Collectors` on Java 8
91
+
92
+ ```java
93
+ // ❌ Stream.toList() is Java 16+ — not available on Java 8
94
+ list.stream().map(...).toList();
95
+
96
+ // ✅ Java 8
97
+ List<Dto> result = list.stream()
98
+ .map(...)
99
+ .collect(Collectors.toList());
100
+ ```
101
+
102
+ Note: `Collectors.toList()` is not guaranteed immutable. For an unmodifiable list, wrap with `Collections.unmodifiableList(...)` or use Guava / `List.copyOf` (the latter needs a newer JDK).
103
+
104
+ ### Two classic `Collectors.toMap` pitfalls
105
+
106
+ ```java
107
+ // ❌ Null values → NPE (internally uses Map.merge, which forbids null values)
108
+ Map<Long, String> map = users.stream()
109
+ .collect(Collectors.toMap(User::getId, User::getNickname)); // nickname may be null
110
+
111
+ // ✅ Filter first, or handle nulls explicitly
112
+ Map<Long, String> map = users.stream()
113
+ .filter(u -> u.getNickname() != null)
114
+ .collect(Collectors.toMap(User::getId, User::getNickname));
115
+
116
+ // ❌ Duplicate keys → IllegalStateException
117
+ .collect(Collectors.toMap(User::getName, Function.identity()));
118
+
119
+ // ✅ Provide a merge function
120
+ .collect(Collectors.toMap(User::getName, Function.identity(), (a, b) -> a));
121
+ ```
122
+
123
+ ### Be careful with `parallelStream()`
124
+
125
+ ```java
126
+ // ❌ Small collections / I/O / shared mutable state — often slower or unsafe
127
+ list.parallelStream().forEach(sharedList::add); // race
128
+
129
+ // ❌ Side-effecting forEach into a non-concurrent collection
130
+ map.entrySet().parallelStream().forEach(e -> result.put(e.getKey(), e.getValue()));
131
+
132
+ // ✅ Consider only for CPU-bound work, no shared mutable state, and large enough data
133
+ // ✅ Collect with toMap / toConcurrentMap — do not forEach into an external Map
134
+ Map<K, V> result = list.parallelStream()
135
+ .collect(Collectors.toConcurrentMap(Item::getKey, Item::getValue, (a, b) -> a));
136
+ ```
137
+
138
+ Parallel streams use `ForkJoinPool.commonPool()` by default and compete with other parallel / CompletableFuture work in the same process.
139
+
140
+ ### Do not mutate the stream source; avoid nested `forEach`
141
+
142
+ ```java
143
+ // ❌ Mutating the source during the pipeline → ConcurrentModificationException
144
+ list.stream().peek(list::add).count();
145
+
146
+ // ❌ Nested forEach is hard to read and hard to short-circuit
147
+ a.forEach(x -> b.forEach(y -> ...));
148
+
149
+ // ✅ Prefer flatMap or ordinary loops for cartesian / join-style logic
150
+ ```
151
+
152
+ ### Prefer primitive streams to avoid boxing
153
+
154
+ ```java
155
+ // ❌ Stream<Integer> boxing overhead
156
+ int sum = list.stream().map(Order::getAmount).reduce(0, Integer::sum);
157
+
158
+ // ✅ IntStream / LongStream / DoubleStream
159
+ int sum = list.stream().mapToInt(Order::getAmount).sum();
160
+ ```
161
+
162
+ ---
163
+
164
+ ## Optional
165
+
166
+ **Intent:** express possible absence as a **return type**, not as a general null replacement.
167
+
168
+ ```java
169
+ // ❌ Optional as field / parameter / collection element (serialization, reflection, API noise)
170
+ class User {
171
+ private Optional<String> email;
172
+ }
173
+ void send(Optional<String> email) { ... }
174
+ Optional<List<Order>> findOrders(); // empty list already means "none"
175
+
176
+ // ✅ Return type only; return empty collections for "none"
177
+ public Optional<User> findById(Long id) { ... }
178
+ public List<Order> findOrders(Long userId) { ... } // emptyList when none
179
+ ```
180
+
181
+ ### Do not use `isPresent()` + `get()` as a null check
182
+
183
+ ```java
184
+ // ❌ More verbose than a null check, and get() can still blow up
185
+ if (userOpt.isPresent()) {
186
+ return userOpt.get().getName();
187
+ }
188
+ return "Unknown";
189
+
190
+ // ✅ Functional chain (available on Java 8)
191
+ return userOpt.map(User::getName).orElse("Unknown");
192
+ ```
193
+
194
+ ### `orElse` vs `orElseGet`
195
+
196
+ ```java
197
+ // ❌ orElse argument is always evaluated (even when the Optional is present)
198
+ return findUser(id).orElse(loadDefaultFromDb()); // always hits DB
199
+
200
+ // ✅ Expensive defaults → orElseGet
201
+ return findUser(id).orElseGet(this::loadDefaultFromDb);
202
+
203
+ // ✅ Required value (Java 8)
204
+ return findUser(id).orElseThrow(() -> new UserNotFoundException(id));
205
+ // Note: no-arg orElseThrow() is Java 10+; Java 8 requires a Supplier
206
+ ```
207
+
208
+ ### `of` vs `ofNullable`; nest with `flatMap`
209
+
210
+ ```java
211
+ // ❌ of(null) → immediate NPE
212
+ Optional.of(possiblyNull);
213
+
214
+ // ✅
215
+ Optional.ofNullable(possiblyNull);
216
+
217
+ // ❌ map returning Optional → Optional<Optional<T>>
218
+ optional.map(this::findOther); // findOther returns Optional
219
+
220
+ // ✅
221
+ optional.flatMap(this::findOther);
222
+ ```
223
+
224
+ Java 8 has **no** `Optional.stream()` / `ifPresentOrElse` / `or` (those are Java 9+). Filtering a collection of Optionals:
225
+
226
+ ```java
227
+ list.stream()
228
+ .map(this::findUser)
229
+ .filter(Optional::isPresent)
230
+ .map(Optional::get) // OK after filter; or extract a helper
231
+ .collect(Collectors.toList());
232
+ ```
233
+
234
+ ---
235
+
236
+ ## Date/Time API (`java.time`)
237
+
238
+ One of the most common production footguns in legacy systems: keeping `Date` / `Calendar` / `SimpleDateFormat`.
239
+
240
+ ```java
241
+ // ❌ SimpleDateFormat is not thread-safe; a shared static instance corrupts state
242
+ private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd");
243
+
244
+ // ✅ DateTimeFormatter is immutable and thread-safe
245
+ private static final DateTimeFormatter FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
246
+ ```
247
+
248
+ ### Pick the right type
249
+
250
+ | Type | Use for |
251
+ |------|---------|
252
+ | `Instant` | Machine timestamps, audit, cross-service events (UTC timeline) |
253
+ | `LocalDate` | Date only (birthday, business day) |
254
+ | `LocalDateTime` | Date-time **without** zone; do not use for "when an event happened" |
255
+ | `ZonedDateTime` / `OffsetDateTime` | Human time that needs a zone or offset |
256
+
257
+ ```java
258
+ // ❌ LocalDateTime for "order placed at" — ambiguous across zones / DST
259
+ private LocalDateTime createdAt = LocalDateTime.now();
260
+
261
+ // ✅ Instant for event time; convert to a zone only for display
262
+ private Instant createdAt = Instant.now();
263
+
264
+ // ❌ now() depends on the JVM default zone — CI / prod / laptop disagree
265
+ LocalDate.now();
266
+
267
+ // ✅ Explicit ZoneId, or inject Clock for tests
268
+ LocalDate.now(ZoneOffset.UTC);
269
+ LocalDate.now(clock);
270
+ ```
271
+
272
+ ### Formatting traps
273
+
274
+ ```java
275
+ // ❌ YYYY is week-based year — wrong year near year boundaries
276
+ DateTimeFormatter.ofPattern("YYYY-MM-dd");
277
+
278
+ // ✅ Calendar year uses yyyy
279
+ DateTimeFormatter.ofPattern("yyyy-MM-dd");
280
+ ```
281
+
282
+ Interop with legacy APIs: `date.toInstant()`, `Date.from(instant)`, `LocalDateTime.ofInstant(instant, zone)`.
283
+
284
+ ---
285
+
286
+ ## Concurrency: Thread Pools & CompletableFuture
287
+
288
+ Java 8 has **no virtual threads**. I/O-heavy work needs a well-sized pool — not `newCachedThreadPool` or an unbounded queue.
289
+
290
+ ```java
291
+ // ❌ Unbounded queue + default rejection — latency explodes or OOM under load
292
+ ExecutorService exec = Executors.newFixedThreadPool(8); // unbounded queue
293
+
294
+ // ❌ Never shut down → thread leak
295
+ Executors.newFixedThreadPool(8).submit(task);
296
+
297
+ // ✅ Bounded queue + explicit rejection policy + lifecycle management
298
+ ThreadPoolExecutor exec = new ThreadPoolExecutor(
299
+ 8, 16, 60L, TimeUnit.SECONDS,
300
+ new ArrayBlockingQueue<>(500),
301
+ new ThreadPoolExecutor.CallerRunsPolicy());
302
+ // On shutdown: shutdown → awaitTermination → shutdownNow
303
+ ```
304
+
305
+ ### CompletableFuture
306
+
307
+ ```java
308
+ // ❌ I/O on the common pool (supplyAsync with no Executor)
309
+ CompletableFuture.supplyAsync(() -> restTemplate.getForObject(url, Dto.class));
310
+
311
+ // ✅ Explicit I/O executor
312
+ CompletableFuture.supplyAsync(() -> callRemote(), ioExecutor);
313
+
314
+ // ❌ thenApply returning a CF → nested CompletableFuture<CompletableFuture<T>>
315
+ .thenApply(id -> findAsync(id));
316
+
317
+ // ✅ Dependent async work → thenCompose
318
+ .thenCompose(id -> findAsync(id));
319
+
320
+ // ❌ get()/join() inside async callbacks — starves the pool or deadlocks
321
+ .thenApply(x -> other.join());
322
+
323
+ // ✅ Combine with allOf / thenCombine; join once at the boundary
324
+ ```
325
+
326
+ **Timeouts:** Java 8 has no `orTimeout` / `completeOnTimeout` (Java 9+). Use `get(timeout, unit)`, or a scheduler + `applyToEither` that completes exceptionally.
327
+
328
+ **Exceptions:** Attach `exceptionally` / `handle` / `whenComplete` at the chain boundary so failures are not swallowed.
329
+
330
+ ```java
331
+ // ❌ Shared mutable SimpleDateFormat / HashMap as a cache
332
+ private static final SimpleDateFormat SDF = ...;
333
+ private final Map<String, String> cache = new HashMap<>(); // concurrent puts
334
+
335
+ // ✅ ConcurrentHashMap; dates via java.time
336
+ private final ConcurrentHashMap<String, String> cache = new ConcurrentHashMap<>();
337
+ ```
338
+
339
+ ---
340
+
341
+ ## Spring Boot 2
342
+
343
+ Packages are **`javax.*`**, not `jakarta.*`. Do not require a Jakarta migration unless the PR's goal is upgrading to Boot 3.
344
+
345
+ ### Dependency injection
346
+
347
+ ```java
348
+ // ❌ Field @Autowired: hard to test, opaque dependencies
349
+ @Autowired
350
+ private UserRepository userRepo;
351
+
352
+ // ✅ Constructor injection (Boot 2: single constructor can omit @Autowired)
353
+ private final UserRepository userRepo;
354
+
355
+ public UserService(UserRepository userRepo) {
356
+ this.userRepo = userRepo;
357
+ }
358
+ ```
359
+
360
+ ### Configuration
361
+
362
+ ```java
363
+ // ❌ Hard-coded secrets; @Value scattered everywhere
364
+ @Value("${app.payment.api-key}")
365
+ private String apiKey;
366
+
367
+ // ✅ @ConfigurationProperties (Java 8 uses a class, not a record)
368
+ @ConfigurationProperties(prefix = "app.payment")
369
+ public class PaymentProperties {
370
+ private String apiKey;
371
+ private int timeoutMs;
372
+ // getters / setters
373
+ }
374
+ ```
375
+
376
+ Register with `@EnableConfigurationProperties`. On Boot **2.2+**, `@ConfigurationPropertiesScan` also works (`@SpringBootApplication` scans the startup class package by default).
377
+
378
+ ### RestTemplate must have timeouts
379
+
380
+ Default is **infinite wait**. A hung downstream can exhaust Tomcat / worker threads.
381
+
382
+ ```java
383
+ // ❌ Bare new RestTemplate() — no timeouts
384
+ return new RestTemplate();
385
+
386
+ // ✅ Boot 2.1+ Duration-based timeouts
387
+ @Bean
388
+ public RestTemplate restTemplate(RestTemplateBuilder builder) {
389
+ return builder
390
+ .setConnectTimeout(Duration.ofSeconds(2))
391
+ .setReadTimeout(Duration.ofSeconds(5))
392
+ .build();
393
+ }
394
+ ```
395
+
396
+ If you customize `ClientHttpRequestFactory` (e.g. Apache HttpClient), set connect / read / connectionRequest timeouts on the factory too — otherwise builder timeouts may be ignored.
397
+
398
+ ### Transaction proxy trap (same on Boot 2/3)
399
+
400
+ ```java
401
+ // ❌ Same-class self-call — @Transactional does not apply (proxy not invoked)
402
+ public void create(Order o) {
403
+ save(o); // internal call
404
+ }
405
+ @Transactional
406
+ public void save(Order o) { ... }
407
+
408
+ // ✅ Put the transaction boundary on the public entry point, or split into another bean
409
+ @Transactional
410
+ public void create(Order o) { saveInternal(o); }
411
+ ```
412
+
413
+ `@Transactional` on `private` methods is also ineffective.
414
+
415
+ ---
416
+
417
+ ## JPA / Hibernate 5
418
+
419
+ Entity annotations come from `javax.persistence.*`.
420
+
421
+ ### N+1
422
+
423
+ > Cross-language background: [N+1 query guide](cross-cutting/n-plus-one-queries.md)
424
+
425
+ ```java
426
+ // ❌ EAGER, or loops that trigger lazy loads
427
+ @OneToMany(fetch = FetchType.EAGER)
428
+ private List<Order> orders;
429
+
430
+ for (User u : userRepo.findAll()) {
431
+ u.getOrders().size(); // N queries when lazy
432
+ }
433
+
434
+ // ✅ JOIN FETCH / @EntityGraph; keep LAZY by default
435
+ @Query("SELECT u FROM User u JOIN FETCH u.orders")
436
+ List<User> findAllWithOrders();
437
+ ```
438
+
439
+ ### Transactions & read-only
440
+
441
+ ```java
442
+ // ❌ Transactions opened in Controllers; or @Transactional on private methods
443
+ // ✅ Public Service methods; mark reads readOnly
444
+ @Transactional(readOnly = true)
445
+ public User get(Long id) { ... }
446
+ ```
447
+
448
+ ### Entities & Lombok
449
+
450
+ ```java
451
+ // ❌ @Data equals/hashCode often pulls in lazy associations
452
+ @Entity
453
+ @Data
454
+ public class User { ... }
455
+
456
+ // ✅ @Getter/@Setter; equals/hashCode on a stable business key, or null-safe id
457
+ // ⚠️ Do not include lazy associations; new unsaved entities all have null id,
458
+ // so id-only equals treats them as unequal (usually acceptable)
459
+ @Entity
460
+ @Getter
461
+ @Setter
462
+ public class User {
463
+ @Id
464
+ private Long id;
465
+
466
+ @Override
467
+ public boolean equals(Object o) {
468
+ if (this == o) return true;
469
+ if (!(o instanceof User)) return false;
470
+ return id != null && id.equals(((User) o).id);
471
+ }
472
+
473
+ @Override
474
+ public int hashCode() {
475
+ return getClass().hashCode();
476
+ }
477
+ }
478
+ ```
479
+
480
+ ### Temporal fields
481
+
482
+ Hibernate 5 supports `java.time`, but mapping `ZonedDateTime` to a zone-less `TIMESTAMP` normalizes with the JVM zone and drifts across regions. Prefer:
483
+
484
+ - Store UTC: `Instant` or `OffsetDateTime`
485
+ - Set `spring.jpa.properties.hibernate.jdbc.time_zone=UTC` when the team agrees
486
+ - Migrate legacy `java.util.Date` fields deliberately; do not add new `Date` / `@Temporal` in new code
487
+
488
+ ---
489
+
490
+ ## Exception Handling
491
+
492
+ Boot 2 does not treat Spring 6 `ProblemDetail` as a first-class citizen (that is a Boot 3 story). Use a shared `@ControllerAdvice` with clear HTTP statuses.
493
+
494
+ ```java
495
+ // ❌ Swallow exceptions, printStackTrace, return null to hide failure
496
+ try {
497
+ userService.create(user);
498
+ } catch (Exception e) {
499
+ e.printStackTrace();
500
+ return null;
501
+ }
502
+
503
+ // ✅ Domain exceptions + global handler
504
+ @RestControllerAdvice
505
+ public class GlobalExceptionHandler {
506
+ @ExceptionHandler(UserNotFoundException.class)
507
+ public ResponseEntity<ApiError> handleNotFound(UserNotFoundException e) {
508
+ return ResponseEntity.status(HttpStatus.NOT_FOUND)
509
+ .body(new ApiError("USER_NOT_FOUND", e.getMessage()));
510
+ }
511
+ }
512
+ ```
513
+
514
+ Close resources with try-with-resources (Java 7+). Avoid hand-rolled `finally { close() }` that forgets null checks.
515
+
516
+ ---
517
+
518
+ ## Testing
519
+
520
+ ```java
521
+ // ❌ @SpringBootTest for every unit test (slow and brittle)
522
+ @SpringBootTest
523
+ public class UserServiceTest { ... }
524
+
525
+ // ✅ Pure unit tests: JUnit 4/5 + Mockito
526
+ @RunWith(MockitoJUnitRunner.class) // JUnit 4
527
+ // or @ExtendWith(MockitoExtension.class) // JUnit 5
528
+ public class UserServiceTest {
529
+ @Mock private UserRepository repo;
530
+ @InjectMocks private UserService service;
531
+
532
+ @Test
533
+ public void shouldCreateUser() { ... }
534
+ }
535
+ ```
536
+
537
+ Inject `Clock` into time-sensitive logic so tests do not depend on `Instant.now()`.
538
+
539
+ Legacy stacks often use JUnit 4; if JUnit 5 is mixed in, keep one runner style per module.
540
+
541
+ ---
542
+
543
+ ## Review Checklist
544
+
545
+ ### Version & scope
546
+ - [ ] Confirmed Java 8 / Boot 2 / `javax.*` — do not demand Java 17+ APIs or `jakarta.*`
547
+ - [ ] Do not treat "use records / virtual threads / text blocks" as blocking feedback
548
+
549
+ ### Language features
550
+ - [ ] Lambdas stay short; prefer method references and standard functional interfaces
551
+ - [ ] Streams used for transform / filter / reduce, not simple side-effect loops
552
+ - [ ] Collection uses `Collectors.*` (no `Stream.toList()`)
553
+ - [ ] `toMap` handles null values and duplicate keys
554
+ - [ ] `parallelStream` is justified and has no shared mutable state
555
+ - [ ] Optional is return-type only; no `isPresent`+`get` abuse; expensive defaults use `orElseGet`
556
+ - [ ] Dates use `java.time`; no shared `SimpleDateFormat`; correct type (`Instant` vs `LocalDateTime`)
557
+ - [ ] Patterns use `yyyy`, not week-based `YYYY` (unless week-year is intentional)
558
+
559
+ ### Concurrency
560
+ - [ ] Thread pools are bounded and shut down; I/O does not use `ForkJoinPool.commonPool()`
561
+ - [ ] CompletableFuture uses an explicit Executor; `thenCompose` for nested async; timeouts and error handling present
562
+ - [ ] Shared state uses concurrent collections; no static mutable `DateFormat`
563
+
564
+ ### Spring Boot 2 / JPA
565
+ - [ ] Constructor injection; config via `@ConfigurationProperties`
566
+ - [ ] `RestTemplate` (and RequestFactory) has connect/read timeouts
567
+ - [ ] `@Transactional` on public entry points — no self-invocation / private-method failures
568
+ - [ ] No N+1; entities avoid `@Data`; temporal strategy is explicit (UTC)
569
+ - [ ] Packages stay consistently `javax.*` — no javax/jakarta mix
570
+
571
+ ### Quality
572
+ - [ ] Exceptions are not swallowed; error responses are centralized
573
+ - [ ] try-with-resources for I/O and DB resources
574
+ - [ ] Core logic has unit tests; time is injectable via `Clock`
575
+
576
+ ---
577
+
578
+ ## References
579
+
580
+ - [What to Look for in Java 8 Code (JetBrains)](https://blog.jetbrains.com/upsource/2016/08/03/what-to-look-for-in-java-8-code/)
581
+ - [JDK-8148463: Collectors.toMap fails on null values](https://bugs.openjdk.org/browse/JDK-8148463)
582
+ - [Oracle Tutorial: Parallelism](https://docs.oracle.com/javase/tutorial/collections/streams/parallelism.html)
583
+ - [Baeldung: Migrating to Java 8 Date/Time API](https://www.baeldung.com/migrating-to-java-8-date-time-api)
584
+ - [Baeldung: CompletableFuture and ThreadPool](https://www.baeldung.com/java-completablefuture-threadpool)
585
+ - [Spring Boot RestTemplate customization](https://docs.spring.io/spring-boot/docs/2.7.x/reference/html/io.html#io.rest-client.resttemplate)
586
+ - [Thorben Janssen: Hibernate/JPA Date and Time](https://thorben-janssen.com/hibernate-jpa-date-and-time/)