FeaturesSkillsJava

Metadata

FieldValue
Typecontext
Applies tojava, maven, gradle, junit, spring, jakarta, quarkus, mockito, testcontainers, hibernate, jpa
File extensions.java

Java Coding Standards

Core Principles

  1. Simplicity: Simple, understandable code
  2. Readability: Readability over cleverness
  3. Maintainability: Code that’s easy to maintain
  4. Testability: Code that’s easy to test
  5. SOLID: Follow SOLID principles for object-oriented design
  6. DRY: Don’t Repeat Yourself - but don’t overdo it

General Rules

  • Early Returns: Use early returns to avoid nesting
  • Descriptive Names: Meaningful names for classes, methods, and variables
  • Minimal Changes: Only change relevant code parts
  • No Over-Engineering: No unnecessary complexity
  • Immutability: Prefer immutable objects where possible
  • Minimal Comments: Code should be self-explanatory. No redundant comments!

Naming Conventions

ElementConventionExample
ClassesPascalCaseUserService, OrderRepository
InterfacesPascalCaseUserRepository, PaymentProcessor
MethodscamelCasegetUserById, calculateTotal
VariablescamelCasefirstName, totalAmount
ConstantsUPPER_SNAKE_CASEMAX_RETRY_COUNT, DEFAULT_TIMEOUT
Packageslowercase.dot.separatedcom.example.service, com.example.repository
Test ClassesClassNameTestUserServiceTest, OrderRepositoryTest
Test Methodsdescriptive_snake_case or camelCaseshouldReturnUserWhenIdExists

Project Structure

Maven Project

myproject/
├── pom.xml
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/example/myapp/
│   │   │       ├── Application.java        # Main entry point
│   │   │       ├── config/
│   │   │       │   └── AppConfig.java      # Configuration
│   │   │       ├── domain/
│   │   │       │   └── User.java           # Domain models
│   │   │       ├── repository/
│   │   │       │   └── UserRepository.java # Data access
│   │   │       ├── service/
│   │   │       │   └── UserService.java    # Business logic
│   │   │       └── controller/
│   │   │           └── UserController.java # REST endpoints
│   │   └── resources/
│   │       ├── application.properties
│   │       └── application-dev.properties
│   └── test/
│       ├── java/
│       │   └── com/example/myapp/
│       │       ├── service/
│       │       │   └── UserServiceTest.java
│       │       └── repository/
│       │           └── UserRepositoryTest.java
│       └── resources/
│           └── application-test.properties
└── README.md

Gradle Project

myproject/
├── build.gradle or build.gradle.kts
├── settings.gradle or settings.gradle.kts
├── src/
│   ├── main/
│   │   └── java/...      # Same structure as Maven
│   └── test/
│       └── java/...      # Same structure as Maven
└── README.md

Modern Java Features

Recommended: Use the latest LTS for new projects (currently Java 21 or Java 25).

Java 17 Features

Records (Immutable Data)

// Replace verbose POJOs with records
public record User(String id, String name, String email) {
    // Compact constructor for validation
    public User {
        if (name == null || name.isBlank()) {
            throw new IllegalArgumentException("Name cannot be blank");
        }
    }
 
    // Custom methods allowed
    public String displayName() {
        return name.toUpperCase();
    }
}
 
// Usage
var user = new User("1", "John Doe", "john@example.com");
System.out.println(user.name()); // Auto-generated accessor

Sealed Classes (Restricted Hierarchies)

// Define closed set of subclasses
public sealed interface Result<T>
    permits Success, Failure {
}
 
public record Success<T>(T value) implements Result<T> {}
public record Failure<T>(String error) implements Result<T> {}
 
// Pattern matching exhaustiveness
public <T> void handleResult(Result<T> result) {
    switch (result) {
        case Success<T> s -> System.out.println("Success: " + s.value());
        case Failure<T> f -> System.out.println("Error: " + f.error());
        // No default needed - compiler knows all cases
    }
}

Pattern Matching (instanceof)

// Old way
if (obj instanceof String) {
    String s = (String) obj;
    System.out.println(s.toUpperCase());
}
 
// Modern way - pattern matching
if (obj instanceof String s) {
    System.out.println(s.toUpperCase());
}
 
