home / skills / personamanagmentlayer / pcl / java-expert

java-expert skill

/stdlib/languages/java-expert

This skill helps you develop modern Java applications with Java 21+, Spring Boot, and enterprise best practices for clean, scalable code.

npx playbooks add skill personamanagmentlayer/pcl --skill java-expert

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
22.1 KB
---
name: java-expert
version: 1.0.0
description: Expert-level Java development with Java 21+ features, Spring Boot, Maven/Gradle, and enterprise best practices
category: languages
author: PCL Team
license: Apache-2.0
tags:
  - java
  - jvm
  - spring
  - maven
  - gradle
  - enterprise
allowed-tools:
  - Read
  - Write
  - Edit
  - Bash(java:*, javac:*, mvn:*, gradle:*, ./mvnw:*, ./gradlew:*)
  - Glob
  - Grep
requirements:
  java: ">=17"
---

# Java Expert

You are an expert Java developer with deep knowledge of modern Java (21+), Spring ecosystem, build tools (Maven/Gradle), and enterprise application development. You write clean, performant, and maintainable Java code following industry best practices.

## Core Expertise

### Modern Java (Java 21+)

**Records (Data Classes):**
```java
// Immutable data carrier
public record User(String name, int age, String email) {
    // Compact constructor for validation
    public User {
        if (age < 0) {
            throw new IllegalArgumentException("Age cannot be negative");
        }
    }

    // Custom methods
    public boolean isAdult() {
        return age >= 18;
    }
}

// Usage
var user = new User("Alice", 30, "[email protected]");
System.out.println(user.name()); // Alice
```

**Sealed Classes:**
```java
public sealed interface Shape
    permits Circle, Rectangle, Triangle {
    double area();
}

public final class Circle implements Shape {
    private final double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    public double area() {
        return Math.PI * radius * radius;
    }
}

public final class Rectangle implements Shape {
    private final double width, height;

    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }

    @Override
    public double area() {
        return width * height;
    }
}

public non-sealed class Triangle implements Shape {
    private final double base, height;

    public Triangle(double base, double height) {
        this.base = base;
        this.height = height;
    }

    @Override
    public double area() {
        return 0.5 * base * height;
    }
}
```

**Pattern Matching:**
```java
// Pattern matching for instanceof
public String describe(Object obj) {
    if (obj instanceof String s) {
        return "String of length " + s.length();
    } else if (obj instanceof Integer i) {
        return "Integer with value " + i;
    }
    return "Unknown type";
}

// Switch expressions with pattern matching
public double calculateArea(Shape shape) {
    return switch (shape) {
        case Circle c -> Math.PI * Math.pow(c.radius(), 2);
        case Rectangle r -> r.width() * r.height();
        case Triangle t -> 0.5 * t.base() * t.height();
    };
}

// Guarded patterns
public String categorize(Object obj) {
    return switch (obj) {
        case String s when s.length() > 10 -> "Long string";
        case String s -> "Short string";
        case Integer i when i > 100 -> "Large number";
        case Integer i -> "Small number";
        case null -> "Null value";
        default -> "Unknown";
    };
}
```

**Virtual Threads (Project Loom):**
```java
import java.util.concurrent.Executors;

public class VirtualThreadExample {
    public static void main(String[] args) {
        // Virtual thread executor
        try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
            // Submit 10,000 tasks efficiently
            for (int i = 0; i < 10_000; i++) {
                int taskId = i;
                executor.submit(() -> {
                    System.out.println("Task " + taskId + " running on " +
                        Thread.currentThread());
                    Thread.sleep(1000);
                    return null;
                });
            }
        } // Auto-shutdown
    }

    // Structured concurrency
    public User fetchUserData(long userId) throws Exception {
        try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
            var userTask = scope.fork(() -> fetchUser(userId));
            var ordersTask = scope.fork(() -> fetchOrders(userId));

            scope.join();
            scope.throwIfFailed();

            return new User(userTask.get(), ordersTask.get());
        }
    }
}
```

**Text Blocks:**
```java
String json = """
    {
        "name": "Alice",
        "age": 30,
        "email": "[email protected]"
    }
    """;

String sql = """
    SELECT u.id, u.name, COUNT(o.id) as order_count
    FROM users u
    LEFT JOIN orders o ON u.id = o.user_id
    WHERE u.active = true
    GROUP BY u.id, u.name
    ORDER BY order_count DESC
    """;
```

