| name | java-expert |
| description | Java development principles and decision-making. Modern Java 21+ patterns, Spring Boot, Quarkus, concurrency, JVM tuning. |
Java Expert - Modern Java 21+
Java development principles and decision-making.
Learn to THINK, not copy-paste patterns.
⚠️ How to Use This Skill
This skill teaches decision-making principles for Java development.
- ASK user for framework preference and use case
- Choose modern Java features (records, pattern matching)
- Don't default to old Java patterns
1. Framework Selection
Decision Tree
What are you building?
│
├── REST API (Main use case)
│ ├── Spring Boot (default, batteries included)
│ ├── Quarkus (fast startup, Kubernetes-native)
│ ├── Micronaut (fast startup, ahead-of-time)
│ └── Javalin (lightweight, simple)
│
├── Full-stack Web
│ └── Spring Boot + Thymeleaf/HTMX
│
├── Microservices
│ ├── Spring Boot
│ ├── Quarkus (if fast startup needed)
│ └── Micronaut (if low memory needed)
│
├── CLI Tool
│ ├── Picocli (rich CLI features)
│ └──jbang (scripting with Java)
│
└── Batch Processing
└── Spring Batch (complete framework)
Comparison Table
| Factor | Spring Boot | Quarkus | Micronaut | Javalin |
|---|
| Startup | Medium | Very Fast | Very Fast | Fast |
| Memory | Medium | Low | Low | Very Low |
| Ecosystem | Largest | Growing | Growing | Small |
| Learning Curve | Medium | Low | Low | Very Low |
| Best For | Enterprise | Cloud/K8s | K8s/Microservices | Light APIs |
2. Modern Java 21+ Features
Records (Immutable Data Classes)
@Data
public class User {
private Long id;
private String name;
private String email;
}
public record User(Long id, String name, String email) {
public static User anonymous() {
return new User(null, "anonymous", null);
}
}
Pattern Matching
if (obj instanceof String) {
String s = (String) obj;
System.out.println(s.toUpperCase());
}
if (obj instanceof String s) {
System.out.println(s.toUpperCase());
}
public String describe(Object obj) {
return switch (obj) {
case Integer i -> "Got integer: " + i;
case String s -> "Got string: " + s.toUpperCase();
case User u -> "Got user: " + u.name();
case null -> "It's null!";
default -> "Unknown type: " + obj.getClass().getSimpleName();
};
}
Virtual Threads (Loom - Java 21+)
@RestController
public class OldController {
@GetMapping("/users")
public List<User> getUsers() {
return userService.findAll();
}
}
@RestController
public class ModernController {
@GetMapping("/users")
public CompletableFuture<List<User>> getUsers() {
return CompletableFuture.supplyAsync(() -> {
return userService.findAll();
});
}
}
3. Dependency Injection
Constructor Injection (Recommended)
@Service
public class UserService {
private final UserRepository userRepository;
private final EmailService emailService;
public UserService(UserRepository userRepository,
EmailService emailService) {
this.userRepository = userRepository;
this.emailService = emailService;
}
}
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
private final EmailService emailService;
}
Record-based Configuration
public record DatabaseConfig(
String url,
String username,
String password,
int maxConnections
) {
public DatabaseConfig {
if (maxConnections < 1) {
throw new IllegalArgumentException(
"maxConnections must be positive");
}
}
}
@Configuration
@ConfigurationProperties(prefix = "database")
@Bean
public DatabaseConfig databaseConfig() {
return new DatabaseConfig(
"jdbc:postgresql://localhost:5432/db",
"user", "pass", 10
);
}
4. Database Access
JPA/Hibernate Best Practices
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String email;
@Column(name = "created_at")
private LocalDateTime createdAt;
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private Set<Order> orders;
}
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
@Query("SELECT u FROM User u WHERE u.name LIKE %:name%")
List<User> searchByName(@Param("name") String name);
@EntityGraph(attributePaths = {"orders"})
Optional<User> findWithOrdersById(Long id);
}
Performance Tips
| Pattern | Use For | Avoid |
|---|
@Transactional(readOnly = true) | Read queries | Writes |
SELECT FETCH / @EntityGraph | Related entities | Always |
@Query + nativeQuery=false | Complex queries | Simple queries |
| Pagination | Large datasets | All data at once |
5. Testing Patterns
Test Structure
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@Test
void findById_whenExists_returnsUser() {
User user = new User(1L, "test@test.com");
when(userRepository.findById(1L)).thenReturn(Optional.of(user));
Optional<User> result = userService.findById(1L);
assertTrue(result.isPresent());
assertEquals("test@test.com", result.get().email());
}
}
@DataJpaTest
class UserRepositoryTest {
@Autowired
private UserRepository userRepository;
@Test
void findByEmail_savesAndFinds() {
User user = new User("test@test.com");
userRepository.save(user);
Optional<User> found = userRepository.findByEmail("test@test.com");
assertTrue(found.isPresent());
}
}
Testing Tools
| Tool | Purpose | When to Use |
|---|
| JUnit 5 | Core testing | Always |
| Mockito | Mocking | Unit tests |
| AssertJ | Fluent assertions | All tests |
| Testcontainers | Docker databases | Integration tests |
| MockMvc | HTTP testing | REST controllers |
| WebTestClient | Reactive testing | WebFlux |
6. Build Tools
Maven vs Gradle
| Factor | Maven | Gradle |
|---|
| Syntax | XML | DSL (Kotlin/Groovy) |
| Build Speed | Good | Faster with daemon |
| Caching | Basic | Excellent |
| Learning Curve | Lower | Higher |
| Plugins | More mature | Flexible |
<groupId>com.example</groupId>
<artifactId>myapp</artifactId>
<version>1.0.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
// Gradle build.gradle
plugins {
id 'java'
id 'org.springframework.boot' version '3.2.0'
id 'io.spring.dependency-management-plugin'
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.jetbrains.kotlin:kotlin-stdlib'
}
7. Performance & JVM Tuning
JVM Arguments
-Xms512m -Xmx2g
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-Xlog:gc*:file=gc.log:time,uptime:filecount=5,filesize=10m
Monitoring Tools
| Tool | Purpose |
|---|
| VisualVM | CPU/Memory profiling |
| Async-profiler | Low-overhead profiling |
| JMC (Mission Control) | Flight recorder |
| Prometheus + Grafana | Metrics collection |
8. Error Handling
Exception Strategy
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(NotFoundException.class)
public ResponseEntity<ErrorResponse> handleNotFound(
NotFoundException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(new ErrorResponse("NOT_FOUND", ex.getMessage()));
}
@ExceptionHandler(ValidationException.class)
public ResponseEntity<ErrorResponse> handleValidation(
ValidationException ex) {
return ResponseEntity.badRequest()
.body(new ErrorResponse("VALIDATION_ERROR", ex.getMessage()));
}
}
public record ErrorResponse(String code, String message) {}
9. Decision Checklist
Before implementing:
❌ Anti-Patterns to Avoid
❌ DON'T:
- Use Lombok @Data on entities (use @Getter/@Setter sparingly)
- Use field injection (@Autowired private XxxService xxx)
- Skip Lazy loading considerations
- Use raw types (List instead of List)
- Catch generic Exception instead of specific
- Forget connection pool configuration
- Use synchronized instead of concurrent collections
✅ DO:
- Use Records for DTOs and entities (Java 16+)
- Use Virtual Threads for I/O (Java 21+)
- Use Constructor or Builder injection
- Use Optional for nullable returns
- Use Stream API for processing
- Test with @DataJpaTest for repositories
- Configure HikariCP properly
Remember: Java has evolved significantly.
Use modern Java (17/21+) features like records and virtual threads.
Think in modern Java, not Java 8.