$npx -y skills add softspark/ai-toolkit --skill java-patternsJava: Spring Boot, CompletableFuture, records, sealed types, JPA/Hibernate, virtual threads. Triggers: Java, Spring, JPA, Hibernate, Maven, Gradle, virtual thread, sealed class.
| 1 | # Java Patterns Skill |
| 2 | |
| 3 | ## Project Structure |
| 4 | |
| 5 | ### Maven / Gradle Standard Layout |
| 6 | |
| 7 | ``` |
| 8 | my-app/ |
| 9 | ├── pom.xml (or build.gradle.kts + settings.gradle.kts) |
| 10 | ├── src/ |
| 11 | │ ├── main/ |
| 12 | │ │ ├── java/com/example/myapp/ |
| 13 | │ │ │ ├── MyApplication.java |
| 14 | │ │ │ ├── config/ |
| 15 | │ │ │ ├── controller/ |
| 16 | │ │ │ ├── service/ |
| 17 | │ │ │ ├── repository/ |
| 18 | │ │ │ ├── model/ |
| 19 | │ │ │ │ ├── entity/ |
| 20 | │ │ │ │ └── dto/ |
| 21 | │ │ │ └── exception/ |
| 22 | │ │ └── resources/ |
| 23 | │ │ ├── application.yml |
| 24 | │ │ └── db/migration/ |
| 25 | │ └── test/ |
| 26 | │ ├── java/com/example/myapp/ |
| 27 | │ └── resources/application-test.yml |
| 28 | └── target/ (or build/) |
| 29 | ``` |
| 30 | |
| 31 | ### Multi-Module |
| 32 | |
| 33 | ``` |
| 34 | parent/ |
| 35 | ├── pom.xml (packaging=pom) |
| 36 | ├── common/ (shared utilities) |
| 37 | ├── domain/ (entities, business rules) |
| 38 | ├── api/ (REST controllers, DTOs) |
| 39 | └── app/ (Spring Boot main, wiring) |
| 40 | ``` |
| 41 | |
| 42 | --- |
| 43 | |
| 44 | ## Idioms / Code Style |
| 45 | |
| 46 | ### Records (Java 16+) |
| 47 | ```java |
| 48 | public record UserDto(Long id, String name, String email) { |
| 49 | public UserDto { // compact constructor for validation |
| 50 | Objects.requireNonNull(name, "name must not be null"); |
| 51 | Objects.requireNonNull(email, "email must not be null"); |
| 52 | } |
| 53 | } |
| 54 | ``` |
| 55 | |
| 56 | ### Sealed Classes (Java 17+) |
| 57 | ```java |
| 58 | public sealed interface Shape permits Circle, Rectangle, Triangle { |
| 59 | double area(); |
| 60 | } |
| 61 | public record Circle(double radius) implements Shape { |
| 62 | public double area() { return Math.PI * radius * radius; } |
| 63 | } |
| 64 | public record Rectangle(double w, double h) implements Shape { |
| 65 | public double area() { return w * h; } |
| 66 | } |
| 67 | public record Triangle(double base, double height) implements Shape { |
| 68 | public double area() { return 0.5 * base * height; } |
| 69 | } |
| 70 | ``` |
| 71 | |
| 72 | ### Switch Expressions (Java 14+) |
| 73 | ```java |
| 74 | String describe(Shape shape) { |
| 75 | return switch (shape) { |
| 76 | case Circle c -> "Circle r=" + c.radius(); |
| 77 | case Rectangle r -> "Rect %sx%s".formatted(r.w(), r.h()); |
| 78 | case Triangle t -> "Triangle base=" + t.base(); |
| 79 | }; |
| 80 | } |
| 81 | // Guard patterns (Java 21+) |
| 82 | String classify(Shape shape) { |
| 83 | return switch (shape) { |
| 84 | case Circle c when c.radius() > 100 -> "large circle"; |
| 85 | case Circle c -> "small circle"; |
| 86 | case Rectangle r -> "rectangle"; |
| 87 | case Triangle t -> "triangle"; |
| 88 | }; |
| 89 | } |
| 90 | ``` |
| 91 | |
| 92 | ### var, Streams, Optional |
| 93 | ```java |
| 94 | // var -- use when RHS makes type obvious |
| 95 | var users = new ArrayList<User>(); |
| 96 | var response = client.send(request, HttpResponse.BodyHandlers.ofString()); |
| 97 | // Avoid: var result = service.process(data); -- type unclear |
| 98 | |
| 99 | // Streams |
| 100 | List<String> names = users.stream() |
| 101 | .filter(User::isActive) |
| 102 | .map(User::name) |
| 103 | .sorted() |
| 104 | .toList(); // Java 16+, unmodifiable |
| 105 | |
| 106 | Map<Department, List<User>> byDept = users.stream() |
| 107 | .collect(Collectors.groupingBy(User::department)); |
| 108 | |
| 109 | // Optional -- return type only, never as field or parameter |
| 110 | String city = findByEmail(email) |
| 111 | .map(User::address) |
| 112 | .map(Address::city) |
| 113 | .orElse("Unknown"); |
| 114 | // Never call .get() without guard -- use orElse/orElseThrow |
| 115 | |
| 116 | // Text blocks (Java 15+) |
| 117 | String json = """ |
| 118 | {"name": "%s", "email": "%s"} |
| 119 | """.formatted(name, email); |
| 120 | ``` |
| 121 | |
| 122 | --- |
| 123 | |
| 124 | ## Error Handling |
| 125 | |
| 126 | | Type | When | Examples | |
| 127 | |------|------|---------| |
| 128 | | Checked | Recoverable I/O the caller must handle | IOException, SQLException | |
| 129 | | Unchecked | Programming errors, business rule violations | IllegalArgumentException, custom domain exceptions | |
| 130 | |
| 131 | ### Custom Exception Hierarchy |
| 132 | ```java |
| 133 | public abstract class DomainException extends RuntimeException { |
| 134 | private final String errorCode; |
| 135 | protected DomainException(String errorCode, String message) { |
| 136 | super(message); |
| 137 | this.errorCode = errorCode; |
| 138 | } |
| 139 | public String errorCode() { return errorCode; } |
| 140 | } |
| 141 | |
| 142 | public class EntityNotFoundException extends DomainException { |
| 143 | public EntityNotFoundException(String entity, Object id) { |
| 144 | super("NOT_FOUND", "%s with id %s not found".formatted(entity, id)); |
| 145 | } |
| 146 | } |
| 147 | ``` |
| 148 | |
| 149 | ### Try-With-Resources |
| 150 | ```java |
| 151 | try (var conn = dataSource.getConnection(); |
| 152 | var stmt = conn.prepareStatement(sql); |
| 153 | var rs = stmt.executeQuery()) { |
| 154 | while (rs.next()) { results.add(mapRow(rs)); } |
| 155 | } |
| 156 | ``` |
| 157 | |
| 158 | ### Global Handler (Spring) |
| 159 | ```java |
| 160 | @RestControllerAdvice |
| 161 | public class GlobalExceptionHandler { |
| 162 | @ExceptionHandler(EntityNotFoundException.class) |
| 163 | public ResponseEntity<ErrorResponse> handleNotFound(EntityNotFoundException ex) { |
| 164 | return ResponseEntity.status(404).body(new ErrorResponse(ex.errorCode(), ex.getMessage())); |
| 165 | } |
| 166 | @ExceptionHandler(MethodArgumentNotValidException.class) |
| 167 | public ResponseEntity<ErrorResponse> handleValidatio |