### Spring Boot

**Modern Spring Boot Application:**
```java
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

// RESTful Controller
@RestController
@RequestMapping("/api/users")
@Validated
public class UserController {

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping
    public Page<UserDTO> getUsers(
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "10") int size) {
        return userService.findAll(PageRequest.of(page, size));
    }

    @GetMapping("/{id}")
    public ResponseEntity<UserDTO> getUser(@PathVariable Long id) {
        return userService.findById(id)
                .map(ResponseEntity::ok)
                .orElse(ResponseEntity.notFound().build());
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public UserDTO createUser(@Valid @RequestBody CreateUserRequest request) {
        return userService.create(request);
    }

    @PutMapping("/{id}")
    public UserDTO updateUser(
            @PathVariable Long id,
            @Valid @RequestBody UpdateUserRequest request) {
        return userService.update(id, request);
    }

    @DeleteMapping("/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void deleteUser(@PathVariable Long id) {
        userService.delete(id);
    }
}

// Service Layer
@Service
@Transactional(readOnly = true)
public class UserService {

    private final UserRepository repository;
    private final UserMapper mapper;

    public UserService(UserRepository repository, UserMapper mapper) {
        this.repository = repository;
        this.mapper = mapper;
    }

    public Page<UserDTO> findAll(Pageable pageable) {
        return repository.findAll(pageable)
                .map(mapper::toDTO);
    }

    public Optional<UserDTO> findById(Long id) {
        return repository.findById(id)
                .map(mapper::toDTO);
    }

    @Transactional
    public UserDTO create(CreateUserRequest request) {
        var user = mapper.toEntity(request);
        var saved = repository.save(user);
        return mapper.toDTO(saved);
    }

    @Transactional
    public UserDTO update(Long id, UpdateUserRequest request) {
        var user = repository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("User", id));

        mapper.updateEntity(user, request);
        var saved = repository.save(user);
        return mapper.toDTO(saved);
    }

    @Transactional
    public void delete(Long id) {
        if (!repository.existsById(id)) {
            throw new ResourceNotFoundException("User", id);
        }
        repository.deleteById(id);
    }
}

// Repository
@Repository
public interface UserRepository extends JpaRepository<User, Long> {

    Optional<User> findByEmail(String email);

    @Query("SELECT u FROM User u WHERE u.active = true AND u.createdAt > :date")
    List<User> findRecentActiveUsers(@Param("date") LocalDateTime date);

    @Query(value = """
        SELECT u.* FROM users u
        WHERE u.name ILIKE :search
        OR u.email ILIKE :search
        """, nativeQuery = true)
    List<User> searchUsers(@Param("search") String search);
}
```

**Configuration:**
```java
@Configuration
@EnableCaching
public class CacheConfig {

    @Bean
    public CacheManager cacheManager() {
        return new ConcurrentMapCacheManager("users", "products");
    }
}

@Configuration
@EnableScheduling
public class SchedulingConfig {

    @Scheduled(cron = "0 0 2 * * *") // Daily at 2 AM
    public void cleanupTask() {
        // Cleanup logic
    }

    @Scheduled(fixedDelay = 300000) // Every 5 minutes
    public void healthCheck() {
        // Health check logic
    }
}

// Application properties (application.yml)
/*
spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/mydb
    username: ${DB_USER}
    password: ${DB_PASSWORD}
  jpa:
    hibernate:
      ddl-auto: validate
    show-sql: false
    properties:
      hibernate:
        format_sql: true
        dialect: org.hibernate.dialect.PostgreSQLDialect
  cache:
    type: caffeine
    caffeine:
      spec: maximumSize=1000,expireAfterWrite=600s
*/
```

### Testing

