agentic-team-templates 0.13.2 → 0.14.0
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/README.md +6 -1
- package/package.json +1 -1
- package/src/index.js +22 -2
- package/src/index.test.js +5 -0
- package/templates/cpp-expert/.cursorrules/concurrency.md +211 -0
- package/templates/cpp-expert/.cursorrules/error-handling.md +170 -0
- package/templates/cpp-expert/.cursorrules/memory-and-ownership.md +220 -0
- package/templates/cpp-expert/.cursorrules/modern-cpp.md +211 -0
- package/templates/cpp-expert/.cursorrules/overview.md +87 -0
- package/templates/cpp-expert/.cursorrules/performance.md +223 -0
- package/templates/cpp-expert/.cursorrules/testing.md +230 -0
- package/templates/cpp-expert/.cursorrules/tooling.md +312 -0
- package/templates/cpp-expert/CLAUDE.md +242 -0
- package/templates/csharp-expert/.cursorrules/aspnet-core.md +311 -0
- package/templates/csharp-expert/.cursorrules/async-patterns.md +206 -0
- package/templates/csharp-expert/.cursorrules/dependency-injection.md +206 -0
- package/templates/csharp-expert/.cursorrules/error-handling.md +235 -0
- package/templates/csharp-expert/.cursorrules/language-features.md +204 -0
- package/templates/csharp-expert/.cursorrules/overview.md +92 -0
- package/templates/csharp-expert/.cursorrules/performance.md +251 -0
- package/templates/csharp-expert/.cursorrules/testing.md +282 -0
- package/templates/csharp-expert/.cursorrules/tooling.md +254 -0
- package/templates/csharp-expert/CLAUDE.md +360 -0
- package/templates/java-expert/.cursorrules/concurrency.md +209 -0
- package/templates/java-expert/.cursorrules/error-handling.md +205 -0
- package/templates/java-expert/.cursorrules/modern-java.md +216 -0
- package/templates/java-expert/.cursorrules/overview.md +81 -0
- package/templates/java-expert/.cursorrules/performance.md +239 -0
- package/templates/java-expert/.cursorrules/persistence.md +262 -0
- package/templates/java-expert/.cursorrules/spring-boot.md +262 -0
- package/templates/java-expert/.cursorrules/testing.md +272 -0
- package/templates/java-expert/.cursorrules/tooling.md +301 -0
- package/templates/java-expert/CLAUDE.md +325 -0
- package/templates/javascript-expert/.cursorrules/overview.md +5 -3
- package/templates/javascript-expert/.cursorrules/typescript-deep-dive.md +348 -0
- package/templates/javascript-expert/CLAUDE.md +34 -3
- package/templates/kotlin-expert/.cursorrules/coroutines.md +237 -0
- package/templates/kotlin-expert/.cursorrules/error-handling.md +149 -0
- package/templates/kotlin-expert/.cursorrules/frameworks.md +227 -0
- package/templates/kotlin-expert/.cursorrules/language-features.md +231 -0
- package/templates/kotlin-expert/.cursorrules/overview.md +77 -0
- package/templates/kotlin-expert/.cursorrules/performance.md +185 -0
- package/templates/kotlin-expert/.cursorrules/testing.md +213 -0
- package/templates/kotlin-expert/.cursorrules/tooling.md +258 -0
- package/templates/kotlin-expert/CLAUDE.md +276 -0
- package/templates/swift-expert/.cursorrules/concurrency.md +230 -0
- package/templates/swift-expert/.cursorrules/error-handling.md +213 -0
- package/templates/swift-expert/.cursorrules/language-features.md +246 -0
- package/templates/swift-expert/.cursorrules/overview.md +88 -0
- package/templates/swift-expert/.cursorrules/performance.md +260 -0
- package/templates/swift-expert/.cursorrules/swiftui.md +260 -0
- package/templates/swift-expert/.cursorrules/testing.md +286 -0
- package/templates/swift-expert/.cursorrules/tooling.md +285 -0
- package/templates/swift-expert/CLAUDE.md +275 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# Java Concurrency
|
|
2
|
+
|
|
3
|
+
The Java Memory Model is the law. Every concurrent decision must respect it.
|
|
4
|
+
|
|
5
|
+
## Virtual Threads (Java 21+)
|
|
6
|
+
|
|
7
|
+
The default for I/O-bound work. Platform threads for CPU-bound work.
|
|
8
|
+
|
|
9
|
+
```java
|
|
10
|
+
// Virtual threads for I/O concurrency
|
|
11
|
+
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
|
|
12
|
+
var futures = tasks.stream()
|
|
13
|
+
.map(task -> executor.submit(() -> process(task)))
|
|
14
|
+
.toList();
|
|
15
|
+
|
|
16
|
+
var results = futures.stream()
|
|
17
|
+
.map(f -> {
|
|
18
|
+
try { return f.get(30, TimeUnit.SECONDS); }
|
|
19
|
+
catch (TimeoutException e) { throw new ProcessingTimeoutException(e); }
|
|
20
|
+
catch (ExecutionException e) { throw new ProcessingException(e.getCause()); }
|
|
21
|
+
catch (InterruptedException e) {
|
|
22
|
+
Thread.currentThread().interrupt();
|
|
23
|
+
throw new ProcessingException(e);
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
.toList();
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Virtual Thread Rules
|
|
31
|
+
|
|
32
|
+
- Don't pool virtual threads — they're cheap to create
|
|
33
|
+
- Don't use `synchronized` for I/O operations — use `ReentrantLock`
|
|
34
|
+
- Don't pin virtual threads to carrier threads (`synchronized` blocks pin)
|
|
35
|
+
- Don't use `ThreadLocal` for request-scoped data — use `ScopedValue` (preview)
|
|
36
|
+
|
|
37
|
+
```java
|
|
38
|
+
// Bad: synchronized pins virtual thread to carrier
|
|
39
|
+
synchronized (lock) {
|
|
40
|
+
database.query(sql); // I/O while pinned — wastes carrier thread
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Good: ReentrantLock doesn't pin
|
|
44
|
+
private final ReentrantLock lock = new ReentrantLock();
|
|
45
|
+
|
|
46
|
+
lock.lock();
|
|
47
|
+
try {
|
|
48
|
+
database.query(sql); // Virtual thread can unmount during I/O
|
|
49
|
+
} finally {
|
|
50
|
+
lock.unlock();
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Structured Concurrency (Preview)
|
|
55
|
+
|
|
56
|
+
```java
|
|
57
|
+
// All subtasks succeed or all are cancelled — no leaked threads
|
|
58
|
+
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
|
|
59
|
+
Subtask<User> userTask = scope.fork(() -> userService.findById(userId));
|
|
60
|
+
Subtask<List<Order>> ordersTask = scope.fork(() -> orderService.findByUser(userId));
|
|
61
|
+
|
|
62
|
+
scope.join(); // Wait for all
|
|
63
|
+
scope.throwIfFailed(); // Propagate first failure
|
|
64
|
+
|
|
65
|
+
return new Dashboard(userTask.get(), ordersTask.get());
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## CompletableFuture
|
|
70
|
+
|
|
71
|
+
```java
|
|
72
|
+
// Async pipeline with proper error handling
|
|
73
|
+
public CompletableFuture<OrderResult> processOrderAsync(CreateOrderRequest request) {
|
|
74
|
+
return validateAsync(request)
|
|
75
|
+
.thenCompose(this::checkInventoryAsync)
|
|
76
|
+
.thenCompose(this::createOrderAsync)
|
|
77
|
+
.thenApply(OrderResult::success)
|
|
78
|
+
.exceptionally(ex -> {
|
|
79
|
+
log.error("Order processing failed", ex);
|
|
80
|
+
return OrderResult.failure(ex.getMessage());
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Combine independent operations
|
|
85
|
+
public CompletableFuture<Dashboard> loadDashboard(UUID userId) {
|
|
86
|
+
var userFuture = CompletableFuture.supplyAsync(() -> userService.findById(userId));
|
|
87
|
+
var ordersFuture = CompletableFuture.supplyAsync(() -> orderService.recent(userId));
|
|
88
|
+
var statsFuture = CompletableFuture.supplyAsync(() -> statsService.forUser(userId));
|
|
89
|
+
|
|
90
|
+
return CompletableFuture.allOf(userFuture, ordersFuture, statsFuture)
|
|
91
|
+
.thenApply(v -> new Dashboard(
|
|
92
|
+
userFuture.join(),
|
|
93
|
+
ordersFuture.join(),
|
|
94
|
+
statsFuture.join()));
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Thread Safety Patterns
|
|
99
|
+
|
|
100
|
+
### Immutability (Preferred)
|
|
101
|
+
|
|
102
|
+
```java
|
|
103
|
+
// Immutable objects are inherently thread-safe
|
|
104
|
+
public record UserSnapshot(UUID id, String name, Instant capturedAt) {}
|
|
105
|
+
|
|
106
|
+
// Unmodifiable collections
|
|
107
|
+
private final List<String> allowedRoles = List.of("USER", "ADMIN", "MODERATOR");
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Atomic Operations
|
|
111
|
+
|
|
112
|
+
```java
|
|
113
|
+
private final AtomicLong requestCounter = new AtomicLong();
|
|
114
|
+
private final AtomicReference<Config> currentConfig = new AtomicReference<>(Config.defaults());
|
|
115
|
+
|
|
116
|
+
public void handleRequest() {
|
|
117
|
+
requestCounter.incrementAndGet();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
public void updateConfig(Config newConfig) {
|
|
121
|
+
currentConfig.set(newConfig);
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### ConcurrentHashMap
|
|
126
|
+
|
|
127
|
+
```java
|
|
128
|
+
// Thread-safe map with atomic compute operations
|
|
129
|
+
private final ConcurrentHashMap<String, AtomicLong> counters = new ConcurrentHashMap<>();
|
|
130
|
+
|
|
131
|
+
public void increment(String key) {
|
|
132
|
+
counters.computeIfAbsent(key, k -> new AtomicLong()).incrementAndGet();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Never iterate and modify — use compute/merge operations
|
|
136
|
+
counters.merge(key, new AtomicLong(1), (existing, value) -> {
|
|
137
|
+
existing.incrementAndGet();
|
|
138
|
+
return existing;
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Bounded Queues
|
|
143
|
+
|
|
144
|
+
```java
|
|
145
|
+
// Backpressure via bounded queue
|
|
146
|
+
private final BlockingQueue<Event> eventQueue = new LinkedBlockingQueue<>(10_000);
|
|
147
|
+
|
|
148
|
+
public boolean publish(Event event) {
|
|
149
|
+
return eventQueue.offer(event); // Returns false if full — handle backpressure
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
public void consume() {
|
|
153
|
+
while (!Thread.currentThread().isInterrupted()) {
|
|
154
|
+
try {
|
|
155
|
+
Event event = eventQueue.poll(1, TimeUnit.SECONDS);
|
|
156
|
+
if (event != null) process(event);
|
|
157
|
+
} catch (InterruptedException e) {
|
|
158
|
+
Thread.currentThread().interrupt();
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Interrupt Handling
|
|
166
|
+
|
|
167
|
+
```java
|
|
168
|
+
// Always restore interrupt status
|
|
169
|
+
public void processItems(List<Item> items) {
|
|
170
|
+
for (var item : items) {
|
|
171
|
+
if (Thread.currentThread().isInterrupted()) {
|
|
172
|
+
log.warn("Processing interrupted, {} items remaining", items.size());
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
process(item);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// In catch blocks
|
|
180
|
+
try {
|
|
181
|
+
Thread.sleep(Duration.ofSeconds(5));
|
|
182
|
+
} catch (InterruptedException e) {
|
|
183
|
+
Thread.currentThread().interrupt(); // Restore flag
|
|
184
|
+
throw new RuntimeException("Operation interrupted", e);
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Anti-Patterns
|
|
189
|
+
|
|
190
|
+
```java
|
|
191
|
+
// Never: double-checked locking without volatile (pre-Java 5 bug, but still seen)
|
|
192
|
+
// Use: enum singleton, or lazy holder pattern, or just @Singleton
|
|
193
|
+
|
|
194
|
+
// Never: synchronized on non-final fields
|
|
195
|
+
private Object lock = new Object(); // Can be reassigned!
|
|
196
|
+
synchronized (lock) { } // Different threads may lock different objects
|
|
197
|
+
// Use: private final Object lock = new Object();
|
|
198
|
+
|
|
199
|
+
// Never: Thread.stop() or Thread.suspend()
|
|
200
|
+
// Use: cooperative interruption via Thread.interrupt()
|
|
201
|
+
|
|
202
|
+
// Never: busy-wait
|
|
203
|
+
while (!ready) { } // Burns CPU
|
|
204
|
+
// Use: CountDownLatch, Semaphore, or BlockingQueue
|
|
205
|
+
|
|
206
|
+
// Never: shared mutable state without synchronization
|
|
207
|
+
private int counter; // Multiple threads read/write without coordination
|
|
208
|
+
// Use: AtomicInteger, or synchronize access
|
|
209
|
+
```
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# Java Error Handling
|
|
2
|
+
|
|
3
|
+
Exceptions for exceptional conditions. Validation at boundaries. Never swallow errors.
|
|
4
|
+
|
|
5
|
+
## Exception Hierarchy
|
|
6
|
+
|
|
7
|
+
```java
|
|
8
|
+
// Application exception base — unchecked
|
|
9
|
+
public abstract class ApplicationException extends RuntimeException {
|
|
10
|
+
private final String errorCode;
|
|
11
|
+
|
|
12
|
+
protected ApplicationException(String errorCode, String message) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.errorCode = errorCode;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
protected ApplicationException(String errorCode, String message, Throwable cause) {
|
|
18
|
+
super(message, cause);
|
|
19
|
+
this.errorCode = errorCode;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public String getErrorCode() { return errorCode; }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Specific exceptions
|
|
26
|
+
public class NotFoundException extends ApplicationException {
|
|
27
|
+
public NotFoundException(String entity, Object id) {
|
|
28
|
+
super("NOT_FOUND", "%s with id '%s' not found".formatted(entity, id));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public class ConflictException extends ApplicationException {
|
|
33
|
+
public ConflictException(String message) {
|
|
34
|
+
super("CONFLICT", message);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public class ValidationException extends ApplicationException {
|
|
39
|
+
private final Map<String, String> fieldErrors;
|
|
40
|
+
|
|
41
|
+
public ValidationException(Map<String, String> fieldErrors) {
|
|
42
|
+
super("VALIDATION_FAILED", "Validation failed");
|
|
43
|
+
this.fieldErrors = Map.copyOf(fieldErrors);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public Map<String, String> getFieldErrors() { return fieldErrors; }
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Checked vs Unchecked
|
|
51
|
+
|
|
52
|
+
```java
|
|
53
|
+
// Checked exceptions: recoverable conditions at system boundaries
|
|
54
|
+
// - IOException: file not found, network error — caller can retry or use fallback
|
|
55
|
+
// - SQLException: database error — caller can retry with backoff
|
|
56
|
+
|
|
57
|
+
// Unchecked exceptions: programming errors and business rule violations
|
|
58
|
+
// - IllegalArgumentException: bad input
|
|
59
|
+
// - NullPointerException: null where not expected
|
|
60
|
+
// - NotFoundException: domain-level "not found"
|
|
61
|
+
// - ValidationException: business rule violation
|
|
62
|
+
|
|
63
|
+
// Rule: if the caller CAN and SHOULD handle it → checked
|
|
64
|
+
// if it's a bug or unrecoverable → unchecked
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Validation
|
|
68
|
+
|
|
69
|
+
```java
|
|
70
|
+
// Jakarta Bean Validation on DTOs
|
|
71
|
+
public record CreateUserRequest(
|
|
72
|
+
@NotBlank(message = "Name is required")
|
|
73
|
+
@Size(min = 2, max = 200, message = "Name must be 2-200 characters")
|
|
74
|
+
String name,
|
|
75
|
+
|
|
76
|
+
@NotBlank(message = "Email is required")
|
|
77
|
+
@Email(message = "Invalid email format")
|
|
78
|
+
String email,
|
|
79
|
+
|
|
80
|
+
@NotNull(message = "Role is required")
|
|
81
|
+
UserRole role
|
|
82
|
+
) {}
|
|
83
|
+
|
|
84
|
+
// Custom validator
|
|
85
|
+
@Constraint(validatedBy = UniqueEmailValidator.class)
|
|
86
|
+
@Target(ElementType.FIELD)
|
|
87
|
+
@Retention(RetentionPolicy.RUNTIME)
|
|
88
|
+
public @interface UniqueEmail {
|
|
89
|
+
String message() default "Email already registered";
|
|
90
|
+
Class<?>[] groups() default {};
|
|
91
|
+
Class<? extends Payload>[] payload() default {};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
public class UniqueEmailValidator implements ConstraintValidator<UniqueEmail, String> {
|
|
95
|
+
private final UserRepository userRepository;
|
|
96
|
+
|
|
97
|
+
@Override
|
|
98
|
+
public boolean isValid(String email, ConstraintValidatorContext context) {
|
|
99
|
+
return email == null || !userRepository.existsByEmail(email);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Guard Clauses
|
|
105
|
+
|
|
106
|
+
```java
|
|
107
|
+
public class OrderService {
|
|
108
|
+
|
|
109
|
+
public Order create(String customerId, List<OrderItem> items) {
|
|
110
|
+
// Fail fast with meaningful messages
|
|
111
|
+
Objects.requireNonNull(customerId, "customerId must not be null");
|
|
112
|
+
if (customerId.isBlank()) {
|
|
113
|
+
throw new IllegalArgumentException("customerId must not be blank");
|
|
114
|
+
}
|
|
115
|
+
if (items == null || items.isEmpty()) {
|
|
116
|
+
throw new IllegalArgumentException("Order must have at least one item");
|
|
117
|
+
}
|
|
118
|
+
if (items.size() > MAX_ITEMS) {
|
|
119
|
+
throw new ValidationException(Map.of(
|
|
120
|
+
"items", "Maximum %d items allowed".formatted(MAX_ITEMS)));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Business logic follows clean preconditions
|
|
124
|
+
return Order.create(customerId, items);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Result Pattern (Alternative to Exceptions)
|
|
130
|
+
|
|
131
|
+
```java
|
|
132
|
+
// For operations where failure is expected, not exceptional
|
|
133
|
+
public sealed interface Result<T> permits Result.Success, Result.Failure {
|
|
134
|
+
|
|
135
|
+
record Success<T>(T value) implements Result<T> {}
|
|
136
|
+
record Failure<T>(String code, String message) implements Result<T> {}
|
|
137
|
+
|
|
138
|
+
static <T> Result<T> success(T value) { return new Success<>(value); }
|
|
139
|
+
static <T> Result<T> failure(String code, String message) { return new Failure<>(code, message); }
|
|
140
|
+
|
|
141
|
+
default <U> U match(Function<T, U> onSuccess, BiFunction<String, String, U> onFailure) {
|
|
142
|
+
return switch (this) {
|
|
143
|
+
case Success<T> s -> onSuccess.apply(s.value());
|
|
144
|
+
case Failure<T> f -> onFailure.apply(f.code(), f.message());
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Usage
|
|
150
|
+
public Result<User> register(RegisterRequest request) {
|
|
151
|
+
if (userRepository.existsByEmail(request.email())) {
|
|
152
|
+
return Result.failure("CONFLICT", "Email already registered");
|
|
153
|
+
}
|
|
154
|
+
var user = User.create(request.name(), request.email());
|
|
155
|
+
userRepository.save(user);
|
|
156
|
+
return Result.success(user);
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Logging Exceptions
|
|
161
|
+
|
|
162
|
+
```java
|
|
163
|
+
// Log with context, not just the exception
|
|
164
|
+
try {
|
|
165
|
+
return paymentService.charge(orderId, amount);
|
|
166
|
+
} catch (PaymentException ex) {
|
|
167
|
+
log.error("Payment failed for order {} amount {}: {}",
|
|
168
|
+
orderId, amount, ex.getMessage(), ex);
|
|
169
|
+
throw new OrderProcessingException("Payment failed for order " + orderId, ex);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Rules:
|
|
173
|
+
// - Log at the point of handling, not at every rethrow
|
|
174
|
+
// - Include business context (IDs, amounts, actions)
|
|
175
|
+
// - Use structured logging parameters, not string concatenation
|
|
176
|
+
// - Include the exception as the LAST parameter (for stack trace)
|
|
177
|
+
// - Don't log AND throw at the same level (choose one)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Anti-Patterns
|
|
181
|
+
|
|
182
|
+
```java
|
|
183
|
+
// Never: catching Exception or Throwable broadly
|
|
184
|
+
try { doWork(); }
|
|
185
|
+
catch (Exception e) { log.error("Error", e); }
|
|
186
|
+
// Catches InterruptedException, NPE, and everything else — too broad
|
|
187
|
+
|
|
188
|
+
// Never: empty catch blocks
|
|
189
|
+
try { connection.close(); }
|
|
190
|
+
catch (Exception e) { /* ignore */ }
|
|
191
|
+
// At minimum: log.debug("Error closing connection", e);
|
|
192
|
+
|
|
193
|
+
// Never: using exceptions for control flow
|
|
194
|
+
try { return Integer.parseInt(input); }
|
|
195
|
+
catch (NumberFormatException e) { return defaultValue; }
|
|
196
|
+
// Use: input.matches("\\d+") or a tryParse utility
|
|
197
|
+
|
|
198
|
+
// Never: wrapping without adding context
|
|
199
|
+
catch (SQLException e) { throw new RuntimeException(e); }
|
|
200
|
+
// Add context: throw new PersistenceException("Failed to save order " + orderId, e);
|
|
201
|
+
|
|
202
|
+
// Never: losing the original exception
|
|
203
|
+
catch (Exception e) { throw new RuntimeException("Something failed"); }
|
|
204
|
+
// Always chain: throw new RuntimeException("Something failed", e);
|
|
205
|
+
```
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# Modern Java Language Features
|
|
2
|
+
|
|
3
|
+
Java 21+ features used deliberately. Every feature exists to make code clearer — not to show off.
|
|
4
|
+
|
|
5
|
+
## Records
|
|
6
|
+
|
|
7
|
+
```java
|
|
8
|
+
// Immutable data carriers — the default for DTOs, value objects, events
|
|
9
|
+
public record CreateUserRequest(
|
|
10
|
+
@NotBlank String name,
|
|
11
|
+
@Email String email
|
|
12
|
+
) {}
|
|
13
|
+
|
|
14
|
+
public record Money(BigDecimal amount, Currency currency) {
|
|
15
|
+
// Compact constructor for validation
|
|
16
|
+
public Money {
|
|
17
|
+
if (amount.compareTo(BigDecimal.ZERO) < 0) {
|
|
18
|
+
throw new IllegalArgumentException("Amount cannot be negative");
|
|
19
|
+
}
|
|
20
|
+
Objects.requireNonNull(currency, "Currency is required");
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public record PageRequest(int page, int size) {
|
|
25
|
+
public PageRequest {
|
|
26
|
+
if (page < 0) throw new IllegalArgumentException("Page must be >= 0");
|
|
27
|
+
if (size < 1 || size > 100) throw new IllegalArgumentException("Size must be 1-100");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public int offset() {
|
|
31
|
+
return page * size;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### When NOT to Use Records
|
|
37
|
+
|
|
38
|
+
- JPA entities (need mutable state, no-arg constructor)
|
|
39
|
+
- Spring beans (need proxying, lifecycle management)
|
|
40
|
+
- Classes with complex behavior beyond data carrying
|
|
41
|
+
|
|
42
|
+
## Sealed Classes
|
|
43
|
+
|
|
44
|
+
```java
|
|
45
|
+
// Exhaustive type hierarchies — the compiler enforces completeness
|
|
46
|
+
public sealed interface PaymentResult permits PaymentSuccess, PaymentFailure, PaymentPending {
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public record PaymentSuccess(String transactionId, Instant processedAt) implements PaymentResult {}
|
|
50
|
+
public record PaymentFailure(String errorCode, String message) implements PaymentResult {}
|
|
51
|
+
public record PaymentPending(String referenceId, Duration estimatedWait) implements PaymentResult {}
|
|
52
|
+
|
|
53
|
+
// Exhaustive switch — compiler warns if a case is missing
|
|
54
|
+
public String describeResult(PaymentResult result) {
|
|
55
|
+
return switch (result) {
|
|
56
|
+
case PaymentSuccess s -> "Paid: " + s.transactionId();
|
|
57
|
+
case PaymentFailure f -> "Failed: " + f.message();
|
|
58
|
+
case PaymentPending p -> "Pending: " + p.referenceId();
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Pattern Matching
|
|
64
|
+
|
|
65
|
+
```java
|
|
66
|
+
// instanceof pattern matching — no more casting
|
|
67
|
+
public String format(Object value) {
|
|
68
|
+
return switch (value) {
|
|
69
|
+
case Integer i when i < 0 -> "negative: " + i;
|
|
70
|
+
case Integer i -> "integer: " + i;
|
|
71
|
+
case String s when s.isEmpty() -> "empty string";
|
|
72
|
+
case String s -> "string: " + s;
|
|
73
|
+
case null -> "null";
|
|
74
|
+
default -> "unknown: " + value.getClass().getSimpleName();
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Record patterns (Java 21+)
|
|
79
|
+
public double calculateArea(Shape shape) {
|
|
80
|
+
return switch (shape) {
|
|
81
|
+
case Circle(double radius) -> Math.PI * radius * radius;
|
|
82
|
+
case Rectangle(double w, double h) -> w * h;
|
|
83
|
+
case Triangle(double base, double height) -> 0.5 * base * height;
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Text Blocks
|
|
89
|
+
|
|
90
|
+
```java
|
|
91
|
+
// Multi-line strings with proper indentation
|
|
92
|
+
String query = """
|
|
93
|
+
SELECT u.id, u.name, u.email
|
|
94
|
+
FROM users u
|
|
95
|
+
WHERE u.active = true
|
|
96
|
+
AND u.created_at > :since
|
|
97
|
+
ORDER BY u.name
|
|
98
|
+
""";
|
|
99
|
+
|
|
100
|
+
String json = """
|
|
101
|
+
{
|
|
102
|
+
"name": "%s",
|
|
103
|
+
"email": "%s"
|
|
104
|
+
}
|
|
105
|
+
""".formatted(name, email);
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Optional
|
|
109
|
+
|
|
110
|
+
```java
|
|
111
|
+
// Return type for "might not exist" — never for fields or parameters
|
|
112
|
+
public Optional<User> findByEmail(String email) {
|
|
113
|
+
return Optional.ofNullable(userRepository.findByEmail(email));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Good: chain operations
|
|
117
|
+
public String getUserDisplayName(String email) {
|
|
118
|
+
return findByEmail(email)
|
|
119
|
+
.map(User::displayName)
|
|
120
|
+
.orElse("Unknown User");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Good: throw with context
|
|
124
|
+
public User getByEmail(String email) {
|
|
125
|
+
return findByEmail(email)
|
|
126
|
+
.orElseThrow(() -> new NotFoundException("User not found: " + email));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Bad: Optional.get() without check — defeats the purpose
|
|
130
|
+
user.get().getName(); // NoSuchElementException risk
|
|
131
|
+
|
|
132
|
+
// Bad: Optional as method parameter
|
|
133
|
+
public void process(Optional<String> name) { } // Use @Nullable instead
|
|
134
|
+
|
|
135
|
+
// Bad: Optional as field
|
|
136
|
+
private Optional<String> middleName; // Use @Nullable instead
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Collections
|
|
140
|
+
|
|
141
|
+
```java
|
|
142
|
+
// Immutable collections — the default
|
|
143
|
+
var users = List.of("Alice", "Bob", "Charlie");
|
|
144
|
+
var scores = Map.of("Alice", 95, "Bob", 87);
|
|
145
|
+
var uniqueNames = Set.of("Alice", "Bob");
|
|
146
|
+
|
|
147
|
+
// List.copyOf, Set.copyOf, Map.copyOf for defensive copies
|
|
148
|
+
public List<User> getUsers() {
|
|
149
|
+
return List.copyOf(mutableUserList); // Defensive copy
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Collectors and Stream API
|
|
153
|
+
var activeUsersByDepartment = users.stream()
|
|
154
|
+
.filter(User::isActive)
|
|
155
|
+
.collect(Collectors.groupingBy(
|
|
156
|
+
User::department,
|
|
157
|
+
Collectors.toUnmodifiableList()));
|
|
158
|
+
|
|
159
|
+
// toList() shorthand (Java 16+)
|
|
160
|
+
var names = users.stream()
|
|
161
|
+
.map(User::name)
|
|
162
|
+
.toList(); // Returns unmodifiable list
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Virtual Threads (Java 21+)
|
|
166
|
+
|
|
167
|
+
```java
|
|
168
|
+
// Virtual threads for I/O-bound concurrency — massive scalability
|
|
169
|
+
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
|
|
170
|
+
List<Future<Response>> futures = urls.stream()
|
|
171
|
+
.map(url -> executor.submit(() -> httpClient.send(url)))
|
|
172
|
+
.toList();
|
|
173
|
+
|
|
174
|
+
List<Response> responses = futures.stream()
|
|
175
|
+
.map(f -> {
|
|
176
|
+
try { return f.get(); }
|
|
177
|
+
catch (Exception e) { throw new RuntimeException(e); }
|
|
178
|
+
})
|
|
179
|
+
.toList();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Spring Boot 3.2+: enable virtual threads
|
|
183
|
+
// spring.threads.virtual.enabled=true
|
|
184
|
+
|
|
185
|
+
// Rules for virtual threads:
|
|
186
|
+
// - Don't pool them (they're cheap to create)
|
|
187
|
+
// - Don't use synchronized blocks for I/O (use ReentrantLock)
|
|
188
|
+
// - Don't use ThreadLocal for request-scoped data (use ScopedValue)
|
|
189
|
+
// - Perfect for: HTTP handlers, database queries, external API calls
|
|
190
|
+
// - Not for: CPU-bound computation (use platform threads / ForkJoinPool)
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Anti-Patterns
|
|
194
|
+
|
|
195
|
+
```java
|
|
196
|
+
// Never: raw types
|
|
197
|
+
List list = new ArrayList(); // What's in it?
|
|
198
|
+
// Use: List<User> users = new ArrayList<>();
|
|
199
|
+
|
|
200
|
+
// Never: checked exceptions for control flow
|
|
201
|
+
try { return Integer.parseInt(input); }
|
|
202
|
+
catch (NumberFormatException e) { return -1; }
|
|
203
|
+
// Use validation before parsing
|
|
204
|
+
|
|
205
|
+
// Never: mutable public fields
|
|
206
|
+
public class Config {
|
|
207
|
+
public String url; // Anyone can change this
|
|
208
|
+
}
|
|
209
|
+
// Use: records, or private fields with getters
|
|
210
|
+
|
|
211
|
+
// Never: null as a valid business value
|
|
212
|
+
public User findUser(String id) {
|
|
213
|
+
return null; // Caller forgets null check → NPE
|
|
214
|
+
}
|
|
215
|
+
// Use: Optional<User> or throw NotFoundException
|
|
216
|
+
```
|