Java Development
SkillDev toolsWrite Java code following best practices. Use when developing Java applications with Spring Boot, Maven/Gradle. Covers modern Java features, Virtual Threads, and testing.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Java Development skill
What this skill tells your AI
The instructions your AI receives, as published by nguyenhuuca/assessment in .claude/skills/languages/java/SKILL.md and read by ahel’s review.
Project Setup
# Maven project
mvn archetype:generate -DgroupId=com.example -DartifactId=my-app \
-DarchetypeArtifactId=maven-archetype-quickstart
# Spring Boot project
curl https://start.spring.io/starter.zip \
-d dependencies=web,data-jpa,postgresql \
-d type=maven-project \
-d javaVersion=24 \
-o project.zip
pom.xml (Java 24 with Preview Features)
<properties>
<java.version>24</java.version>
<maven.compiler.source>24</maven.compiler.source>
<maven.compiler.target>24</maven.compiler.target>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<release>24</release>
<compilerArgs>--enable-preview</compilerArgs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>--enable-preview</argLine>
</configuration>
</plugin>
</plugins>
</build>
Modern Java Features
Records (Java 14+)
// Immutable data class
public record User(Long id, String email, String name) {
// Compact constructor for validation
public User {
if (email == null || !email.contains("@")) {
throw new IllegalArgumentException("Invalid email");
}
}
}
// Usage
User user = new User(1L, "test@example.com", "John");
String email = user.email(); // Getter auto-generated
Pattern Matching (Java 21+)
// Pattern matching in switch
String format(Object obj) {
return switch (obj) {
case Integer i -> "int %d".formatted(i);
case String s when s.length() > 10 -> "long string: %s".formatted(s);
case String s -> "short string: %s".formatted(s);
case null -> "null";
default -> obj.toString();
};
}
// Pattern matching in instanceof
if (obj instanceof String s && s.length() > 5) {
System.out.println(s.toUpperCase());
}
Virtual Threads (Java 21+)
// Enable in Spring Boot
@Configuration
public class ThreadConfig {
@Bean
public TomcatProtocolHandlerCustomizer<?> protocolHandlerVirtualThreadExecutorCustomizer() {
return protocolHandler -> {
protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
};
}
}
// Manual usage
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
List<Future<String>> futures = urls.stream()
.map(url -> executor.submit(() -> fetchData(url)))
.toList();
for (Future<String> future : futures) {
String result = future.get();
}
}
StructuredTaskScope (Java 21+ Preview)
// Structured concurrency
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Future<String> user = scope.fork(() -> fetchUser(userId));
Future<List<Order>> orders = scope.fork(() -> fetchOrders(userId));
scope.join(); // Wait for all tasks
scope.throwIfFailed(); // Propagate errors
return new UserProfile(user.resultNow(), orders.resultNow());
}
Text Blocks (Java 15+)
String json = """
{
"name": "%s",
"age": %d,
"email": "%s"
}
""".formatted(name, age, email);
String sql = """
SELECT u.id, u.name, o.total
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE u.status = 'ACTIVE'
""";
Spring Boot Patterns
Controller with Validation
@RestController
@RequestMapping("/api/users")
@Validated
public class UserController {
private final UserService userService;
@PostMapping
public ResponseEntity<UserDto> create(@Valid @RequestBody CreateUserRequest request) {
UserDto user = userService.create(request);
return ResponseEntity.status(HttpStatus.CREATED).body(user);
}
@GetMapping("/{id}")
public ResponseEntity<UserDto> getById(@PathVariable @Positive Long id) {
return userService.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
}
Service Layer
@Service
@Transactional
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
@Override
public UserDto create(CreateUserRequest request) {
// Validation
if (userRepository.existsByEmail(request.email())) {
throw new DuplicateEmailException("Email already exists");
}
// Business logic
User user = User.builder()
.email(request.email())
.password(passwordEncoder.encode(request.password()))
.createdAt(Instant.now())
.build();
User saved = userRepository.save(user);
return mapToDto(saved);
}
@Override
@Transactional(readOnly = true)
public Optional<UserDto> findById(Long id) {
return userRepository.findById(id)
.map(this::mapToDto);
}
}
Repository with Custom Query
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
boolean existsByEmail(String email);
Optional<User> findByEmail(String email);
@Query("""
SELECT u FROM User u
WHERE u.status = :status
AND u.createdAt > :since
ORDER BY u.createdAt DESC
""")
Page<User> findActiveUsers(
@Param("status") UserStatus status,
@Param("since") Instant since,
Pageable pageable
);
@Modifying
@Query("UPDATE User u SET u.lastLogin = :now WHERE u.id = :id")
void updateLastLogin(@Param("id") Long id, @Param("now") Instant now);
}
Exception Handling
Custom Exception
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String message) {
super(message);
}
}
// Global exception handler
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleNotFound(ResourceNotFoundException ex) {
ErrorResponse error = new ErrorResponse(
HttpStatus.NOT_FOUND.value(),
ex.getMessage(),
Instant.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(
HttpStatus.BAD_REQUEST.value(),
"Validation failed",
errors,
Instant.now()
);
return ResponseEntity.badRequest().body(error);
}
}
Testing
Unit Test with JUnit 5 & Mockito
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@Mock
private PasswordEncoder passwordEncoder;
@InjectMocks
private UserServiceImpl userService;
@Test
void create_ValidRequest_ReturnsUser() {
// Given
CreateUserRequest request = new CreateUserRequest("test@example.com", "password");
User savedUser = User.builder()
.id(1L)
.email(request.email())
.build();
when(userRepository.existsByEmail(request.email())).thenReturn(false);
when(passwordEncoder.encode(request.password())).thenReturn("encoded");
when(userRepository.save(any(User.class))).thenReturn(savedUser);
// When
UserDto result = userService.create(request);
// Then
assertThat(result.email()).isEqualTo("test@example.com");
verify(userRepository).save(any(User.class));
}
@Test
void create_DuplicateEmail_ThrowsException() {
// Given
CreateUserRequest request = new CreateUserRequest("test@example.com", "password");
when(userRepository.existsByEmail(request.email())).thenReturn(true);
// When & Then
assertThrows(DuplicateEmailException.class, () -> userService.create(request));
verify(userRepository, never()).save(any());
}
}
Integration Test with TestContainers
@SpringBootTest
@Testcontainers
class UserRepositoryIntegrationTest {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")
.withDatabaseName("testdb");
@DynamicPropertySource
static void properties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
}
@Autowired
private UserRepository userRepository;
@Test
void findByEmail_ExistingUser_ReturnsUser() {
// Given
User user = User.builder()
.email("test@example.com")
.password("password")
.build();
userRepository.save(user);
// When
Optional<User> found = userRepository.findByEmail("test@example.com");
// Then
assertThat(found).isPresent();
assertThat(found.get().getEmail()).isEqualTo("test@example.com");
}
}
REST API Test with MockMvc
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@Test
void getById_ExistingUser_ReturnsOk() throws Exception {
// Given
UserDto user = new UserDto(1L, "test@example.com", "Test User");
when(userService.findById(1L)).thenReturn(Optional.of(user));
// When & Then
mockMvc.perform(get("/api/users/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.email").value("test@example.com"))
.andExpect(jsonPath("$.name").value("Test User"));
}
@Test
void create_InvalidEmail_ReturnsBadRequest() throws Exception {
// Given
String requestBody = """
{
"email": "invalid-email",
"password": "password"
}
""";
// When & Then
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(requestBody))
.andExpect(status().isBadRequest());
}
}
Best Practices
Use Optional for Nullable Returns
// Good
public Optional<User> findById(Long id) {
return userRepository.findById(id);
}
// Bad
public User findById(Long id) {
return userRepository.findById(id).orElse(null);
}
Use Builder Pattern with Lombok
@Entity
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String email;
private String password;
@Builder.Default
private Instant createdAt = Instant.now();
}
// Usage
User user = User.builder()
.email("test@example.com")
.password("encoded")
.build();
Validation with Jakarta Bean Validation
public record CreateUserRequest(
@NotBlank(message = "Email is required")
@Email(message = "Invalid email format")
String email,
@NotBlank(message = "Password is required")
@Size(min = 8, message = "Password must be at least 8 characters")
String password
) {}
Tooling
# Maven commands
mvn clean install # Build project
mvn test # Run tests
mvn verify # Run tests + integration tests
mvn jacoco:report # Generate coverage report
# Run with preview features
mvn clean package
java --enable-preview -jar target/app.jar
# Spring Boot
mvn spring-boot:run # Run application
mvn spring-boot:build-image # Build Docker image
Common Dependencies
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<!-- Testing -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- TestContainers -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<scope>test</scope>
</dependency>
Signals
- GitHub stars
- 34
- Forks
- 25
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
java-nguyenhuuca- Source
- github.com/nguyenhuuca/assessment