Java: Spring Boot, CompletableFuture, records, sealed types, JPA/Hibernate, virtual threads. Triggers: Java, Spring, JPA, Hibernate, Maven, Gradle, virtual thread, sealed class.
Scanned 5/27/2026
Install via CLI
openskills install softspark/ai-toolkit---
name: java-patterns
description: "Java: Spring Boot, CompletableFuture, records, sealed types, JPA/Hibernate, virtual threads. Triggers: Java, Spring, JPA, Hibernate, Maven, Gradle, virtual thread, sealed class."
effort: medium
user-invocable: false
allowed-tools: Read
---
# Java Patterns Skill
## Project Structure
### Maven / Gradle Standard Layout
```
my-app/
├── pom.xml (or build.gradle.kts + settings.gradle.kts)
├── src/
│ ├── main/
│ │ ├── java/com/example/myapp/
│ │ │ ├── MyApplication.java
│ │ │ ├── config/
│ │ │ ├── controller/
│ │ │ ├── service/
│ │ │ ├── repository/
│ │ │ ├── model/
│ │ │ │ ├── entity/
│ │ │ │ └── dto/
│ │ │ └── exception/
│ │ └── resources/
│ │ ├── application.yml
│ │ └── db/migration/
│ └── test/
│ ├── java/com/example/myapp/
│ └── resources/application-test.yml
└── target/ (or build/)
```
### Multi-Module
```
parent/
├── pom.xml (packaging=pom)
├── common/ (shared utilities)
├── domain/ (entities, business rules)
├── api/ (REST controllers, DTOs)
└── app/ (Spring Boot main, wiring)
```
---
## Idioms / Code Style
### Records (Java 16+)
```java
public record UserDto(Long id, String name, String email) {
public UserDto { // compact constructor for validation
Objects.requireNonNull(name, "name must not be null");
Objects.requireNonNull(email, "email must not be null");
}
}
```
### Sealed Classes (Java 17+)
```java
public sealed interface Shape permits Circle, Rectangle, Triangle {
double area();
}
public record Circle(double radius) implements Shape {
public double area() { return Math.PI * radius * radius; }
}
public record Rectangle(double w, double h) implements Shape {
public double area() { return w * h; }
}
public record Triangle(double base, double height) implements Shape {
public double area() { return 0.5 * base * height; }
}
```
### Switch Expressions (Java 14+)
```java
String describe(Shape shape) {
return switch (shape) {
case Circle c -> "Circle r=" + c.radius();
case Rectangle r -> "Rect %sx%s".formatted(r.w(), r.h());
case Triangle t -> "Triangle base=" + t.base();
};
}
// Guard patterns (Java 21+)
String classify(Shape shape) {
return switch (shape) {
case Circle c when c.radius() > 100 -> "large circle";
case Circle c -> "small circle";
case Rectangle r -> "rectangle";
case Triangle t -> "triangle";
};
}
```
### var, Streams, Optional
```java
// var -- use when RHS makes type obvious
var users = new ArrayList<User>();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
// Avoid: var result = service.process(data); -- type unclear
// Streams
List<String> names = users.stream()
.filter(User::isActive)
.map(User::name)
.sorted()
.toList(); // Java 16+, unmodifiable
Map<Department, List<User>> byDept = users.stream()
.collect(Collectors.groupingBy(User::department));
// Optional -- return type only, never as field or parameter
String city = findByEmail(email)
.map(User::address)
.map(Address::city)
.orElse("Unknown");
// Never call .get() without guard -- use orElse/orElseThrow
// Text blocks (Java 15+)
String json = """
{"name": "%s", "email": "%s"}
""".formatted(name, email);
```
---
## Error Handling
| Type | When | Examples |
|------|------|---------|
| Checked | Recoverable I/O the caller must handle | IOException, SQLException |
| Unchecked | Programming errors, business rule violations | IllegalArgumentException, custom domain exceptions |
### Custom Exception Hierarchy
```java
public abstract class DomainException extends RuntimeException {
private final String errorCode;
protected DomainException(String errorCode, String message) {
super(message);
this.errorCode = errorCode;
}
public String errorCode() { return errorCode; }
}
public class EntityNotFoundException extends DomainException {
public EntityNotFoundException(String entity, Object id) {
super("NOT_FOUND", "%s with id %s not found".formatted(entity, id));
}
}
```
### Try-With-Resources
```java
try (var conn = dataSource.getConnection();
var stmt = conn.prepareStatement(sql);
var rs = stmt.executeQuery()) {
while (rs.next()) { results.add(mapRow(rs)); }
}
```
### Global Handler (Spring)
```java
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(EntityNotFoundException.class)
public ResponseEntity<ErrorResponse> handleNotFound(EntityNotFoundException ex) {
return ResponseEntity.status(404).body(new ErrorResponse(ex.errorCode(), ex.getMessage()));
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidation(MethodArgumentNotValidException ex) {
var errors = ex.getBindingResult().getFieldErrors().stream()
.map(e -> e.getField() + ": " + e.getDefaultMessage()).toList();
return ResponseEntity.badRequest().body(new ErrorResponse("VALIDATION_ERROR", errors.toString()));
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGeneric(Exception ex) {
log.error("Unhandled exception", ex);
return ResponseEntity.internalServerError()
.body(new ErrorResponse("INTERNAL_ERROR", "An unexpected error occurred"));
}
}
public record ErrorResponse(String code, String message) {}
```
---
## Testing Patterns
### JUnit 5 + Mockito + AssertJ
```java
@DisplayName("UserService")
class UserServiceTest {
private UserRepository repository;
private UserService service;
@BeforeEach
void setUp() {
repository = mock(UserRepository.class);
service = new UserService(repository);
}
@Test
void createsUserWithValidData() {
var request = new CreateUserRequest("Alice", "alice@example.com");
when(repository.save(any())).thenAnswer(inv -> inv.getArgument(0));
var user = service.createUser(request);
assertThat(user.name()).isEqualTo("Alice");
verify(repository).save(any(User.class));
}
@Test
void throwsWhenEmailExists() {
when(repository.existsByEmail("taken@test.com")).thenReturn(true);
assertThatThrownBy(() -> service.createUser(new CreateUserRequest("Bob", "taken@test.com")))
.isInstanceOf(BusinessRuleViolationException.class)
.hasMessageContaining("email already exists");
}
}
```
### Parameterized Tests
```java
@ParameterizedTest
@CsvSource({"1,1,2", "0,0,0", "-1,1,0", "100,200,300"})
void addReturnsSumOfArguments(int a, int b, int expected) {
assertThat(calculator.add(a, b)).isEqualTo(expected);
}
@ParameterizedTest
@MethodSource("invalidEmails")
void rejectsInvalidEmail(String email) {
assertThatThrownBy(() -> new Email(email)).isInstanceOf(IllegalArgumentException.class);
}
static Stream<String> invalidEmails() {
return Stream.of("", "no-at-sign", "@no-local", "spaces in@email.com");
}
```
### Mockito Extras
```java
// Argument captor
var captor = ArgumentCaptor.forClass(User.class);
verify(repository).save(captor.capture());
assertThat(captor.getValue().name()).isEqualTo("Alice");
// BDD style
given(repository.findById(1L)).willReturn(Optional.of(user));
then(repository).should().findById(1L);
```
### Testcontainers (Integration)
```java
@Testcontainers
@SpringBootTest
class UserRepositoryIT {
@Container
static PostgreSQLContainer<?> pg = new PostgreSQLContainer<>("postgres:16-alpine");
@DynamicPropertySource
static void props(DynamicPropertyRegistry r) {
r.add("spring.datasource.url", pg::getJdbcUrl);
r.add("spring.datasource.username", pg::getUsername);
r.add("spring.datasource.password", pg::getPassword);
}
@Autowired UserRepository repository;
@Test
void savesAndRetrievesUser() {
var saved = repository.save(new User("Alice", "alice@test.com"));
assertThat(repository.findById(saved.getId())).isPresent()
.get().extracting("name").isEqualTo("Alice");
}
}
```
---
## Common Frameworks
### Spring Boot (Controller / Service / Repository)
```java
@RestController
@RequestMapping("/api/v1/users")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@GetMapping
public List<UserDto> list(@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
return userService.list(PageRequest.of(page, size));
}
@PostMapping @ResponseStatus(HttpStatus.CREATED)
public UserDto create(@Valid @RequestBody CreateUserRequest request) {
return userService.create(request);
}
}
@Service @Transactional(readOnly = true) @RequiredArgsConstructor
public class UserService {
private final UserRepository repository;
private final UserMapper mapper;
@Transactional
public UserDto create(CreateUserRequest request) {
if (repository.existsByEmail(request.email()))
throw new BusinessRuleViolationException("email already exists");
return mapper.toDto(repository.save(mapper.toEntity(request)));
}
}
public interface UserRepository extends JpaRepository<User, Long> {
boolean existsByEmail(String email);
@Query("SELECT u FROM User u WHERE u.active = true AND u.role = :role")
List<User> findActiveByRole(@Param("role") Role role);
}
```
### Quarkus
Uses Jakarta REST (`@Path`, `@GET`, `@POST`) + CDI (`@Inject`, `@ApplicationScoped`). Panache simplifies JPA: `implements PanacheRepository<User>` gives `find()`, `persist()`, `list()` out of the box. Use `@Transactional` on mutating endpoints.
### Jackson, Lombok, MapStruct
```java
// Jackson -- snake_case response
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public record ApiResponse<T>(T data, @JsonInclude(Include.NON_NULL) String error) {}
// Lombok -- JPA entities only (use records for DTOs)
@Entity @Getter @Setter @NoArgsConstructor(access = PROTECTED)
@Builder @ToString(exclude = "password") @EqualsAndHashCode(of = "id")
public class User {
@Id @GeneratedValue(strategy = IDENTITY) private Long id;
private String name;
private String email;
private String password;
}
// MapStruct
@Mapper(componentModel = "spring")
public interface UserMapper {
UserDto toDto(User entity);
User toEntity(CreateUserRequest request);
}
```
---
## Performance Tips
### JVM Tuning
```bash
# Container-friendly (Java 17+)
java -XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -jar app.jar
# GC: -XX:+UseZGC (low latency) | -XX:+UseG1GC (balanced, default) | -XX:+UseParallelGC (throughput)
```
### Virtual Threads (Java 21+)
```java
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
var futures = urls.stream().map(url -> executor.submit(() -> fetch(url))).toList();
var results = futures.stream().map(f -> { try { return f.get(); }
catch (Exception e) { throw new RuntimeException(e); } }).toList();
}
// Spring Boot 3.2+: spring.threads.virtual.enabled=true
```
### Profiling with JFR
```bash
java -XX:StartFlightRecording=duration=60s,filename=profile.jfr -jar app.jar
jfr print --events jdk.CPULoad,jdk.GCHeapSummary profile.jfr
```
### Common Pitfalls
| Pitfall | Fix |
|---------|-----|
| String concat in loop | `StringBuilder` or `String.join()` |
| Unbounded caches | `Caffeine` with `maximumSize` + `expireAfterWrite` |
| Autoboxing in hot path | Primitive streams (`mapToInt`) or primitive collections |
| Synchronized everything | `ConcurrentHashMap`, `StampedLock`, virtual threads |
| Reflection in tight loop | Cache `MethodHandle` or use code generation |
---
## Build / Package Management
### Maven (key elements)
```xml
<properties>
<java.version>21</java.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.0</version>
</parent>
<!-- Use dependencyManagement + BOM for non-Boot projects -->
```
### Gradle Kotlin DSL
```kotlin
plugins { java; id("org.springframework.boot") version "3.3.0" }
java { toolchain { languageVersion = JavaLanguageVersion.of(21) } }
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
tasks.test { useJUnitPlatform() }
```
### Dependency Management
| Concern | Approach |
|---------|----------|
| Version alignment | BOM import via `dependencyManagement` / `platform()` |
| Vulnerability scan | OWASP plugin (`dependencyCheckAnalyze`) |
| Unused deps | `mvn dependency:analyze` or Gradle `dependency-analysis` plugin |
| Reproducible builds | Pin plugin versions, `maven-enforcer-plugin` |
---
## Anti-Patterns
| Anti-Pattern | Problem | Solution |
|--------------|---------|----------|
| God service class | 1000+ lines | Split by domain concern |
| Anemic domain model | Logic only in services | Put behavior on domain objects |
| Catching `Exception` | Hides bugs | Catch specific types |
| `@Autowired` on fields | Hidden deps, untestable | Constructor injection |
| Mutable DTOs | Thread-safety issues | Use records |
| Raw JDBC everywhere | Injection risk, boilerplate | JPA/jOOQ with parameterized queries |
| Missing `@Transactional` | Inconsistent data | Annotate service methods |
| N+1 queries | Performance death | `JOIN FETCH` or `@EntityGraph` |
No comments yet. Be the first to comment!