// Pattern matching in switch
public String formatValue(Object obj) {
    return switch (obj) {
        case Integer i -> "Number: " + i;
        case String s -> "Text: " + s;
        case null -> "null";
        default -> "Unknown: " + obj;
    };
}

Text Blocks (Multi-line Strings)

// Old way
String json = "{\n" +
              "  \"name\": \"John\",\n" +
              "  \"age\": 30\n" +
              "}";
 
// Modern way - text block
String json = """
    {
      "name": "John",
      "age": 30
    }
    """;

Switch Expressions

// Old switch statement
String result;
switch (day) {
    case MONDAY:
    case FRIDAY:
        result = "Work";
        break;
    case SATURDAY:
    case SUNDAY:
        result = "Weekend";
        break;
    default:
        result = "Unknown";
}
 
// Modern switch expression
String result = switch (day) {
    case MONDAY, FRIDAY -> "Work";
    case SATURDAY, SUNDAY -> "Weekend";
    default -> "Unknown";
};

Java 21 Features

Virtual Threads

// Traditional platform threads - expensive, limited scalability
try (var executor = Executors.newFixedThreadPool(100)) {
    for (int i = 0; i < 10000; i++) {
        executor.submit(() -> fetchData());
    }
}
 
// Virtual threads - lightweight, millions possible
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    for (int i = 0; i < 10000; i++) {
        executor.submit(() -> fetchData());
    }
}
 
// Start virtual thread directly
Thread.startVirtualThread(() -> {
    // Task code
});
 
// Structured concurrency (preview in Java 21)
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
    Future<String> user = scope.fork(() -> fetchUser(id));
    Future<List<Order>> orders = scope.fork(() -> fetchOrders(id));
 
    scope.join();           // Wait for all tasks
    scope.throwIfFailed();  // Throw if any failed
 
    return new UserDetails(user.resultNow(), orders.resultNow());
}

Sequenced Collections

// New interfaces: SequencedCollection, SequencedSet, SequencedMap
 
// Get first and last elements
List<String> list = List.of("a", "b", "c");
String first = list.getFirst();  // "a"
String last = list.getLast();    // "c"
 
// Reversed view (not a copy!)
List<String> reversed = list.reversed();
 
// Works with Set
LinkedHashSet<String> set = new LinkedHashSet<>(List.of("a", "b", "c"));
set.addFirst("z");  // z, a, b, c
set.addLast("x");   // z, a, b, c, x
 
// Works with Map
LinkedHashMap<String, Integer> map = new LinkedHashMap<>();
map.putFirst("first", 1);
map.putLast("last", 99);

Pattern Matching for switch (finalized)

// Pattern matching with null handling
String formatted = switch (obj) {
    case null -> "null";
    case Integer i -> "Number: " + i;
    case String s -> "Text: " + s;
    case List<?> list -> "List of " + list.size() + " items";
    default -> "Unknown";
};
 
// Guard patterns
String category = switch (value) {
    case Integer i when i < 0 -> "Negative";
    case Integer i when i == 0 -> "Zero";
    case Integer i -> "Positive";
    default -> "Not a number";
};

Record Patterns (finalized)

record Point(int x, int y) {}
record Circle(Point center, int radius) {}
 
// Deconstruct records in patterns
static void printPoint(Object obj) {
    if (obj instanceof Point(int x, int y)) {
        System.out.println("x: " + x + ", y: " + y);
    }
}
 
// Nested deconstruction
static void printCircle(Object obj) {
    if (obj instanceof Circle(Point(int x, int y), int r)) {
        System.out.println("Circle at (" + x + ", " + y + ") with radius " + r);
    }
}
 
// In switch
static String describe(Object obj) {
    return switch (obj) {
        case Point(int x, int y) -> "Point at (" + x + ", " + y + ")";
        case Circle(Point(int x, int y), int r) ->
            "Circle at (" + x + ", " + y + ") radius " + r;
        default -> "Unknown shape";
    };
}

Java 25 Features

Flexible Main Methods

// No longer need public static void main(String[] args)
 
// Simple main - for beginners and scripts
void main() {
    System.out.println("Hello World");
}
 