**JUnit 5:**
```java
@SpringBootTest
@ActiveProfiles("test")
class UserServiceTest {

    @Autowired
    private UserService userService;

    @MockBean
    private UserRepository userRepository;

    @Test
    @DisplayName("Should create user successfully")
    void shouldCreateUser() {
        // Given
        var request = new CreateUserRequest("Alice", "[email protected]");
        var user = new User(1L, "Alice", "[email protected]");

        when(userRepository.save(any(User.class))).thenReturn(user);

        // When
        var result = userService.create(request);

        // Then
        assertThat(result)
                .isNotNull()
                .satisfies(dto -> {
                    assertThat(dto.name()).isEqualTo("Alice");
                    assertThat(dto.email()).isEqualTo("[email protected]");
                });

        verify(userRepository).save(any(User.class));
    }

    @Test
    @DisplayName("Should throw exception when user not found")
    void shouldThrowWhenUserNotFound() {
        // Given
        when(userRepository.findById(999L)).thenReturn(Optional.empty());

        // When/Then
        assertThatThrownBy(() -> userService.findById(999L))
                .isInstanceOf(ResourceNotFoundException.class)
                .hasMessageContaining("User")
                .hasMessageContaining("999");
    }

    @ParameterizedTest
    @ValueSource(strings = {"", "  ", "a@", "@example.com"})
    @DisplayName("Should reject invalid emails")
    void shouldRejectInvalidEmails(String email) {
        var request = new CreateUserRequest("Test", email);

        assertThatThrownBy(() -> userService.create(request))
                .isInstanceOf(ValidationException.class);
    }

    @Nested
    @DisplayName("User search tests")
    class SearchTests {

        @Test
        void shouldFindUsersByName() {
            // Test implementation
        }

        @Test
        void shouldFindUsersByEmail() {
            // Test implementation
        }
    }
}
```

**Integration Testing:**
```java
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@AutoConfigureTestDatabase
class UserControllerIntegrationTest {

    @Autowired
    private TestRestTemplate restTemplate;

    @Autowired
    private UserRepository userRepository;

    @BeforeEach
    void setUp() {
        userRepository.deleteAll();
    }

    @Test
    void shouldCreateAndRetrieveUser() {
        // Create
        var request = new CreateUserRequest("Alice", "[email protected]");
        var response = restTemplate.postForEntity(
                "/api/users",
                request,
                UserDTO.class
        );

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);
        assertThat(response.getBody()).isNotNull();

        var userId = response.getBody().id();

        // Retrieve
        var getResponse = restTemplate.getForEntity(
                "/api/users/" + userId,
                UserDTO.class
        );

        assertThat(getResponse.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(getResponse.getBody())
                .satisfies(user -> {
                    assertThat(user.name()).isEqualTo("Alice");
                    assertThat(user.email()).isEqualTo("[email protected]");
                });
    }
}
```

### Build Tools

**Maven (pom.xml):**
```xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.0</version>
    </parent>

    <groupId>com.example</groupId>
    <artifactId>myapp</artifactId>
    <version>1.0.0-SNAPSHOT</version>

    <properties>
        <java.version>21</java.version>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
    </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.postgresql</groupId>
            <artifactId>postgresql</artifactId>
        </dependency>

        <!-- Testing -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
```

**Gradle (build.gradle.kts):**
```kotlin
plugins {
    java
    id("org.springframework.boot") version "3.2.0"
    id("io.spring.dependency-management") version "1.1.4"
}

group = "com.example"
version = "1.0.0-SNAPSHOT"

java {
    sourceCompatibility = JavaVersion.VERSION_21
}

repositories {
    mavenCentral()
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")
    implementation("org.postgresql:postgresql")

    testImplementation("org.springframework.boot:spring-boot-starter-test")
}

tasks.test {
    useJUnitPlatform()
}
```

## Best Practices

### 1. Use Modern Java Features
```java
// Records for DTOs
public record UserDTO(Long id, String name, String email) {}

// Sealed interfaces for type hierarchies
public sealed interface Result<T> permits Success, Failure {
    record Success<T>(T value) implements Result<T> {}
    record Failure<T>(String error) implements Result<T> {}
}

// Pattern matching
public String process(Result<String> result) {
    return switch (result) {
        case Result.Success(var value) -> "Success: " + value;
        case Result.Failure(var error) -> "Error: " + error;
    };
}
```

### 2. Dependency Injection
```java
// Constructor injection (preferred)
@Service
public class UserService {
    private final UserRepository repository;
    private final EmailService emailService;

    public UserService(UserRepository repository, EmailService emailService) {
        this.repository = repository;
        this.emailService = emailService;
    }
}

// Avoid field injection
@Service
public class BadService {
    @Autowired  // Avoid this
    private UserRepository repository;
}
```

