java-expert

Virtual Threads (Project Loom)

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "java-expert" with this command: npx skills add oimiragieo/agent-studio/oimiragieo-agent-studio-java-expert

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&#x3C;UserDTO> getUser(@PathVariable Long id) {
    UserDTO user = userService.findById(id);
    return ResponseEntity.ok(user);
}

@PostMapping
public ResponseEntity&#x3C;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&#x3C;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&#x3C;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&#x3C;Order> orders = new ArrayList&#x3C;>();

@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&#x3C;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&#x3C;ErrorResponse> handleValidation(MethodArgumentNotValidException ex) {
    Map&#x3C;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

  • 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.

  • 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.

  • 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.

  • 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.

  • 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.

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Automation

filesystem

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

slack-notifications

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

chrome-browser

No summary provided by upstream source.

Repository SourceNeeds Review