// With arguments (if needed)
void main(String[] args) {
    System.out.println("Args: " + Arrays.toString(args));
}
 
// Instance main (access to instance fields/methods)
class App {
    private String message = "Hello";
 
    void main() {
        System.out.println(message);  // Access instance field
        greet();                       // Call instance method
    }
 
    void greet() {
        System.out.println("Welcome");
    }
}

Scoped Values (Alternative to ThreadLocal)

// ThreadLocal (old way) - must be cleaned up manually
private static final ThreadLocal<User> CURRENT_USER = new ThreadLocal<>();
 
// Scoped Values (new way) - automatically cleaned up
public static final ScopedValue<User> CURRENT_USER = ScopedValue.newInstance();
 
// Set scoped value (automatically reverted when block exits)
ScopedValue.runWhere(CURRENT_USER, user, () -> {
    // Value is available here
    User currentUser = CURRENT_USER.get();
    processRequest(currentUser);
    // Value automatically cleared when block exits
});
 
// Inherited by virtual threads
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    ScopedValue.runWhere(CURRENT_USER, user, () -> {
        executor.submit(() -> {
            // Virtual thread inherits scoped value
            User u = CURRENT_USER.get();
            handleTask(u);
        });
    });
}

Primitive Pattern Matching (Preview)

// Pattern matching now works with primitives
 
static String classify(int value) {
    return switch (value) {
        case 0 -> "zero";
        case int i when i > 0 -> "positive";
        case int i when i < 0 -> "negative";
    };
}
 
// Type patterns for primitives
Object obj = 42;
if (obj instanceof int i) {
    System.out.println("Integer: " + i);
}

Gatherers (Custom Stream Operations)

// Create custom intermediate stream operations
 
// Built-in gatherers
Stream.of(1, 2, 3, 4, 5)
    .gather(Gatherers.windowFixed(2))  // [[1,2], [3,4], [5]]
    .toList();
 
Stream.of(1, 2, 3, 4, 5)
    .gather(Gatherers.windowSliding(2))  // [[1,2], [2,3], [3,4], [4,5]]
    .toList();
 
// Custom gatherer example (simplified)
var sumAndCount = Gatherer.of(
    () -> new long[2],  // [sum, count]
    (state, element, downstream) -> {
        state[0] += element;
        state[1]++;
        return true;
    },
    (state, downstream) -> {
        downstream.push(state[0] / (double) state[1]);
    }
);
 
double average = Stream.of(1, 2, 3, 4, 5)
    .gather(sumAndCount)
    .findFirst()
    .orElse(0.0);

Code Organization

Visibility Modifiers

// Use the most restrictive visibility possible
public class UserService {
    private final UserRepository repository;  // private - internal state
 
    public UserService(UserRepository repository) {  // public - API
        this.repository = repository;
    }
 
    public User findById(String id) {  // public - API
        return validateAndFetch(id);
    }
 
    private User validateAndFetch(String id) {  // private - internal logic
        if (id == null || id.isBlank()) {
            throw new IllegalArgumentException("ID cannot be blank");
        }
        return repository.findById(id)
            .orElseThrow(() -> new UserNotFoundException(id));
    }
}

SOLID Principles

Single Responsibility Principle

// BAD - class does too much
public class UserService {
    public void createUser(User user) { /* ... */ }
    public void sendEmail(String to, String message) { /* ... */ }
    public void logActivity(String activity) { /* ... */ }
}
 
// GOOD - single responsibility
public class UserService {
    private final UserRepository repository;
    private final EmailService emailService;
    private final AuditService auditService;
 
    public void createUser(User user) {
        repository.save(user);
        emailService.sendWelcomeEmail(user);
        auditService.logUserCreation(user.id());
    }
}

Dependency Inversion

// GOOD - depend on abstractions, not implementations
public class UserService {
    private final UserRepository repository;  // interface, not concrete class
 
    public UserService(UserRepository repository) {
        this.repository = repository;
    }
}

Exception Handling

Checked vs Unchecked

// Checked exceptions for recoverable errors (use sparingly)
public class UserNotFoundException extends Exception {
    public UserNotFoundException(String userId) {
        super("User not found: " + userId);
    }
}
 