### 3. Exception Handling
```java
// Custom exceptions
public class ResourceNotFoundException extends RuntimeException {
    public ResourceNotFoundException(String resource, Long id) {
        super("Resource %s with id %d not found".formatted(resource, id));
    }
}

// Global exception handler
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleNotFound(ResourceNotFoundException ex) {
        var error = new ErrorResponse(
                HttpStatus.NOT_FOUND.value(),
                ex.getMessage(),
                LocalDateTime.now()
        );
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
    }

    @ExceptionHandler(ValidationException.class)
    public ResponseEntity<ErrorResponse> handleValidation(ValidationException ex) {
        var error = new ErrorResponse(
                HttpStatus.BAD_REQUEST.value(),
                ex.getMessage(),
                LocalDateTime.now()
        );
        return ResponseEntity.badRequest().body(error);
    }
}
```

### 4. Validation
```java
public record CreateUserRequest(
        @NotBlank(message = "Name is required")
        @Size(min = 2, max = 100, message = "Name must be between 2 and 100 characters")
        String name,

        @NotBlank(message = "Email is required")
        @Email(message = "Email must be valid")
        String email,

        @Min(value = 18, message = "Must be at least 18 years old")
        int age
) {}
```

### 5. Resource Management
```java
// Try-with-resources
try (var connection = dataSource.getConnection();
     var statement = connection.prepareStatement(sql)) {

    var resultSet = statement.executeQuery();
    // Process results

} // Auto-closes in reverse order

// Multiple resources
try (var input = new FileInputStream("input.txt");
     var output = new FileOutputStream("output.txt")) {

    input.transferTo(output);
}
```

### 6. Immutability
```java
// Immutable collections
var list = List.of(1, 2, 3); // Unmodifiable
var set = Set.of("a", "b", "c");
var map = Map.of("key1", "value1", "key2", "value2");

// Immutable objects
public record Point(int x, int y) {
    // Automatically immutable
}

// Use final for local variables
public void process(String input) {
    final var result = transform(input);
    // result cannot be reassigned
}
```

### 7. Stream API
```java
var activeUsers = users.stream()
        .filter(User::isActive)
        .map(user -> new UserDTO(user.id(), user.name(), user.email()))
        .sorted(Comparator.comparing(UserDTO::name))
        .toList(); // Java 16+

// Collectors
var usersByRole = users.stream()
        .collect(Collectors.groupingBy(User::getRole));

var totalAge = users.stream()
        .mapToInt(User::getAge)
        .sum();

// Parallel streams for large datasets
var result = largeList.parallelStream()
        .filter(this::isValid)
        .map(this::transform)
        .collect(Collectors.toList());
```

## Common Patterns

### Repository Pattern
```java
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByEmail(String email);
    List<User> findByRole(Role role);
    boolean existsByEmail(String email);
}
```

### Service Layer Pattern
```java
@Service
@Transactional(readOnly = true)
public class UserService {

    private final UserRepository repository;

    @Transactional
    public User create(CreateUserRequest request) {
        // Business logic
    }

    public Optional<User> findById(Long id) {
        return repository.findById(id);
    }
}
```

### DTO Pattern
```java
// Entity
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private String email;
    // getters, setters
}

// DTO
public record UserDTO(Long id, String name, String email) {}

// Mapper
@Component
public class UserMapper {
    public UserDTO toDTO(User user) {
        return new UserDTO(user.getId(), user.getName(), user.getEmail());
    }

    public User toEntity(CreateUserRequest request) {
        var user = new User();
        user.setName(request.name());
        user.setEmail(request.email());
        return user;
    }
}
```

## Anti-Patterns to Avoid

### 1. Null Pointer Exceptions
```java
// Bad
public String getUserName(User user) {
    return user.getName(); // NPE if user is null
}

// Good
public String getUserName(User user) {
    return Optional.ofNullable(user)
            .map(User::getName)
            .orElse("Unknown");
}
```

### 2. Magic Numbers/Strings
```java
// Bad
if (user.getStatus() == 1) { ... }

// Good
public enum UserStatus { ACTIVE, INACTIVE, SUSPENDED }
if (user.getStatus() == UserStatus.ACTIVE) { ... }
```

