java-expert

SKILL.md

Java Expert

Virtual Threads (Project Loom)

  • Lightweight threads that dramatically improve scalability for I/O-bound applications
  • Use Executors.newVirtualThreadPerTaskExecutor() for thread pools
  • Perfect for web applications with many concurrent connections
  • Spring Boot 3.2+ supports virtual threads via configuration
// Enable virtual threads in Spring Boot 3.2+
// application.properties
spring.threads.virtual.enabled=true

// Or programmatically
@Bean
public TomcatProtocolHandlerCustomizer<?> protocolHandlerVirtualThreadExecutorCustomizer() {
    return protocolHandler -> {
        protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
    };
}

// Using virtual threads directly
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    executor.submit(() -> {
        // I/O-bound task
        Thread.sleep(1000);
        return "result";
    });
}

Pattern Matching

  • Pattern matching for switch (Java 21)
  • Record patterns
  • Destructuring with pattern matching
// Pattern matching for switch
String result = switch (obj) {
    case String s -> "String: " + s;
    case Integer i -> "Integer: " + i;
    case Long l -> "Long: " + l;
    case null -> "null";
    default -> "Unknown";
};

// Record patterns
record Point(int x, int y) {}

if (obj instanceof Point(int x, int y)) {
    System.out.println("x: " + x + ", y: " + y);
}

Records

  • Immutable data carriers
  • Automatically generates constructor, getters, equals(), hashCode(), toString()
public record UserDTO(String name, String email, LocalDate birthDate) {
    // Compact constructor for validation
    public UserDTO {
        if (name == null || name.isBlank()) {
            throw new IllegalArgumentException("Name cannot be blank");
        }
    }
}

Sealed Classes

  • Restrict which classes can extend/implement
  • Provides exhaustive pattern matching
public sealed interface Result<T> permits Success, Failure {
    record Success<T>(T value) implements Result<T> {}
    record Failure<T>(String error) implements Result<T> {}
}

Spring Boot 3.x Best Practices (2026)

Framework Setup:

  • Java 21+ as baseline (virtual threads, pattern matching)
  • Spring Boot 3.2+ (latest stable)
  • Spring Framework 6.x
  • Jakarta EE (not javax.*) - namespace change

Project Structure (Layered Architecture):

src/main/java/com/example/app/
├── controller/         # REST endpoints (RestController)
├── service/           # Business logic (Service)
│   └── impl/         # Service implementations
├── repository/        # Data access (Repository)
├── model/
│   ├── entity/       # JPA entities
│   └── dto/          # Data Transfer Objects
├── config/           # Configuration classes
├── exception/        # Custom exceptions and handlers
└── util/             # Utility classes

Controller Layer (RestController):

  • Handle HTTP requests/responses only
  • Delegate business logic to services
  • Use DTOs for request/response bodies
  • Never directly inject repositories
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {
    private final UserService userService;

    @GetMapping("/{id}")
    public ResponseEntity<UserDTO> getUser(@PathVariable Long id) {
        UserDTO user = userService.findById(id);
        return ResponseEntity.ok(user);
    }

    @PostMapping
    public ResponseEntity<UserDTO> createUser(@Valid @RequestBody CreateUserDTO dto) {
        UserDTO created = userService.create(dto);
        return ResponseEntity.status(HttpStatus.CREATED).body(created);
    }
}

Service Layer:

  • Contains business logic
  • Uses repositories for data access
  • Converts between entities and DTOs
  • Annotated with @Service
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class UserServiceImpl implements UserService {
    private final UserRepository userRepository;
    private final ModelMapper modelMapper;

    @Override
    public UserDTO findById(Long id) {
        User user = userRepository.findById(id)
            .orElseThrow(() -> new UserNotFoundException(id));
        return modelMapper.map(user, UserDTO.class);
    }

    @Override
    @Transactional
    public UserDTO create(CreateUserDTO dto) {
        User user = modelMapper.map(dto, User.class);
        User saved = userRepository.save(user);
        return modelMapper.map(saved, UserDTO.class);
    }
}

Repository Layer (Spring Data JPA):

  • Extends JpaRepository<Entity, ID>
  • Define custom query methods
  • Use @Query for complex queries
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByEmail(String email);

    @Query("SELECT u FROM User u WHERE u.createdAt > :date")
    List<User> findRecentUsers(@Param("date") LocalDateTime date);

    // Projection for performance
    @Query("SELECT new com.example.dto.UserSummaryDTO(u.id, u.name, u.email) FROM User u")
    List<UserSummaryDTO> findAllSummaries();
}

JPA/Hibernate Best Practices

Entity Design:

  • Use @Entity and @Table annotations
  • Always define @Id with generation strategy
  • Use @Column for constraints and mappings
  • Implement equals() and hashCode() based on business key
@Entity
@Table(name = "users")
@Getter @Setter
@NoArgsConstructor
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true)
    private String email;

    @Column(nullable = false)
    private String name;

    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Order> orders = new ArrayList<>();

    @CreatedDate
    @Column(nullable = false, updatable = false)
    private LocalDateTime createdAt;

    @LastModifiedDate
    private LocalDateTime updatedAt;
}

Performance Optimization:

  • Use @EntityGraph or JOIN FETCH to prevent N+1 queries
  • Lazy load associations by default
  • Use pagination for large result sets
  • Define proper indexes in database
@Query("SELECT u FROM User u JOIN FETCH u.orders WHERE u.id = :id")
Optional<User> findByIdWithOrders(@Param("id") Long id);