// Unchecked exceptions for programming errors (preferred)
public class InvalidUserIdException extends RuntimeException {
    public InvalidUserIdException(String userId) {
        super("Invalid user ID: " + userId);
    }
}

Try-with-Resources

// Automatic resource management
try (BufferedReader reader = new BufferedReader(new FileReader("file.txt"))) {
    String line = reader.readLine();
    // reader automatically closed
} catch (IOException e) {
    throw new UncheckedIOException(e);
}
 
// Multiple resources
try (var inputStream = new FileInputStream("in.txt");
     var outputStream = new FileOutputStream("out.txt")) {
    // Both automatically closed in reverse order
}

Custom Exception Hierarchies

// Base exception for domain
public class DomainException extends RuntimeException {
    public DomainException(String message) {
        super(message);
    }
}
 
// Specific exceptions
public class UserNotFoundException extends DomainException {
    public UserNotFoundException(String userId) {
        super("User not found: " + userId);
    }
}
 
public class InvalidEmailException extends DomainException {
    public InvalidEmailException(String email) {
        super("Invalid email: " + email);
    }
}

Collections & Streams API

When to Use Which Collection

// List - ordered, allows duplicates
List<String> names = new ArrayList<>();
 
// Set - no duplicates
Set<String> uniqueNames = new HashSet<>();
 
// Map - key-value pairs
Map<String, User> userById = new HashMap<>();
 
// Prefer List.of(), Set.of(), Map.of() for immutable collections
List<String> immutableList = List.of("a", "b", "c");
Set<Integer> immutableSet = Set.of(1, 2, 3);
Map<String, Integer> immutableMap = Map.of("a", 1, "b", 2);

Stream Best Practices

// Filter and map
List<String> activeUserNames = users.stream()
    .filter(User::isActive)
    .map(User::name)
    .toList();  // Java 16+, or .collect(Collectors.toList())
 
// Find first
Optional<User> firstAdmin = users.stream()
    .filter(User::isAdmin)
    .findFirst();
 
// Reduce
int totalAge = users.stream()
    .mapToInt(User::age)
    .sum();
 
// Group by
Map<String, List<User>> usersByRole = users.stream()
    .collect(Collectors.groupingBy(User::role));
 
// Don't reuse streams (they're one-time use)
// BAD
var stream = users.stream();
stream.filter(...).toList();
stream.map(...).toList();  // IllegalStateException
 
// GOOD
users.stream().filter(...).toList();
users.stream().map(...).toList();

Optional & Null Handling

When to Use Optional

// GOOD - Optional for return values (absence is expected)
public Optional<User> findUserById(String id) {
    return repository.findById(id);
}
 
// BAD - don't use Optional for parameters or fields
public void processUser(Optional<User> user) { /* avoid */ }
private Optional<User> currentUser;  /* avoid */
 
// GOOD - use null for parameters if optional
public void processUser(User user) {
    if (user != null) {
        // process
    }
}

Optional Best Practices

// Chain operations
String userName = userService.findUserById(id)
    .map(User::name)
    .map(String::toUpperCase)
    .orElse("UNKNOWN");
 
// Throw exception if absent
User user = userService.findUserById(id)
    .orElseThrow(() -> new UserNotFoundException(id));
 
// Execute action if present
userService.findUserById(id)
    .ifPresent(user -> emailService.sendWelcome(user));
 
// Don't use Optional.get() without checking
// BAD
Optional<User> maybeUser = findUser(id);
User user = maybeUser.get();  // NoSuchElementException if empty
 
// GOOD
User user = maybeUser.orElseThrow();  // More explicit

Null Safety

// Use Objects.requireNonNull for validation
public User(String id, String name) {
    this.id = Objects.requireNonNull(id, "id cannot be null");
    this.name = Objects.requireNonNull(name, "name cannot be null");
}
 
// Prefer Objects utilities
String result = Objects.requireNonNullElse(value, "default");
boolean equals = Objects.equals(a, b);  // null-safe equals

Testing Fundamentals

JUnit 5 Basics

import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
 
class UserServiceTest {
    private UserService service;
    private UserRepository repository;
 
    @BeforeEach
    void setUp() {
        repository = new InMemoryUserRepository();
        service = new UserService(repository);
    }
 
