Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
personamanagmentlayer avatar

Java Expert

  • 284 installs
  • 41 repo stars
  • Updated March 30, 2026
  • personamanagmentlayer/pcl

java-expert is a Claude Code skill that guides expert-level Java 21+ and Spring Boot development with Maven/Gradle builds, testing, and enterprise API patterns for developers shipping JVM backends.

About

java-expert is an Apache-2.0 skill from PCL Team (version 1.0.0) that turns an AI agent into a senior Java engineer for Java 17+ codebases, with emphasis on Java 21+ language features, Spring Boot services, and Maven or Gradle dependency management. It is invoked when refactoring enterprise APIs, Android components, or backend modules where clean architecture, performance, and maintainable test coverage matter. Allowed tools include Read, Write, Edit, Glob, Grep, and Bash scoped to java, javac, mvn, gradle, and wrapper scripts. Developers reach for java-expert when they need opinionated JVM patterns applied consistently across services rather than one-off snippets.

  • Spring and enterprise service patterns
  • JVM performance and memory tuning
  • REST and messaging integrations
  • JUnit testing and build tooling
  • Modular project and dependency management

Java Expert by the numbers

  • 284 all-time installs (skills.sh)
  • +5 installs in the week ending Aug 4, 2026 (Skillselion tracking)
  • Ranked #18 of 89 Java & JVM skills by installs in the Skillselion catalog
  • Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/personamanagmentlayer/pcl --skill java-expert

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs284
repo stars41
Last updatedMarch 30, 2026
Repositorypersonamanagmentlayer/pcl

How do you refactor Spring Boot APIs with Java 21?

Build or refactor Java and JVM backends, Spring services, enterprise APIs, and Android components using solid patterns, dependency management, testing, and performance practices.

Who is it for?

Backend engineers maintaining Java 17+ or Java 21+ Spring Boot services who want agent-guided refactors, dependency fixes, and API hardening.

Skip if: Teams building non-JVM stacks or greenfield prototypes that do not need Spring, Maven, Gradle, or enterprise API conventions.

When should I use this skill?

A task mentions Java, JVM, Spring Boot, Maven, Gradle, enterprise APIs, or Android components needing refactor or new implementation.

What you get

Updated Java source files, Maven or Gradle build configs, Spring service classes, and test suites aligned to enterprise JVM patterns.

  • Refactored Java source
  • Updated build files
  • Spring service and test code

By the numbers

  • Requires Java >=17 and documents Java 21+ feature usage
  • Skill manifest version 1.0.0 under Apache-2.0 license

Files

SKILL.mdMarkdownGitHub ↗

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):

// 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, "alice@example.com");
System.out.println(user.name()); // Alice

Sealed Classes:

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:

// 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):

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:

String json = """
    {
        "name": "Alice",
        "age": 30,
        "email": "alice@example.com"
    }
    """;

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:

@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:

@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:

@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", "alice@example.com");
        var user = new User(1L, "Alice", "alice@example.com");

        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("alice@example.com");
                });

        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:

@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", "alice@example.com");
        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("alice@example.com");
                });
    }
}

Build Tools

Maven (pom.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):

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

// 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

// 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

// 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

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

// 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

// 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

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

@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

@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

// 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

// 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

// Bad
if (user.getStatus() == 1) { ... }

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

3. God Classes

// 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

// 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

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

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

Related skills

How it compares

Choose java-expert over generic language skills when the codebase is Spring Boot with Maven or Gradle and needs JVM-specific refactors, not syntax-only help.

FAQ

What Java version does java-expert require?

java-expert requires Java 17 or newer and emphasizes Java 21+ language features, Spring Boot, and Maven or Gradle workflows in its manifest and skill description.

Which build tools can java-expert run?

java-expert allows Bash access for java, javac, mvn, gradle, ./mvnw, and ./gradlew, so agents can compile, test, and verify JVM projects during edits.

Java & JVMbackendintegrationstesting

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.