2026-02-20 01:11:30 -08:00
---
name : java-coding-standards
2026-08-12 06:58:14 +03:00
description : "Java coding standards for Spring Boot and Quarkus services: naming, immutability, Optional usage, streams, exceptions, generics, CDI, reactive patterns, and project layout. Automatically applies framework-specific conventions. Use when writing or reviewing Java in a Spring Boot or Quarkus service."
2026-06-11 21:12:21 +09:00
metadata :
origin : ECC
2026-02-20 01:11:30 -08:00
---
# Java Coding Standards
2026-05-12 15:30:26 +02:00
Standards for readable, maintainable Java (17+) code in Spring Boot and Quarkus services.
2026-02-20 01:11:30 -08:00
2026-05-12 15:30:26 +02:00
## When to Use
2026-02-20 01:11:30 -08:00
2026-05-12 15:30:26 +02:00
- Writing or reviewing Java code in Spring Boot or Quarkus projects
2026-02-20 01:11:30 -08:00
- Enforcing naming, immutability, or exception handling conventions
- Working with records, sealed classes, or pattern matching (Java 17+)
- Reviewing use of Optional, streams, or generics
- Structuring packages and project layout
2026-05-12 15:30:26 +02:00
- **[QUARKUS]**: Working with CDI scopes, Panache entities, or reactive pipelines
## How It Works
### Framework Detection
Before applying standards, determine the framework from the build file:
- Build file contains `quarkus` → apply ** [QUARKUS]** conventions
- Build file contains `spring-boot` → apply ** [SPRING]** conventions
- Neither detected → apply shared conventions only
2026-02-20 01:11:30 -08:00
## Core Principles
- Prefer clarity over cleverness
- Immutable by default; minimize shared mutable state
- Fail fast with meaningful exceptions
- Consistent naming and package structure
2026-05-12 15:30:26 +02:00
- **[QUARKUS]**: Favor build-time over runtime processing; avoid runtime reflection where possible
## Examples
The sections below show concrete Spring Boot, Quarkus, and shared Java examples
for naming, immutability, dependency injection, reactive code, exceptions,
project layout, logging, configuration, and tests.
2026-02-20 01:11:30 -08:00
## Naming
```java
2026-03-29 08:59:06 -04:00
// PASS: Classes/Records: PascalCase
2026-02-20 01:11:30 -08:00
public class MarketService {}
public record Money ( BigDecimal amount , Currency currency ) {}
2026-03-29 08:59:06 -04:00
// PASS: Methods/fields: camelCase
2026-02-20 01:11:30 -08:00
private final MarketRepository marketRepository ;
public Market findBySlug ( String slug ) {}
2026-03-29 08:59:06 -04:00
// PASS: Constants: UPPER_SNAKE_CASE
2026-02-20 01:11:30 -08:00
private static final int MAX_PAGE_SIZE = 100 ;
2026-05-12 15:30:26 +02:00
// PASS: [QUARKUS] JAX-RS resources named as *Resource, not *Controller
public class MarketResource {}
// PASS: [SPRING] REST controllers named as *Controller
public class MarketController {}
2026-02-20 01:11:30 -08:00
```
## Immutability
```java
2026-03-29 08:59:06 -04:00
// PASS: Favor records and final fields
2026-02-20 01:11:30 -08:00
public record MarketDto ( Long id , String name , MarketStatus status ) {}
public class Market {
private final Long id ;
private final String name ;
// getters only, no setters
}
2026-05-12 15:30:26 +02:00
// PASS: [QUARKUS] Panache active-record entities use public fields (Quarkus convention)
@Entity
public class Market extends PanacheEntity {
public String name ;
public MarketStatus status ;
// Panache generates accessors at build time; public fields are idiomatic here
}
// PASS: [QUARKUS] Panache MongoDB entities
@MongoEntity ( collection = "markets" )
public class Market extends PanacheMongoEntity {
public String name ;
public MarketStatus status ;
}
2026-02-20 01:11:30 -08:00
```
## Optional Usage
```java
2026-03-29 08:59:06 -04:00
// PASS: Return Optional from find* methods
2026-05-12 15:30:26 +02:00
// [SPRING]
2026-02-20 01:11:30 -08:00
Optional < Market > market = marketRepository . findBySlug ( slug );
2026-05-12 15:30:26 +02:00
// [QUARKUS] Panache
Optional < Market > market = Market . find ( "slug" , slug ). firstResultOptional ();
2026-03-29 08:59:06 -04:00
// PASS: Map/flatMap instead of get()
2026-02-20 01:11:30 -08:00
return market
. map ( MarketResponse :: from )
. orElseThrow (() -> new EntityNotFoundException ( "Market not found" ));
```
## Streams Best Practices
```java
2026-03-29 08:59:06 -04:00
// PASS: Use streams for transformations, keep pipelines short
2026-02-20 01:11:30 -08:00
List < String > names = markets . stream ()
. map ( Market :: name )
. filter ( Objects :: nonNull )
. toList ();
2026-03-29 08:59:06 -04:00
// FAIL: Avoid complex nested streams; prefer loops for clarity
2026-02-20 01:11:30 -08:00
```
2026-05-12 15:30:26 +02:00
## Dependency Injection
```java
// PASS: [SPRING] Constructor injection (preferred over @Autowired on fields)
@Service
public class MarketService {
private final MarketRepository marketRepository ;
public MarketService ( MarketRepository marketRepository ) {
this . marketRepository = marketRepository ;
}
}
// PASS: [QUARKUS] Constructor injection
@ApplicationScoped
public class MarketService {
private final MarketRepository marketRepository ;
@Inject
public MarketService ( MarketRepository marketRepository ) {
this . marketRepository = marketRepository ;
}
}
// PASS: [QUARKUS] Package-private field injection (acceptable in Quarkus — avoids proxy issues)
@ApplicationScoped
public class MarketService {
@Inject
MarketRepository marketRepository ;
}
// FAIL: [SPRING] Field injection with @Autowired
@Autowired
private MarketRepository marketRepository ; // use constructor injection
// FAIL: [QUARKUS] @Singleton when interception or lazy init is needed
@Singleton // non-proxyable — use @ApplicationScoped instead
public class MarketService {}
```
## Reactive Patterns [QUARKUS]
```java
// PASS: Return Uni/Multi from reactive endpoints
@GET
@Path ( "/{slug}" )
public Uni < Market > findBySlug ( @PathParam ( "slug" ) String slug ) {
return Market . find ( "slug" , slug )
. < Market > firstResult ()
. onItem (). ifNull (). failWith (() -> new MarketNotFoundException ( slug ));
}
// PASS: Non-blocking pipeline composition
public Uni < OrderConfirmation > placeOrder ( OrderRequest req ) {
return validateOrder ( req )
. chain ( valid -> persistOrder ( valid ))
. chain ( order -> notifyFulfillment ( order ));
}
// FAIL: Blocking call inside a Uni/Multi pipeline
public Uni < Market > find ( String slug ) {
Market m = Market . find ( "slug" , slug ). firstResult (); // BLOCKING — breaks event loop
return Uni . createFrom (). item ( m );
}
// FAIL: Subscribing more than once to a shared Uni
Uni < Market > shared = fetchMarket ( slug );
shared . subscribe (). with ( m -> log ( m ));
shared . subscribe (). with ( m -> cache ( m )); // double subscribe — use Uni.memoize()
```
2026-02-20 01:11:30 -08:00
## 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
```java
throw new MarketNotFoundException ( slug );
```
2026-05-12 15:30:26 +02:00
### Centralised Exception Handling
```java
// [SPRING]
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler ( MarketNotFoundException . class )
public ResponseEntity < ErrorResponse > handle ( MarketNotFoundException ex ) {
return ResponseEntity . status ( 404 ). body ( ErrorResponse . from ( ex ));
}
}
// [QUARKUS] Option A: ExceptionMapper
@Provider
public class MarketNotFoundMapper implements ExceptionMapper < MarketNotFoundException > {
@Override
public Response toResponse ( MarketNotFoundException ex ) {
return Response . status ( 404 ). entity ( ErrorResponse . from ( ex )). build ();
}
}
// [QUARKUS] Option B: @ServerExceptionMapper (RESTEasy Reactive)
@ServerExceptionMapper
public RestResponse < ErrorResponse > handle ( MarketNotFoundException ex ) {
return RestResponse . status ( Status . NOT_FOUND , ErrorResponse . from ( ex ));
}
```
2026-02-20 01:11:30 -08:00
## Generics and Type Safety
- Avoid raw types; declare generic parameters
- Prefer bounded generics for reusable utilities
```java
public < T extends Identifiable > Map < Long , T > indexById ( Collection < T > items ) { ... }
```
2026-05-12 15:30:26 +02:00
## Project Structure
### [SPRING] Maven/Gradle
2026-02-20 01:11:30 -08:00
```
src/main/java/com/example/app/
config/
controller/
service/
repository/
domain/
dto/
util/
src/main/resources/
application.yml
src/test/java/... (mirrors main)
```
2026-05-12 15:30:26 +02:00
### [QUARKUS] Maven/Gradle
```
src/main/java/com/example/app/
config/ # @ConfigMapping, @ConfigProperty beans, Producers
resource/ # JAX-RS resources (not "controller")
service/
repository/ # PanacheRepository implementations (if not using active record)
domain/ # JPA/Panache entities, MongoDB entities
dto/
util/
mapper/ # MapStruct mappers (if used)
src/main/resources/
application.properties # Quarkus convention (YAML supported with quarkus-config-yaml)
import.sql # Hibernate auto-import for dev/test
src/test/java/... (mirrors main)
```
2026-02-20 01:11:30 -08:00
## 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
2026-05-12 15:30:26 +02:00
- **[QUARKUS]**: `@Singleton` where `@ApplicationScoped` is intended — breaks proxying and interception
- **[QUARKUS]**: Mixing `quarkus-resteasy-reactive` and `quarkus-resteasy` (classic) — pick one stack
- **[QUARKUS]**: Panache active-record + repository pattern in the same bounded context — pick one
2026-02-20 01:11:30 -08:00
## Logging
```java
2026-05-12 15:30:26 +02:00
// [SPRING] SLF4J
2026-02-20 01:11:30 -08:00
private static final Logger log = LoggerFactory . getLogger ( MarketService . class );
log . info ( "fetch_market slug={}" , slug );
log . error ( "failed_fetch_market slug={}" , slug , ex );
2026-05-12 15:30:26 +02:00
// [QUARKUS] JBoss Logging (default, zero-cost at build time)
private static final Logger log = Logger . getLogger ( MarketService . class );
log . infof ( "fetch_market slug=%s" , slug );
log . errorf ( ex , "failed_fetch_market slug=%s" , slug );
// [QUARKUS] Alternative: simplified logging with @Inject
@Inject
Logger log ; // CDI-injected, scoped to declaring class
2026-02-20 01:11:30 -08:00
```
## Null Handling
- Accept `@Nullable` only when unavoidable; otherwise use `@NonNull`
- Use Bean Validation (`@NotNull` , `@NotBlank` ) on inputs
2026-05-12 15:30:26 +02:00
- **[QUARKUS]**: Apply `@Valid` on `@BeanParam` , `@RestForm` , and request body parameters
## Configuration
```java
// [SPRING] @ConfigurationProperties
@ConfigurationProperties ( prefix = "market" )
public record MarketProperties ( int maxPageSize , Duration cacheTtl ) {}
// [QUARKUS] @ConfigMapping (type-safe, build-time validated)
@ConfigMapping ( prefix = "market" )
public interface MarketConfig {
int maxPageSize ();
Duration cacheTtl ();
}
// [QUARKUS] Simple values with @ConfigProperty
@ConfigProperty ( name = "market.max-page-size" , defaultValue = "100" )
int maxPageSize ;
```
2026-02-20 01:11:30 -08:00
## Testing Expectations
2026-05-12 15:30:26 +02:00
### Shared
2026-02-20 01:11:30 -08:00
- JUnit 5 + AssertJ for fluent assertions
- Mockito for mocking; avoid partial mocks where possible
- Favor deterministic tests; no hidden sleeps
2026-05-12 15:30:26 +02:00
### [SPRING]
- `@WebMvcTest` for controller slices, `@DataJpaTest` for repository slices
- `@SpringBootTest` reserved for full integration tests
- `@MockBean` for replacing beans in Spring context
### [QUARKUS]
- Plain JUnit 5 + Mockito for unit tests (no `@QuarkusTest` )
- `@QuarkusTest` reserved for CDI integration tests
- `@InjectMock` for replacing CDI beans in integration tests
- Dev Services for database/Kafka/Redis — avoid manual Testcontainers setup when Dev Services suffice
- `@QuarkusTestResource` for custom external service lifecycle
```java
// [SPRING] Controller test
@WebMvcTest ( MarketController . class )
class MarketControllerTest {
@Autowired MockMvc mockMvc ;
@MockBean MarketService marketService ;
}
// [QUARKUS] Integration test
@QuarkusTest
class MarketResourceTest {
@InjectMock
MarketService marketService ;
@Test
void should_return_404_when_market_not_found () {
given (). when (). get ( "/markets/unknown" ). then (). statusCode ( 404 );
}
}
// [QUARKUS] Unit test (no CDI, no @QuarkusTest)
@ExtendWith ( MockitoExtension . class )
class MarketServiceTest {
@Mock MarketRepository marketRepository ;
@InjectMocks MarketService marketService ;
}
```
2026-02-20 01:11:30 -08:00
**Remember** : Keep code intentional, typed, and observable. Optimize for maintainability over micro-optimizations unless proven necessary.