| name | java-patterns |
| description | Java coding standards and idioms for Java 25+ — naming, immutability, Optional, streams, exceptions, generics, records, sealed classes. Applies to plain Java, Spring Boot, Quarkus, and Jakarta EE projects. |
Java Coding Standards
Standards for readable, maintainable Java (25+) code. Applies to any Java project — plain Java, Spring Boot, Quarkus, or Jakarta EE.
When to Activate
- Writing or reviewing Java code in any Java 25+ project
- Enforcing naming, immutability, or exception handling conventions
- Working with records, sealed classes, or pattern matching (Java 25+)
- Reviewing use of Optional, streams, or generics
- Structuring packages and project layout
- Onboarding a new Java project and establishing coding standards before the first PR
- Refactoring legacy Java code to remove raw types, mutation, or broad catch blocks
- Evaluating whether Java 21+ features (virtual threads, pattern matching, sequenced collections) can simplify existing code
Core Principles
- Prefer clarity over cleverness
- Immutable by default; minimize shared mutable state
- Fail fast with meaningful exceptions
- Consistent naming and package structure
Naming
public class MarketService {}
public record Money(BigDecimal amount, Currency currency) {}
private final MarketRepository marketRepository;
public Market findBySlug(String slug) {}
private static final int MAX_PAGE_SIZE = 100;
Immutability
public record MarketDto(Long id, String name, MarketStatus status) {}
public class Market {
private final Long id;
private final String name;
}
Optional Usage
Optional<Market> market = marketRepository.findBySlug(slug);
return market
.map(MarketResponse::from)
.orElseThrow(() -> new EntityNotFoundException("Market not found"));
Streams Best Practices
List<String> names = markets.stream()
.map(Market::name)
.filter(Objects::nonNull)
.toList();
Exceptions
- Use unchecked exceptions for domain errors; wrap technical exceptions with context
- Create domain-specific exceptions (e.g.,
MarketNotFoundException)
- Avoid broad
catch (Exception ex) unless rethrowing/logging centrally
throw new MarketNotFoundException(slug);
Generics and Type Safety
- Avoid raw types; declare generic parameters
- Prefer bounded generics for reusable utilities
public <T extends Identifiable> Map<Long, T> indexById(Collection<T> items) { ... }
Project Structure (Hexagonal / Ports & Adapters)
src/main/java/com/example/app/
domain/
model/ # Entities, value objects, aggregates (NO framework annotations)
port/
in/ # Input port interfaces (use case contracts)
out/ # Output port interfaces (repository, external service contracts)
event/ # Domain events
application/
usecase/ # Use case implementations (@Transactional here)
adapter/
in/
web/ # REST controllers + request/response DTOs
messaging/ # Message consumers
out/
persistence/ # JPA entities, Spring Data repos, mappers
client/ # External API clients
config/ # Spring @Configuration, bean wiring only
src/main/resources/
application.yml
src/test/java/... # mirrors main
Formatting and Style
- Use 2 or 4 spaces consistently (project standard)
- One public top-level type per file
- Keep methods short and focused; extract helpers
- Order members: constants, fields, constructors, public methods, protected, private
Code Smells to Avoid
- Long parameter lists → use DTO/builders
- Deep nesting → early returns
- Magic numbers → named constants
- Static mutable state → prefer dependency injection
- Silent catch blocks → log and act or rethrow
Logging
private static final Logger log = LoggerFactory.getLogger(MarketService.class);
log.info("fetch_market slug={}", slug);
log.error("failed_fetch_market slug={}", slug, ex);
Null Handling
- Accept
@Nullable only when unavoidable; otherwise use @NonNull
- Use Bean Validation (
@NotNull, @NotBlank) on inputs
Testing Expectations
- JUnit 5 + AssertJ for fluent assertions
- Mockito for mocking; avoid partial mocks where possible
- Favor deterministic tests; no hidden sleeps
Java 25 Features to Prefer
Use these Java 25 LTS features in new code:
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(() -> callExternalApi());
}
String describe(Object obj) {
return switch (obj) {
case Integer i -> "int: " + i;
case String s when s.isBlank() -> "blank string";
case String s -> "string: " + s;
default -> "other";
};
}
try {
return compute();
} catch (IOException _) {
return Optional.empty();
}
if (obj instanceof Point(int x, _)) {
return x;
}
return switch (shape) {
case Circle c -> c.area();
case Rectangle _ -> 0;
};
SequencedCollection<String> items = <>(List.of(, , ));
items.getFirst();
items.getLast();
Anti-Patterns
Calling Optional.get() Without Checking Presence
Wrong:
Optional<User> user = userRepository.findByEmail(email);
return user.get().getName();
Correct:
return userRepository.findByEmail(email)
.map(User::getName)
.orElseThrow(() -> new UserNotFoundException(email));
Why: Optional.get() on an empty Optional throws an uninformative exception; orElseThrow makes the failure explicit and provides a meaningful domain error.
Using Raw Types Instead of Generics
Wrong:
List users = new ArrayList();
users.add("not a user");
User u = (User) users.get(0);
Correct:
List<User> users = new ArrayList<>();
users.add(new User("Alice"));
User u = users.get(0);
Why: Raw types bypass the compiler's type-checker, moving errors that could be caught at compile time to unpredictable runtime failures.
Catching Exception Broadly and Swallowing It
Wrong:
try {
return orderRepository.save(order);
} catch (Exception e) {
return null;
}
Correct:
try {
return orderRepository.save(order);
} catch (DataAccessException e) {
throw new OrderPersistenceException("Failed to save order: " + order.id(), e);
}
Why: Catching Exception silently discards programming bugs and infrastructure failures; catching a specific exception and re-throwing with context preserves the stack trace and makes the failure observable.
Mutable Public Fields Instead of Records or Final Fields
Wrong:
public class Money {
public BigDecimal amount;
public String currency;
}
Money price = new Money();
price.amount = new BigDecimal("9.99");
price.amount = price.amount.negate();
Correct:
public record Money(BigDecimal amount, String currency) {
public Money {
Objects.requireNonNull(amount, "amount");
Objects.requireNonNull(currency, "currency");
}
public Money negate() {
return new Money(amount.negate(), currency);
}
}
Why: Mutable public fields break encapsulation and enable uncontrolled state changes; records provide an immutable value type with built-in equals, hashCode, and toString.
Using instanceof Without Pattern Matching in Java 21+
Wrong:
if (shape instanceof Circle) {
Circle c = (Circle) shape;
return c.area();
}
Correct:
if (shape instanceof Circle c) {
return c.area();
}
return switch (shape) {
case Circle c -> c.area();
case Rectangle r -> r.width() * r.height();
};
Why: The old pattern repeats the type check and cast redundantly; pattern matching in instanceof and switch (stable since Java 21) eliminates the cast and is exhaustive with sealed types.
Remember: Keep code intentional, typed, and observable. Optimize for maintainability over micro-optimizations unless proven necessary.