    @Test
    void shouldReturnUserWhenIdExists() {
        // Given
        var user = new User("1", "John", "john@example.com");
        repository.save(user);
 
        // When
        var result = service.findById("1");
 
        // Then
        assertTrue(result.isPresent());
        assertEquals("John", result.get().name());
    }
 
    @Test
    void shouldThrowWhenIdIsNull() {
        assertThrows(IllegalArgumentException.class,
            () -> service.findById(null));
    }
 
    @ParameterizedTest
    @ValueSource(strings = {"", "  ", "\t"})
    void shouldThrowWhenIdIsBlank(String id) {
        assertThrows(IllegalArgumentException.class,
            () -> service.findById(id));
    }
}

Mockito for Mocking

import org.mockito.*;
import static org.mockito.Mockito.*;
 
class UserServiceTest {
    @Mock
    private UserRepository repository;
 
    @InjectMocks
    private UserService service;
 
    @BeforeEach
    void setUp() {
        MockitoAnnotations.openMocks(this);
    }
 
    @Test
    void shouldCallRepositoryWhenFindingUser() {
        // Given
        var user = new User("1", "John", "john@example.com");
        when(repository.findById("1")).thenReturn(Optional.of(user));
 
        // When
        var result = service.findById("1");
 
        // Then
        verify(repository).findById("1");
        assertTrue(result.isPresent());
        assertEquals("John", result.get().name());
    }
}

Test Naming

// Descriptive names - what scenario, what expected
@Test
void shouldReturnEmptyWhenUserNotFound() { }
 
@Test
void shouldThrowExceptionWhenEmailIsInvalid() { }
 
@Test
void shouldCalculateDiscountWhenUserIsVip() { }

Build Tool Awareness

Maven Dependencies (pom.xml)

<dependencies>
    <!-- Core dependencies -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
 
    <!-- Test dependencies -->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

Gradle Dependencies (build.gradle.kts)

dependencies {
    // Core dependencies
    implementation("org.springframework.boot:spring-boot-starter-web")
 
    // Test dependencies
    testImplementation("org.junit.jupiter:junit-jupiter")
    testImplementation("org.mockito:mockito-core")
}

Project Conventions

// Maven standard directory layout
src/main/java       - Production code
src/main/resources  - Configuration files
src/test/java       - Test code
src/test/resources  - Test configuration
 
// Gradle uses same layout
// Package structure matches directory structure
// com.example.myapp.service -> src/main/java/com/example/myapp/service/
ToolPurpose
maven or gradleBuild automation
junit-jupiterTesting framework (JUnit 5)
mockitoMocking framework
checkstyleCode style checking
spotbugsStatic bug detection
jacocoCode coverage
maven-enforcerDependency management rules
testcontainersIntegration testing with Docker

Production Best Practices

  1. Immutability - Prefer records and final fields, reduces bugs
  2. Dependency Injection - Constructor injection over field injection
  3. Fail Fast - Validate inputs immediately, throw exceptions early
  4. Explicit over Implicit - Clear code over clever code
  5. Resource Management - Always use try-with-resources for I/O
  6. Optional for Return Types - Signal absence without null
  7. Stream API - Use streams for collection operations, but don’t overuse
  8. Modern Java - Use records, sealed classes, pattern matching
  9. Minimal Checked Exceptions - Prefer unchecked for most cases
  10. Constructor Validation - Validate in constructor or compact constructor (records)
  11. Descriptive Names - Method names should explain intent
  12. Single Responsibility - Classes and methods should do one thing
  13. Package by Feature - Not by layer (service/repository/controller in same package)
  14. Logging - Use SLF4J, structured logging, appropriate levels
  15. Configuration - Externalize via application.properties, environment variables

Comments - Less is More

// BAD - redundant comment
// Get user from repository
User user = repository.findById(id);
 
// GOOD - self-explanatory code, no comment needed
User user = repository.findById(id);
 
// GOOD - comment explains WHY (not obvious)
// Rate limit: API allows max 100 requests per minute per client
rateLimiter.acquire();
 
// GOOD - comment documents constraint
/**
 * Processes payment. Amount must be positive.
 * @throws IllegalArgumentException if amount <= 0
 */
public void processPayment(BigDecimal amount) { }

References