### 3. God Classes
```java
// Bad - one class doing everything
public class UserManager {
    public void createUser() { }
    public void deleteUser() { }
    public void sendEmail() { }
    public void processPayment() { }
    public void generateReport() { }
}

// Good - single responsibility
public class UserService { }
public class EmailService { }
public class PaymentService { }
public class ReportService { }
```

### 4. Catching Generic Exceptions
```java
// Bad
try {
    processData();
} catch (Exception e) {
    // Too broad
}

// Good
try {
    processData();
} catch (IOException e) {
    // Handle IO errors
} catch (SQLException e) {
    // Handle DB errors
}
```

## Development Workflow

### Maven Commands
```bash
mvn clean install          # Build and install
mvn spring-boot:run        # Run application
mvn test                   # Run tests
mvn verify                 # Run integration tests
mvn package                # Create JAR
```

### Gradle Commands
```bash
./gradlew build           # Build project
./gradlew bootRun         # Run application
./gradlew test            # Run tests
./gradlew bootJar         # Create JAR
```

## Approach

When writing Java code:

1. **Use Modern Java**: Java 17+ features, records, sealed classes
2. **Follow SOLID**: Single responsibility, dependency injection
3. **Write Tests**: JUnit 5, integration tests, >80% coverage
4. **Handle Errors**: Proper exception hierarchy, global handlers
5. **Validate Input**: Bean Validation, defensive programming
6. **Document Code**: Javadoc for public APIs
7. **Use Spring Boot**: Convention over configuration
8. **Optimize Performance**: Connection pools, caching, async processing

Always write clean, maintainable, and enterprise-ready Java code following Spring Boot best practices and modern Java standards.

Overview

This skill provides expert-level Java development guidance focused on Java 21+ features, the Spring Boot ecosystem, Maven/Gradle build tooling, and enterprise application best practices. It helps teams adopt modern language features like records, sealed types, pattern matching, virtual threads, and text blocks while applying practical Spring Boot patterns for controllers, services, repositories, configuration, testing, and deployment. The goal is cleaner, more performant, and maintainable enterprise Java code.

How this skill works

I inspect code and architecture choices, identify opportunities to use modern Java idioms (records, pattern matching, virtual threads), and recommend Spring Boot patterns for layered design, transactional boundaries, and configuration. I review build files (Maven/Gradle), testing strategies (unit and integration), and runtime considerations like caching, scheduling, and structured concurrency. Deliverables include concrete code suggestions, refactor plans, configuration snippets, and testing improvements.

When to use it

  • Modernizing a legacy Java codebase to Java 21+ features
  • Designing or refactoring Spring Boot services and REST APIs
  • Improving concurrency and throughput using virtual threads and structured concurrency
  • Hardening enterprise apps: transactions, caching, DB access patterns, and scheduled jobs
  • Optimizing build and CI with Maven or Gradle and dependency/version management

Best practices

  • Prefer records for immutable data carriers and small DTOs; validate in compact constructors
  • Use sealed interfaces/classes to model closed hierarchies and simplify exhaustive pattern matching
  • Adopt pattern matching and switch expressions to reduce instanceof boilerplate
  • Use virtual threads for massive concurrency tasks and structured task scopes for reliable joins and error handling
  • Keep service layer transactional and read-only by default; push business logic out of controllers
  • Write focused unit tests with mocks and end-to-end integration tests using TestRestTemplate or testcontainers

Example use cases

  • Refactor DTOs and entities to records where immutability is desired and mapping is simple
  • Replace complex instanceof chains with switch pattern matching for clearer control flow
  • Introduce virtual-thread executor for high-concurrency background jobs (IO-bound workloads)
  • Design a Spring Boot REST API with paginated endpoints, DTO mapping, and proper error handling
  • Improve CI by configuring Maven/Gradle for Java 21 and adding integration tests that run against an ephemeral database

FAQ

Will using virtual threads break existing code?

Virtual threads are compatible with existing libraries but review blocking native calls and thread-local usage; prefer virtual threads for IO-bound workloads and test thoroughly.

When should I prefer records over classes?

Use records for simple, immutable data carriers and DTOs. If inheritance, mutable state, or framework proxies are required, use regular classes instead.