// Pagination
Page<User> findAll(Pageable pageable);

Testing (JUnit 5 + Mockito)

Unit Testing Services:

@ExtendWith(MockitoExtension.class)
class UserServiceImplTest {
    @Mock
    private UserRepository userRepository;

    @Mock
    private ModelMapper modelMapper;

    @InjectMocks
    private UserServiceImpl userService;

    @Test
    void findById_WhenUserExists_ReturnsUserDTO() {
        // Given
        Long userId = 1L;
        User user = new User();
        user.setId(userId);
        UserDTO expectedDTO = new UserDTO();

        when(userRepository.findById(userId)).thenReturn(Optional.of(user));
        when(modelMapper.map(user, UserDTO.class)).thenReturn(expectedDTO);

        // When
        UserDTO result = userService.findById(userId);

        // Then
        assertNotNull(result);
        verify(userRepository).findById(userId);
        verify(modelMapper).map(user, UserDTO.class);
    }
}

Integration Testing (Spring Boot Test):

@SpringBootTest
@AutoConfigureMockMvc
@Transactional
class UserControllerIntegrationTest {
    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private ObjectMapper objectMapper;

    @Test
    void createUser_WithValidData_ReturnsCreated() throws Exception {
        CreateUserDTO dto = new CreateUserDTO("John Doe", "john@example.com");

        mockMvc.perform(post("/api/users")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(dto)))
            .andExpect(status().isCreated())
            .andExpect(jsonPath("$.name").value("John Doe"));
    }
}

Build Tools (Maven & Gradle)

Maven (pom.xml):

<properties>
    <java.version>21</java.version>
    <spring-boot.version>3.2.0</spring-boot.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
</dependencies>

Gradle (build.gradle):

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.2.0'
    id 'io.spring.dependency-management' version '1.1.4'
}

java {
    sourceCompatibility = JavaVersion.VERSION_21
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-validation'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

Exception Handling

Global Exception Handler:

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(UserNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleUserNotFound(UserNotFoundException ex) {
        ErrorResponse error = new ErrorResponse(
            "USER_NOT_FOUND",
            ex.getMessage(),
            LocalDateTime.now()
        );
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponse> handleValidation(MethodArgumentNotValidException ex) {
        Map<String, String> errors = ex.getBindingResult()
            .getFieldErrors()
            .stream()
            .collect(Collectors.toMap(
                FieldError::getField,
                FieldError::getDefaultMessage
            ));

        ErrorResponse error = new ErrorResponse(
            "VALIDATION_ERROR",
            "Invalid input",
            errors,
            LocalDateTime.now()
        );
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
    }
}

Logging and Monitoring

Logging (SLF4J + Logback):

@Slf4j
@Service
public class UserServiceImpl implements UserService {

    public UserDTO findById(Long id) {
        log.debug("Finding user with id: {}", id);
        try {
            User user = userRepository.findById(id)
                .orElseThrow(() -> new UserNotFoundException(id));
            log.info("User found: {}", user.getEmail());
            return modelMapper.map(user, UserDTO.class);
        } catch (UserNotFoundException ex) {
            log.error("User not found with id: {}", id, ex);
            throw ex;
        }
    }
}

Actuator for Monitoring:

management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
  endpoint:
    health:
      show-details: always

Iron Laws

  1. ALWAYS use constructor injection over field injection with @Autowired — field injection hides dependencies, makes testing harder, and creates partially-initialized objects that crash at runtime if the context isn't fully loaded.
  2. NEVER use Optional.get() without a preceding isPresent() check or orElse()/orElseThrow() — unconditional get() throws NoSuchElementException on empty optionals, silently defeating Optional's entire purpose.
  3. ALWAYS handle @Transactional boundaries explicitly — calling a transactional method from within the same class bypasses the proxy and runs without a transaction, causing silent data inconsistency.
  4. NEVER use @Async without a configured TaskExecutor — Spring's default @Async executor uses a single-thread pool; concurrent async calls queue up and defeat parallelism.
  5. ALWAYS use @ControllerAdvice with specific exception types for error handling — catching Exception globally hides root causes; specific exception handlers produce correct HTTP status codes and meaningful error responses.

Anti-Patterns

Anti-Pattern Why It Fails Correct Approach
Field injection with @Autowired Hidden dependencies; untestable without Spring context; null in unit tests Constructor injection; all required dependencies declared as final fields
Optional.get() without check NoSuchElementException at runtime; defeats Optional's null-safety contract Use orElseThrow(), orElse(), or map()/flatMap() chains
@Transactional on same-class method calls Spring proxy bypassed; method runs outside transaction; data integrity lost Move transactional methods to a separate service bean; inject and call from outside
Default @Async thread pool Single-thread pool queues all tasks; async calls run sequentially Configure ThreadPoolTaskExecutor with pool size, queue, and rejection policy
Global @ExceptionHandler(Exception.class) Swallows specific exceptions; all errors return same generic 500 response Map specific exception types to HTTP status codes; use @ResponseStatus annotations

Consolidated Skills

This expert skill consolidates 1 individual skills:

  • java-expert

Memory Protocol (MANDATORY)

Before starting:

cat .claude/context/memory/learnings.md

After completing: Record any new patterns or exceptions discovered.

ASSUME INTERRUPTION: Your context may reset. If it's not in memory, it didn't happen.

Weekly Installs
57
GitHub Stars
16
First Seen
Jan 27, 2026
Installed on
github-copilot56
gemini-cli55
cursor55
codex54
kimi-cli54
opencode54