@sun-asterisk/sunlint 1.3.47 → 1.3.49
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.
- package/config/rules/rules-registry-generated.json +1717 -282
- package/core/architecture-integration.js +57 -15
- package/core/cli-action-handler.js +51 -36
- package/core/config-manager.js +6 -0
- package/core/config-merger.js +33 -0
- package/core/config-validator.js +37 -2
- package/core/file-targeting-service.js +148 -15
- package/core/init-command.js +118 -70
- package/core/output-service.js +12 -3
- package/core/project-detector.js +517 -0
- package/core/scoring-service.js +12 -6
- package/core/summary-report-service.js +9 -4
- package/core/tui-select.js +245 -0
- package/engines/arch-detect/rules/layered/l001-presentation-layer.js +7 -15
- package/engines/arch-detect/rules/layered/l002-business-layer.js +7 -15
- package/engines/arch-detect/rules/layered/l003-data-layer.js +7 -15
- package/engines/arch-detect/rules/layered/l004-model-layer.js +7 -15
- package/engines/arch-detect/rules/layered/l005-layer-separation.js +22 -2
- package/engines/arch-detect/rules/layered/l006-dependency-direction.js +8 -5
- package/engines/arch-detect/rules/modular/m005-no-deep-imports.js +67 -29
- package/engines/arch-detect/rules/presentation/pr001-view-layer.js +16 -9
- package/engines/arch-detect/rules/presentation/pr006-router-layer.js +33 -8
- package/engines/arch-detect/rules/presentation/pr007-interactor-layer.js +35 -6
- package/engines/arch-detect/rules/project-scanner/ps003-framework-detection.js +56 -10
- package/engines/impact/cli.js +54 -39
- package/engines/impact/config/default-config.js +105 -5
- package/engines/impact/core/impact-analyzer.js +12 -15
- package/engines/impact/core/utils/gitignore-parser.js +123 -0
- package/engines/impact/core/utils/method-call-graph.js +272 -87
- package/origin-rules/dart-en.md +1 -1
- package/origin-rules/go-en.md +231 -0
- package/origin-rules/php-en.md +107 -0
- package/origin-rules/python-en.md +113 -0
- package/origin-rules/ruby-en.md +607 -0
- package/package.json +1 -1
- package/scripts/copy-arch-detect.js +5 -1
- package/scripts/copy-impact-analyzer.js +5 -1
- package/scripts/generate-rules-registry.js +30 -14
- package/skill-assets/sunlint-code-quality/SKILL.md +3 -2
- package/skill-assets/sunlint-code-quality/rules/dart/C006-verb-noun-functions.md +45 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C013-no-dead-code.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C014-dependency-injection.md +92 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C017-no-constructor-logic.md +62 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C018-generic-errors.md +57 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C019-error-log-level.md +50 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C020-no-unused-imports.md +46 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C022-no-unused-variables.md +50 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C023-no-duplicate-names.md +56 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C024-centralize-constants.md +75 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C029-catch-log-root-cause.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C030-custom-error-classes.md +86 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C033-separate-data-access.md +90 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C035-error-context-logging.md +62 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C041-no-hardcoded-secrets.md +75 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C042-boolean-naming.md +73 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C052-widget-parsing.md +84 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C060-superclass-logic.md +91 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C067-no-hardcoded-config.md +108 -0
- package/skill-assets/sunlint-code-quality/rules/go/G001-explicit-error-handling.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/go/G002-context-first-argument.md +44 -0
- package/skill-assets/sunlint-code-quality/rules/go/G003-receiver-consistency.md +38 -0
- package/skill-assets/sunlint-code-quality/rules/go/G004-avoid-panic.md +49 -0
- package/skill-assets/sunlint-code-quality/rules/go/G005-goroutine-leak-prevention.md +49 -0
- package/skill-assets/sunlint-code-quality/rules/go/G006-interface-consumer-definition.md +45 -0
- package/skill-assets/sunlint-code-quality/rules/go/GN001-gin-binding-validation.md +57 -0
- package/skill-assets/sunlint-code-quality/rules/go/GN002-gin-error-response.md +48 -0
- package/skill-assets/sunlint-code-quality/rules/go/GN003-graceful-shutdown.md +57 -0
- package/skill-assets/sunlint-code-quality/rules/go/GN004-gin-route-logical-grouping.md +54 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/AGENTS.md +149 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN001-abort-after-response.md +75 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN002-request-context.md +64 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN003-bind-error-handling.md +70 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN004-dependency-injection.md +78 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN005-route-groups-middleware.md +71 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN006-http-status-codes.md +91 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN007-release-mode.md +64 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN008-struct-validation-tags.md +90 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN009-recovery-middleware.md +68 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN010-context-scope.md +68 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN011-middleware-concerns.md +92 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN012-no-log-sensitive.md +84 -0
- package/skill-assets/sunlint-code-quality/rules/java/J001-try-with-resources.md +86 -0
- package/skill-assets/sunlint-code-quality/rules/java/J002-equals-and-hashcode.md +88 -0
- package/skill-assets/sunlint-code-quality/rules/java/J003-string-comparison.md +72 -0
- package/skill-assets/sunlint-code-quality/rules/java/J004-use-java-time.md +91 -0
- package/skill-assets/sunlint-code-quality/rules/java/J005-no-print-stack-trace.md +80 -0
- package/skill-assets/sunlint-code-quality/rules/java/J006-no-system-println.md +89 -0
- package/skill-assets/sunlint-code-quality/rules/java/J007-proper-logger.md +91 -0
- package/skill-assets/sunlint-code-quality/rules/java/J008-thread-safe-singleton.md +119 -0
- package/skill-assets/sunlint-code-quality/rules/java/J009-utility-class-constructor.md +82 -0
- package/skill-assets/sunlint-code-quality/rules/java/J010-preserve-stack-trace.md +119 -0
- package/skill-assets/sunlint-code-quality/rules/java/J011-null-safe-compare.md +88 -0
- package/skill-assets/sunlint-code-quality/rules/java/J012-use-enum-collections.md +104 -0
- package/skill-assets/sunlint-code-quality/rules/java/J013-return-empty-not-null.md +102 -0
- package/skill-assets/sunlint-code-quality/rules/java/J014-hardcoded-crypto-key.md +108 -0
- package/skill-assets/sunlint-code-quality/rules/java/J015-optional-instead-of-null.md +109 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/AGENTS.md +124 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV001-form-request-validation.md +64 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV002-eager-load-no-n-plus-1.md +58 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV003-config-not-env.md +54 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV004-fillable-mass-assignment.md +51 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV005-policies-gates-authorization.md +71 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV006-queue-heavy-tasks.md +68 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV007-hash-passwords.md +51 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV008-route-model-binding.md +67 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV009-api-resources.md +72 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV010-chunk-large-datasets.md +58 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV011-db-transactions.md +73 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV012-service-layer.md +78 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV013-testing-factories.md +75 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV014-service-container.md +61 -0
- package/skill-assets/sunlint-code-quality/rules/python/P001-mutable-default-argument.md +55 -0
- package/skill-assets/sunlint-code-quality/rules/python/P002-specify-file-encoding.md +45 -0
- package/skill-assets/sunlint-code-quality/rules/python/P003-context-manager-for-resources.md +54 -0
- package/skill-assets/sunlint-code-quality/rules/python/P004-no-bare-except.md +65 -0
- package/skill-assets/sunlint-code-quality/rules/python/P005-use-isinstance.md +60 -0
- package/skill-assets/sunlint-code-quality/rules/python/P006-timezone-aware-datetime.md +58 -0
- package/skill-assets/sunlint-code-quality/rules/python/P007-use-pathlib.md +62 -0
- package/skill-assets/sunlint-code-quality/rules/python/P008-no-wildcard-import.md +52 -0
- package/skill-assets/sunlint-code-quality/rules/python/P009-logging-lazy-format.md +50 -0
- package/skill-assets/sunlint-code-quality/rules/python/P010-exception-chaining.md +57 -0
- package/skill-assets/sunlint-code-quality/rules/python/P011-subprocess-check.md +59 -0
- package/skill-assets/sunlint-code-quality/rules/python/P012-requests-timeout.md +70 -0
- package/skill-assets/sunlint-code-quality/rules/python/P013-no-global-statement.md +73 -0
- package/skill-assets/sunlint-code-quality/rules/python/P014-no-modify-collection-while-iterating.md +66 -0
- package/skill-assets/sunlint-code-quality/rules/python/P015-prefer-fstrings.md +61 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/AGENTS.md +121 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR001-strong-parameters.md +55 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR002-eager-load-includes.md +51 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR003-service-objects.md +99 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR004-active-job-background.md +67 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR005-pagination.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR006-find-each-batches.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR007-http-status-codes.md +76 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR008-before-action-auth.md +77 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR009-rails-credentials.md +61 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR010-scopes.md +57 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR011-counter-cache.md +59 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR012-render-json-status.md +42 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C006-verb-noun-functions.md +37 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C013-no-dead-code.md +55 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C014-dependency-injection.md +69 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C017-no-constructor-logic.md +66 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C018-generic-errors.md +64 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C019-error-log-level.md +64 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C020-no-unused-imports.md +47 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C022-no-unused-variables.md +46 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C023-no-duplicate-names.md +55 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C024-centralize-constants.md +68 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C029-catch-log-root-cause.md +69 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C030-custom-error-classes.md +77 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C033-separate-data-access.md +89 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C035-error-context-logging.md +66 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C041-no-hardcoded-secrets.md +65 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C042-boolean-naming.md +60 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C052-controller-parsing.md +67 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C060-superclass-logic.md +95 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C067-no-hardcoded-config.md +80 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S003-sql-injection.md +65 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S004-no-log-credentials.md +67 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S005-server-authorization.md +73 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S006-default-credentials.md +76 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S007-output-encoding.md +96 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S009-approved-crypto.md +86 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S010-csprng.md +71 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S011-insecure-deserialization.md +74 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S012-secrets-management.md +81 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S013-tls-connections.md +67 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S017-parameterized-queries.md +86 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S019-session-management.md +131 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S020-kvc-injection.md +91 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S025-input-validation.md +125 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S029-brute-force-protection.md +120 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S036-path-traversal.md +102 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S039-tls-certificate-validation.md +109 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S041-logout-invalidation.md +103 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S043-password-hashing.md +116 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S044-critical-changes-reauth.md +145 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S045-debug-info-exposure.md +116 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S046-unvalidated-redirect.md +140 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S051-token-expiry.md +134 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S053-jwt-validation.md +139 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S059-background-snapshot-protection.md +113 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S060-data-protection-api.md +106 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S061-jailbreak-detection.md +132 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use java.time Instead of Legacy Date and Calendar
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: java.util.Date and Calendar are mutable, thread-unsafe, and poorly designed; java.time provides a modern, immutable, thread-safe API
|
|
5
|
+
tags: best-practice, java, modernization, thread-safety
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use java.time Instead of Legacy Date and Calendar
|
|
9
|
+
|
|
10
|
+
`java.util.Date` and `java.util.Calendar` were introduced in Java 1.0 and 1.1 respectively. They are mutable (not thread-safe), have confusing APIs (months are 0-indexed in `Calendar`), and are generally considered a design failure. Since Java 8, the `java.time` package (JSR-310) provides a comprehensive, immutable, thread-safe date/time API.
|
|
11
|
+
|
|
12
|
+
**Incorrect (using legacy Date/Calendar):**
|
|
13
|
+
|
|
14
|
+
```java
|
|
15
|
+
import java.util.Date;
|
|
16
|
+
import java.util.Calendar;
|
|
17
|
+
|
|
18
|
+
public class OrderService {
|
|
19
|
+
public Date calculateDeliveryDate(Date orderDate, int days) {
|
|
20
|
+
Calendar cal = Calendar.getInstance();
|
|
21
|
+
cal.setTime(orderDate);
|
|
22
|
+
cal.add(Calendar.DAY_OF_MONTH, days); // Calendar.DAY_OF_MONTH is error-prone
|
|
23
|
+
return cal.getTime(); // mutable — caller can modify the returned value
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public boolean isExpired(Date expiryDate) {
|
|
27
|
+
return new Date().after(expiryDate); // new Date() for "now" is verbose
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Storing timestamps
|
|
31
|
+
private Date createdAt = new Date(); // mutable — bad for records
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Correct (using java.time):**
|
|
36
|
+
|
|
37
|
+
```java
|
|
38
|
+
import java.time.LocalDate;
|
|
39
|
+
import java.time.LocalDateTime;
|
|
40
|
+
import java.time.ZonedDateTime;
|
|
41
|
+
import java.time.ZoneId;
|
|
42
|
+
import java.time.Instant;
|
|
43
|
+
import java.time.Duration;
|
|
44
|
+
|
|
45
|
+
public class OrderService {
|
|
46
|
+
public LocalDate calculateDeliveryDate(LocalDate orderDate, int days) {
|
|
47
|
+
return orderDate.plusDays(days); // fluent, immutable, clear
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public boolean isExpired(LocalDateTime expiryDateTime) {
|
|
51
|
+
return LocalDateTime.now().isAfter(expiryDateTime);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Storing timestamps
|
|
55
|
+
private final Instant createdAt = Instant.now(); // immutable
|
|
56
|
+
|
|
57
|
+
// Working with timezones
|
|
58
|
+
public ZonedDateTime getOrderTimeInJST(Instant instant) {
|
|
59
|
+
return instant.atZone(ZoneId.of("Asia/Tokyo"));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Duration/period calculation
|
|
63
|
+
public long getDaysUntilDeadline(LocalDate deadline) {
|
|
64
|
+
return Duration.between(
|
|
65
|
+
LocalDate.now().atStartOfDay(),
|
|
66
|
+
deadline.atStartOfDay()
|
|
67
|
+
).toDays();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**Migration guide:**
|
|
73
|
+
|
|
74
|
+
| Legacy | java.time replacement |
|
|
75
|
+
|--------|----------------------|
|
|
76
|
+
| `java.util.Date` | `java.time.Instant` (for UTC timestamps) |
|
|
77
|
+
| `java.util.Calendar` | `java.time.ZonedDateTime` |
|
|
78
|
+
| `java.sql.Date` | `java.time.LocalDate` |
|
|
79
|
+
| `java.sql.Timestamp` | `java.time.LocalDateTime` or `Instant` |
|
|
80
|
+
| `SimpleDateFormat` | `java.time.format.DateTimeFormatter` |
|
|
81
|
+
|
|
82
|
+
**Key types in java.time:**
|
|
83
|
+
- `LocalDate` — date without time/timezone (e.g., 2024-01-15)
|
|
84
|
+
- `LocalTime` — time without date/timezone
|
|
85
|
+
- `LocalDateTime` — date + time, no timezone
|
|
86
|
+
- `ZonedDateTime` — date + time + timezone
|
|
87
|
+
- `Instant` — a Unix timestamp (UTC)
|
|
88
|
+
- `Duration` — time-based amount (seconds, nanoseconds)
|
|
89
|
+
- `Period` — date-based amount (years, months, days)
|
|
90
|
+
|
|
91
|
+
**Tools:** PMD (`ReplaceJavaUtilDate`, `ReplaceJavaUtilCalendar`), SonarQube (`S2143`)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Do Not Use printStackTrace(); Use a Logger
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: printStackTrace() outputs to stderr, cannot be controlled by log configuration, and may expose sensitive stack traces in production
|
|
5
|
+
tags: logging, best-practice, java, security
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Do Not Use printStackTrace(); Use a Logger
|
|
9
|
+
|
|
10
|
+
`e.printStackTrace()` writes directly to `System.err` — it bypasses the logging framework entirely, cannot be filtered, redirected, or formatted by log configuration, and in production environments the output may be lost or may expose sensitive internal details (class names, method names, library versions) to attackers.
|
|
11
|
+
|
|
12
|
+
**Incorrect (using printStackTrace):**
|
|
13
|
+
|
|
14
|
+
```java
|
|
15
|
+
public void processPayment(Payment payment) {
|
|
16
|
+
try {
|
|
17
|
+
paymentGateway.charge(payment);
|
|
18
|
+
} catch (PaymentException e) {
|
|
19
|
+
e.printStackTrace(); // writes to System.err — uncontrolled output
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public User findUser(Long id) {
|
|
24
|
+
try {
|
|
25
|
+
return userRepository.findById(id).orElseThrow();
|
|
26
|
+
} catch (Exception e) {
|
|
27
|
+
e.printStackTrace(); // loses structured logging, no context
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Correct (using a logger):**
|
|
34
|
+
|
|
35
|
+
```java
|
|
36
|
+
import org.slf4j.Logger;
|
|
37
|
+
import org.slf4j.LoggerFactory;
|
|
38
|
+
|
|
39
|
+
public class PaymentService {
|
|
40
|
+
private static final Logger logger = LoggerFactory.getLogger(PaymentService.class);
|
|
41
|
+
|
|
42
|
+
public void processPayment(Payment payment) {
|
|
43
|
+
try {
|
|
44
|
+
paymentGateway.charge(payment);
|
|
45
|
+
} catch (PaymentException e) {
|
|
46
|
+
// Log at ERROR with context AND the exception (includes stack trace in log output)
|
|
47
|
+
logger.error("Payment failed for orderId={}, amount={}",
|
|
48
|
+
payment.getOrderId(), payment.getAmount(), e);
|
|
49
|
+
throw new PaymentProcessingException("Payment failed", e);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
public User findUser(Long id) {
|
|
54
|
+
try {
|
|
55
|
+
return userRepository.findById(id)
|
|
56
|
+
.orElseThrow(() -> new UserNotFoundException("User not found: " + id));
|
|
57
|
+
} catch (UserNotFoundException e) {
|
|
58
|
+
logger.warn("User lookup failed: userId={}", id, e);
|
|
59
|
+
throw e;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Differences between logging levels for exceptions:**
|
|
66
|
+
|
|
67
|
+
```java
|
|
68
|
+
// ERROR: unexpected, system-level failure, requires immediate attention
|
|
69
|
+
logger.error("Database connection lost", e);
|
|
70
|
+
|
|
71
|
+
// WARN: recoverable error, degraded functionality
|
|
72
|
+
logger.warn("Payment gateway timeout, retrying: orderId={}", orderId, e);
|
|
73
|
+
|
|
74
|
+
// INFO: expected business exception (user not found, validation fail)
|
|
75
|
+
logger.info("Login attempt failed: username={}", username, e);
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Passing the exception as the last argument** to SLF4J/Logback/Log4j2 automatically appends a full stack trace to the log entry — equivalent to calling `printStackTrace()` but through the proper logging pipeline.
|
|
79
|
+
|
|
80
|
+
**Tools:** PMD (`AvoidPrintStackTrace`), SonarQube (`S1148`), SpotBugs
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Do Not Use System.out.println in Production Code
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: System.out/err output bypasses logging configuration, cannot be disabled without code changes, and may leak sensitive data
|
|
5
|
+
tags: logging, best-practice, java, production
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Do Not Use System.out.println in Production Code
|
|
9
|
+
|
|
10
|
+
`System.out.println` and `System.err.println` write directly to standard output/error streams. In production:
|
|
11
|
+
- They cannot be filtered, leveled, or structured.
|
|
12
|
+
- They cannot be turned off without changing code and redeploying.
|
|
13
|
+
- They do not carry context (timestamp, thread, class name) unless added manually.
|
|
14
|
+
- Output may be lost in containerized or cloud environments.
|
|
15
|
+
- They may print sensitive data (credentials, PII) in server logs.
|
|
16
|
+
|
|
17
|
+
**Incorrect:**
|
|
18
|
+
|
|
19
|
+
```java
|
|
20
|
+
public class AuthService {
|
|
21
|
+
public User authenticate(String username, String password) {
|
|
22
|
+
System.out.println("Authenticating user: " + username); // logs PII
|
|
23
|
+
System.out.println("password = " + password); // SECURITY RISK
|
|
24
|
+
try {
|
|
25
|
+
User user = userRepository.findByUsername(username);
|
|
26
|
+
System.out.println("User found: " + user.getEmail()); // PII leak
|
|
27
|
+
return user;
|
|
28
|
+
} catch (Exception e) {
|
|
29
|
+
System.err.println("Auth failed: " + e.getMessage()); // unstructured
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public class DataProcessor {
|
|
36
|
+
public void process(List<Order> orders) {
|
|
37
|
+
System.out.println("Processing " + orders.size() + " orders"); // no structured log
|
|
38
|
+
for (Order order : orders) {
|
|
39
|
+
System.out.println("Order: " + order); // cannot be filtered
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Correct (using SLF4J/Logback):**
|
|
46
|
+
|
|
47
|
+
```java
|
|
48
|
+
import org.slf4j.Logger;
|
|
49
|
+
import org.slf4j.LoggerFactory;
|
|
50
|
+
|
|
51
|
+
public class AuthService {
|
|
52
|
+
private static final Logger logger = LoggerFactory.getLogger(AuthService.class);
|
|
53
|
+
|
|
54
|
+
public User authenticate(String username, String password) {
|
|
55
|
+
logger.debug("Authenticating user: username={}", username);
|
|
56
|
+
// Never log the password
|
|
57
|
+
try {
|
|
58
|
+
User user = userRepository.findByUsername(username);
|
|
59
|
+
logger.info("Authentication successful: userId={}", user.getId());
|
|
60
|
+
return user;
|
|
61
|
+
} catch (UserNotFoundException e) {
|
|
62
|
+
logger.warn("Authentication failed: username={}", username);
|
|
63
|
+
throw e;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
public class DataProcessor {
|
|
69
|
+
private static final Logger logger = LoggerFactory.getLogger(DataProcessor.class);
|
|
70
|
+
|
|
71
|
+
public void process(List<Order> orders) {
|
|
72
|
+
logger.info("Starting order processing: count={}", orders.size());
|
|
73
|
+
for (Order order : orders) {
|
|
74
|
+
logger.debug("Processing order: orderId={}", order.getId());
|
|
75
|
+
}
|
|
76
|
+
logger.info("Order processing complete: count={}", orders.size());
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Exception for tests and scripts:**
|
|
82
|
+
|
|
83
|
+
`System.out.println` is acceptable only in:
|
|
84
|
+
- `main()` methods of CLI tools and standalone scripts
|
|
85
|
+
- Unit test setup code (though test frameworks' reporters are better)
|
|
86
|
+
|
|
87
|
+
Mark these with a `@SuppressWarnings("PMD.SystemPrintln")` annotation if needed.
|
|
88
|
+
|
|
89
|
+
**Tools:** PMD (`SystemPrintln`), SonarQube (`S106`), Checkstyle
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Logger Must Be private static final
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: improper logger declaration can cause memory leaks, serialization issues, and incorrect class attribution in log output
|
|
5
|
+
tags: logging, best-practice, java, naming
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Logger Must Be private static final
|
|
9
|
+
|
|
10
|
+
A logger field should always be declared as `private static final` and named after the class it belongs to. This ensures:
|
|
11
|
+
- **`private`**: Not accessible from outside the class, encapsulated properly.
|
|
12
|
+
- **`static`**: Shared across all instances — not re-created per object, avoiding memory overhead.
|
|
13
|
+
- **`final`**: Cannot be reassigned — prevents accidental reassignment.
|
|
14
|
+
- **Named with the correct class**: Ensures log entries display the correct source class.
|
|
15
|
+
|
|
16
|
+
**Incorrect:**
|
|
17
|
+
|
|
18
|
+
```java
|
|
19
|
+
// Not static — a new logger is created for every object instance
|
|
20
|
+
public class OrderService {
|
|
21
|
+
private final Logger logger = LoggerFactory.getLogger(OrderService.class); // bad: not static
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Not private — accessible from outside
|
|
25
|
+
public class UserService {
|
|
26
|
+
public static final Logger LOGGER = LoggerFactory.getLogger(UserService.class); // bad: public
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Wrong class name — log entries will show wrong class
|
|
30
|
+
public class PaymentService {
|
|
31
|
+
private static final Logger logger = LoggerFactory.getLogger(OrderService.class); // bad: wrong class
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Using java.util.logging without naming it LOG or logger consistently
|
|
35
|
+
public class ReportService {
|
|
36
|
+
private static Logger l = Logger.getLogger("reports"); // bad: name "l" is vague; string name is wrong
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Correct:**
|
|
41
|
+
|
|
42
|
+
```java
|
|
43
|
+
import org.slf4j.Logger;
|
|
44
|
+
import org.slf4j.LoggerFactory;
|
|
45
|
+
|
|
46
|
+
public class OrderService {
|
|
47
|
+
private static final Logger logger = LoggerFactory.getLogger(OrderService.class);
|
|
48
|
+
|
|
49
|
+
public void createOrder(Order order) {
|
|
50
|
+
logger.info("Creating order: orderId={}", order.getId());
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// For java.util.logging (standard library):
|
|
55
|
+
import java.util.logging.Logger;
|
|
56
|
+
|
|
57
|
+
public class ReportService {
|
|
58
|
+
private static final Logger logger = Logger.getLogger(ReportService.class.getName());
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// For Log4j 2:
|
|
62
|
+
import org.apache.logging.log4j.Logger;
|
|
63
|
+
import org.apache.logging.log4j.LogManager;
|
|
64
|
+
|
|
65
|
+
public class NotificationService {
|
|
66
|
+
private static final Logger logger = LogManager.getLogger(NotificationService.class);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// With Lombok @Slf4j annotation (auto-generates private static final logger):
|
|
70
|
+
import lombok.extern.slf4j.Slf4j;
|
|
71
|
+
|
|
72
|
+
@Slf4j
|
|
73
|
+
public class InventoryService {
|
|
74
|
+
public void checkStock(Long productId) {
|
|
75
|
+
log.info("Checking stock: productId={}", productId); // 'log' is auto-generated
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**Standard conventions:**
|
|
81
|
+
- Variable name: `logger` (preferred) or `log` (common with Lombok)
|
|
82
|
+
- Avoid `LOG`, `LOGGER` (all-caps suggests a compile-time constant, loggers are runtime objects)
|
|
83
|
+
- Always pass the owning class literal: `LoggerFactory.getLogger(MyClass.class)`
|
|
84
|
+
|
|
85
|
+
**Frameworks supported:**
|
|
86
|
+
- SLF4J + Logback (recommended)
|
|
87
|
+
- Log4j 2
|
|
88
|
+
- `java.util.logging` (JUL, standard library)
|
|
89
|
+
- Lombok `@Slf4j`, `@Log4j2`, `@CommonsLog`
|
|
90
|
+
|
|
91
|
+
**Tools:** PMD (`ProperLogger`), SonarQube (`S1312`), IntelliJ Inspections
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Implement Thread-Safe Singleton Correctly
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: a non-thread-safe singleton can create multiple instances under concurrent load, breaking application invariants
|
|
5
|
+
tags: concurrency, design-pattern, java, thread-safety
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Implement Thread-Safe Singleton Correctly
|
|
9
|
+
|
|
10
|
+
The Singleton pattern ensures a class has only one instance. In multi-threaded Java applications, a naive singleton implementation can create multiple instances when two threads enter `getInstance()` simultaneously. This is a classic race condition.
|
|
11
|
+
|
|
12
|
+
**Incorrect (not thread-safe):**
|
|
13
|
+
|
|
14
|
+
```java
|
|
15
|
+
// Lazy initialization — race condition when two threads call getInstance() simultaneously
|
|
16
|
+
public class ConfigManager {
|
|
17
|
+
private static ConfigManager instance;
|
|
18
|
+
|
|
19
|
+
private ConfigManager() {
|
|
20
|
+
loadConfiguration();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public static ConfigManager getInstance() {
|
|
24
|
+
if (instance == null) { // thread A and B both see null
|
|
25
|
+
instance = new ConfigManager(); // both create an instance — BUG
|
|
26
|
+
}
|
|
27
|
+
return instance;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**Incorrect (broken double-checked locking without volatile):**
|
|
33
|
+
|
|
34
|
+
```java
|
|
35
|
+
public class DatabasePool {
|
|
36
|
+
private static DatabasePool instance; // missing volatile — broken!
|
|
37
|
+
|
|
38
|
+
public static DatabasePool getInstance() {
|
|
39
|
+
if (instance == null) {
|
|
40
|
+
synchronized (DatabasePool.class) {
|
|
41
|
+
if (instance == null) {
|
|
42
|
+
instance = new DatabasePool(); // may be visible as partially initialized
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return instance;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Correct (Initialization-on-demand holder idiom — preferred):**
|
|
52
|
+
|
|
53
|
+
```java
|
|
54
|
+
public class ConfigManager {
|
|
55
|
+
private ConfigManager() {
|
|
56
|
+
loadConfiguration();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// JVM class loading guarantees thread-safe, lazy initialization
|
|
60
|
+
private static final class Holder {
|
|
61
|
+
private static final ConfigManager INSTANCE = new ConfigManager();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
public static ConfigManager getInstance() {
|
|
65
|
+
return Holder.INSTANCE;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Correct (double-checked locking with volatile — acceptable):**
|
|
71
|
+
|
|
72
|
+
```java
|
|
73
|
+
public class DatabasePool {
|
|
74
|
+
private static volatile DatabasePool instance; // volatile is required
|
|
75
|
+
|
|
76
|
+
private DatabasePool() {}
|
|
77
|
+
|
|
78
|
+
public static DatabasePool getInstance() {
|
|
79
|
+
if (instance == null) {
|
|
80
|
+
synchronized (DatabasePool.class) {
|
|
81
|
+
if (instance == null) {
|
|
82
|
+
instance = new DatabasePool();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return instance;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Correct (enum singleton — simplest and safest):**
|
|
92
|
+
|
|
93
|
+
```java
|
|
94
|
+
// Enum singletons are thread-safe by JVM specification and handle serialization correctly
|
|
95
|
+
public enum AppConfig {
|
|
96
|
+
INSTANCE;
|
|
97
|
+
|
|
98
|
+
private final String dbUrl;
|
|
99
|
+
|
|
100
|
+
AppConfig() {
|
|
101
|
+
dbUrl = System.getenv("DB_URL");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
public String getDbUrl() {
|
|
105
|
+
return dbUrl;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Usage:
|
|
110
|
+
AppConfig.INSTANCE.getDbUrl();
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**Preferred approaches (in order):**
|
|
114
|
+
1. **Enum singleton** — safest, handles serialization automatically
|
|
115
|
+
2. **Initialization-on-demand holder** — lazy, thread-safe, no synchronization overhead
|
|
116
|
+
3. **Double-checked locking with `volatile`** — acceptable for legacy code
|
|
117
|
+
4. **Spring `@Component` / `@Service`** — let the DI container manage lifecycle (best for application code)
|
|
118
|
+
|
|
119
|
+
**Tools:** PMD (`NonThreadSafeSingleton`, `DoubleCheckedLocking`), FindBugs/SpotBugs (`DC_DOUBLECHECK`, `ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD`), SonarQube (`S2168`)
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Utility Classes Must Have a Private Constructor
|
|
3
|
+
impact: LOW
|
|
4
|
+
impactDescription: utility classes with public or default constructors can be accidentally instantiated, wasting memory and exposing confusing API
|
|
5
|
+
tags: design, best-practice, java, clean-code
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Utility Classes Must Have a Private Constructor
|
|
9
|
+
|
|
10
|
+
A **utility class** is a class that consists only of `static` methods and/or constants — it is never meant to be instantiated (e.g., `java.util.Collections`, `java.util.Arrays`, `Math`). Without a private constructor, a utility class:
|
|
11
|
+
- Can be instantiated by mistake, creating useless objects.
|
|
12
|
+
- May confuse callers about whether an instance has state.
|
|
13
|
+
- May be accidentally extended, inheriting a public constructor.
|
|
14
|
+
|
|
15
|
+
**Incorrect:**
|
|
16
|
+
|
|
17
|
+
```java
|
|
18
|
+
// No constructor — Java adds a public default constructor automatically
|
|
19
|
+
public class StringUtils {
|
|
20
|
+
public static String capitalize(String s) { ... }
|
|
21
|
+
public static boolean isBlank(String s) { ... }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Default-visibility constructor — package-accessible
|
|
25
|
+
public class MathUtils {
|
|
26
|
+
MathUtils() {} // package-private, should be private
|
|
27
|
+
public static int add(int a, int b) { return a + b; }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Can be instantiated:
|
|
31
|
+
StringUtils utils = new StringUtils(); // meaningless object
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Correct:**
|
|
35
|
+
|
|
36
|
+
```java
|
|
37
|
+
public final class StringUtils { // final prevents subclassing
|
|
38
|
+
private StringUtils() {
|
|
39
|
+
// Utility class — do not instantiate
|
|
40
|
+
throw new UnsupportedOperationException("Utility class cannot be instantiated");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
public static String capitalize(String s) {
|
|
44
|
+
if (s == null || s.isEmpty()) return s;
|
|
45
|
+
return Character.toUpperCase(s.charAt(0)) + s.substring(1).toLowerCase();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public static boolean isBlank(String s) {
|
|
49
|
+
return s == null || s.trim().isEmpty();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
public final class DateUtils {
|
|
54
|
+
private DateUtils() {
|
|
55
|
+
throw new UnsupportedOperationException("Utility class");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public static LocalDate parseDate(String date) {
|
|
59
|
+
return LocalDate.parse(date, DateTimeFormatter.ISO_LOCAL_DATE);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**Using Lombok `@UtilityClass`:**
|
|
65
|
+
|
|
66
|
+
```java
|
|
67
|
+
import lombok.experimental.UtilityClass;
|
|
68
|
+
|
|
69
|
+
@UtilityClass // auto-generates private constructor + throws exception + makes class final
|
|
70
|
+
public class ValidationUtils {
|
|
71
|
+
public boolean isValidEmail(String email) {
|
|
72
|
+
return email != null && email.matches("^[\\w.-]+@[\\w.-]+\\.[a-zA-Z]{2,}$");
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Conditions for a utility class:**
|
|
78
|
+
- All methods are `static`
|
|
79
|
+
- No instance fields (or only `static final` constants)
|
|
80
|
+
- The class is not designed as a base class
|
|
81
|
+
|
|
82
|
+
**Tools:** Checkstyle (`HideUtilityClassConstructor`), PMD (`UseUtilityClass`), SonarQube (`S1118`)
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Preserve Stack Trace When Rethrowing Exceptions
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: discarding the original exception's stack trace makes debugging nearly impossible — the root cause is permanently lost
|
|
5
|
+
tags: error-handling, best-practice, java, debugging
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Preserve Stack Trace When Rethrowing Exceptions
|
|
9
|
+
|
|
10
|
+
When catching an exception and throwing a new one, always pass the original exception as the **cause**. If you only rethrow a new exception with `e.getMessage()` or no cause at all, the original stack trace — which shows where the error actually originated — is permanently discarded.
|
|
11
|
+
|
|
12
|
+
**Incorrect (losing the stack trace):**
|
|
13
|
+
|
|
14
|
+
```java
|
|
15
|
+
public void processOrder(Order order) throws OrderException {
|
|
16
|
+
try {
|
|
17
|
+
paymentService.charge(order.getPayment());
|
|
18
|
+
} catch (PaymentException e) {
|
|
19
|
+
// Message-only: root cause stack trace is lost
|
|
20
|
+
throw new OrderException("Payment failed: " + e.getMessage());
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public User loadUser(Long id) throws ServiceException {
|
|
25
|
+
try {
|
|
26
|
+
return userRepository.findById(id).orElseThrow();
|
|
27
|
+
} catch (Exception e) {
|
|
28
|
+
// No cause: impossible to trace back to the original error
|
|
29
|
+
throw new ServiceException("User not found");
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public void sendEmail(String to, String body) {
|
|
34
|
+
try {
|
|
35
|
+
emailClient.send(to, body);
|
|
36
|
+
} catch (EmailException e) {
|
|
37
|
+
logger.error("Email failed"); // No exception logged — stack trace lost
|
|
38
|
+
throw new RuntimeException("Email failed"); // No cause chained
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Correct (preserving the stack trace):**
|
|
44
|
+
|
|
45
|
+
```java
|
|
46
|
+
public void processOrder(Order order) throws OrderException {
|
|
47
|
+
try {
|
|
48
|
+
paymentService.charge(order.getPayment());
|
|
49
|
+
} catch (PaymentException e) {
|
|
50
|
+
// Pass the original exception as the cause
|
|
51
|
+
throw new OrderException("Payment failed for orderId=" + order.getId(), e);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public User loadUser(Long id) throws ServiceException {
|
|
56
|
+
try {
|
|
57
|
+
return userRepository.findById(id)
|
|
58
|
+
.orElseThrow(() -> new UserNotFoundException("User not found: id=" + id));
|
|
59
|
+
} catch (UserNotFoundException e) {
|
|
60
|
+
throw new ServiceException("User lookup failed: id=" + id, e); // chained
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
public void sendEmail(String to, String body) {
|
|
65
|
+
try {
|
|
66
|
+
emailClient.send(to, body);
|
|
67
|
+
} catch (EmailException e) {
|
|
68
|
+
logger.error("Email sending failed: recipient={}", to, e); // log with cause
|
|
69
|
+
throw new NotificationException("Email failed", e); // chain the cause
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Using initCause for exceptions without cause constructors:**
|
|
75
|
+
|
|
76
|
+
```java
|
|
77
|
+
try {
|
|
78
|
+
someOperation();
|
|
79
|
+
} catch (SomeException e) {
|
|
80
|
+
RuntimeException wrapped = new RuntimeException("Operation failed");
|
|
81
|
+
wrapped.initCause(e); // alternative to constructor argument
|
|
82
|
+
throw wrapped;
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Inspecting cause chains:**
|
|
87
|
+
|
|
88
|
+
```java
|
|
89
|
+
// Cause chain is accessible at runtime
|
|
90
|
+
try {
|
|
91
|
+
processOrder(order);
|
|
92
|
+
} catch (OrderException e) {
|
|
93
|
+
Throwable cause = e.getCause(); // retrieves PaymentException
|
|
94
|
+
logger.error("Root cause: {}", cause.getMessage());
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**Adding suppressed exceptions (try-with-resources pattern):**
|
|
99
|
+
|
|
100
|
+
```java
|
|
101
|
+
// When closing a resource also throws — use addSuppressed
|
|
102
|
+
Exception primary = null;
|
|
103
|
+
try {
|
|
104
|
+
doWork();
|
|
105
|
+
} catch (Exception e) {
|
|
106
|
+
primary = e;
|
|
107
|
+
throw e;
|
|
108
|
+
} finally {
|
|
109
|
+
try {
|
|
110
|
+
resource.close();
|
|
111
|
+
} catch (Exception closeEx) {
|
|
112
|
+
if (primary != null) {
|
|
113
|
+
primary.addSuppressed(closeEx); // preserves both
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**Tools:** PMD (`PreserveStackTrace`), SpotBugs (`REC_CATCH_EXCEPTION`), SonarQube (`S1166`)
|