java-coding-standards

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Java Coding Standards

Java编码规范

Standards for readable, maintainable Java (17+) code in Spring Boot and Quarkus services.
适用于Spring Boot和Quarkus服务的可读、可维护Java(17+)代码规范。

When to Use

适用场景

  • Writing or reviewing Java code in Spring Boot or Quarkus projects
  • 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
  • [QUARKUS]: Working with CDI scopes, Panache entities, or reactive pipelines
  • 在Spring Boot或Quarkus项目中编写或评审Java代码
  • 强制执行命名、不可变性或异常处理约定
  • 使用records、密封类或模式匹配(Java 17+)
  • 评审Optional、流或泛型的使用
  • 规划包结构和项目布局
  • [QUARKUS]:使用CDI作用域、Panache实体或响应式管道

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
应用规范前,从构建文件判断使用的框架:
  • 构建文件包含
    quarkus
    → 应用**[QUARKUS]**约定
  • 构建文件包含
    spring-boot
    → 应用**[SPRING]**约定
  • 未检测到上述框架 → 仅应用通用约定

Core Principles

核心原则

  • Prefer clarity over cleverness
  • Immutable by default; minimize shared mutable state
  • Fail fast with meaningful exceptions
  • Consistent naming and package structure
  • [QUARKUS]: Favor build-time over runtime processing; avoid runtime reflection where possible
  • 优先清晰性而非技巧性
  • 默认不可变;最小化共享可变状态
  • 快速失败并抛出有意义的异常
  • 一致的命名和包结构
  • [QUARKUS]:优先构建时处理而非运行时处理;尽可能避免运行时反射

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.
以下章节展示了Spring Boot、Quarkus和通用Java在命名、不可变性、依赖注入、响应式代码、异常、项目布局、日志、配置和测试方面的具体示例。

Naming

命名

java
// PASS: Classes/Records: PascalCase
public class MarketService {}
public record Money(BigDecimal amount, Currency currency) {}

// PASS: Methods/fields: camelCase
private final MarketRepository marketRepository;
public Market findBySlug(String slug) {}

// PASS: Constants: UPPER_SNAKE_CASE
private static final int MAX_PAGE_SIZE = 100;

// PASS: [QUARKUS] JAX-RS resources named as *Resource, not *Controller
public class MarketResource {}

// PASS: [SPRING] REST controllers named as *Controller
public class MarketController {}
java
// PASS: Classes/Records: PascalCase
public class MarketService {}
public record Money(BigDecimal amount, Currency currency) {}

// PASS: Methods/fields: camelCase
private final MarketRepository marketRepository;
public Market findBySlug(String slug) {}

// PASS: Constants: UPPER_SNAKE_CASE
private static final int MAX_PAGE_SIZE = 100;

// PASS: [QUARKUS] JAX-RS resources named as *Resource, not *Controller
public class MarketResource {}

// PASS: [SPRING] REST controllers named as *Controller
public class MarketController {}

Immutability

不可变性

java
// PASS: Favor records and final fields
public record MarketDto(Long id, String name, MarketStatus status) {}

public class Market {
  private final Long id;
  private final String name;
  // getters only, no setters
}

// 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;
}
java
// PASS: Favor records and final fields
public record MarketDto(Long id, String name, MarketStatus status) {}

public class Market {
  private final Long id;
  private final String name;
  // getters only, no setters
}

// 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;
}

Optional Usage

Optional使用

java
// PASS: Return Optional from find* methods
// [SPRING]
Optional<Market> market = marketRepository.findBySlug(slug);

// [QUARKUS] Panache
Optional<Market> market = Market.find("slug", slug).firstResultOptional();

// PASS: Map/flatMap instead of get()
return market
    .map(MarketResponse::from)
    .orElseThrow(() -> new EntityNotFoundException("Market not found"));
java
// PASS: Return Optional from find* methods
// [SPRING]
Optional<Market> market = marketRepository.findBySlug(slug);

// [QUARKUS] Panache
Optional<Market> market = Market.find("slug", slug).firstResultOptional();

// PASS: Map/flatMap instead of get()
return market
    .map(MarketResponse::from)
    .orElseThrow(() -> new EntityNotFoundException("Market not found"));

Streams Best Practices

流最佳实践

java
// PASS: Use streams for transformations, keep pipelines short
List<String> names = markets.stream()
    .map(Market::name)
    .filter(Objects::nonNull)
    .toList();

// FAIL: Avoid complex nested streams; prefer loops for clarity
java
// PASS: Use streams for transformations, keep pipelines short
List<String> names = markets.stream()
    .map(Market::name)
    .filter(Objects::nonNull)
    .toList();

// FAIL: Avoid complex nested streams; prefer loops for clarity

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 {}
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]

响应式模式 [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()
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()

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);
  • 针对领域错误使用非检查型异常;为技术异常添加上下文包装
  • 创建领域特定异常(例如
    MarketNotFoundException
  • 除非集中处理日志或重抛,否则避免宽泛的
    catch (Exception ex)
java
throw new MarketNotFoundException(slug);

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));
}
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));
}

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) { ... }
  • 避免原始类型;声明泛型参数
  • 针对可复用工具优先使用有界泛型
java
public <T extends Identifiable> Map<Long, T> indexById(Collection<T> items) { ... }

Project Structure

项目结构

[SPRING] Maven/Gradle

[SPRING] Maven/Gradle

src/main/java/com/example/app/
  config/
  controller/
  service/
  repository/
  domain/
  dto/
  util/
src/main/resources/
  application.yml
src/test/java/... (mirrors main)
src/main/java/com/example/app/
  config/
  controller/
  service/
  repository/
  domain/
  dto/
  util/
src/main/resources/
  application.yml
src/test/java/... (mirrors main)

[QUARKUS] Maven/Gradle

[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)
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)

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
  • 统一使用2或4个空格(遵循项目标准)
  • 每个文件仅包含一个公共顶层类型
  • 保持方法简短且聚焦;提取辅助方法
  • 成员顺序:常量、字段、构造函数、公共方法、受保护方法、私有方法

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
  • [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
  • 长参数列表 → 使用DTO/构建器
  • 深层嵌套 → 提前返回
  • 魔法数字 → 使用命名常量
  • 静态可变状态 → 优先使用依赖注入
  • 静默捕获块 → 记录日志并处理或重抛
  • [QUARKUS]:在需要拦截或延迟初始化时使用
    @Singleton
    — 会破坏代理和拦截功能
  • [QUARKUS]:混合使用
    quarkus-resteasy-reactive
    quarkus-resteasy
    (经典版)— 选择其中一个栈
  • [QUARKUS]:在同一限界上下文中同时使用Panache活动记录模式和仓库模式 — 选择其中一种

Logging

日志

java
// [SPRING] SLF4J
private static final Logger log = LoggerFactory.getLogger(MarketService.class);
log.info("fetch_market slug={}", slug);
log.error("failed_fetch_market slug={}", slug, ex);

// [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
java
// [SPRING] SLF4J
private static final Logger log = LoggerFactory.getLogger(MarketService.class);
log.info("fetch_market slug={}", slug);
log.error("failed_fetch_market slug={}", slug, ex);

// [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

Null Handling

Null处理

  • Accept
    @Nullable
    only when unavoidable; otherwise use
    @NonNull
  • Use Bean Validation (
    @NotNull
    ,
    @NotBlank
    ) on inputs
  • [QUARKUS]: Apply
    @Valid
    on
    @BeanParam
    ,
    @RestForm
    , and request body parameters
  • 仅在不可避免时接受
    @Nullable
    ;否则使用
    @NonNull
  • 在输入上使用Bean Validation(
    @NotNull
    @NotBlank
  • [QUARKUS]:在
    @BeanParam
    @RestForm
    和请求体参数上应用
    @Valid

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;
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;

Testing Expectations

测试预期

Shared

通用规则

  • JUnit 5 + AssertJ for fluent assertions
  • Mockito for mocking; avoid partial mocks where possible
  • Favor deterministic tests; no hidden sleeps
  • 使用JUnit 5 + AssertJ进行流畅断言
  • 使用Mockito进行模拟;尽可能避免部分模拟
  • 优先确定性测试;避免隐藏的休眠操作

[SPRING]

[SPRING]

  • @WebMvcTest
    for controller slices,
    @DataJpaTest
    for repository slices
  • @SpringBootTest
    reserved for full integration tests
  • @MockBean
    for replacing beans in Spring context
  • 使用
    @WebMvcTest
    进行控制器切片测试,
    @DataJpaTest
    进行仓库切片测试
  • @SpringBootTest
    仅用于完整集成测试
  • 使用
    @MockBean
    替换Spring上下文里的Bean

[QUARKUS]

[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;
}
Remember: Keep code intentional, typed, and observable. Optimize for maintainability over micro-optimizations unless proven necessary.
  • 使用纯JUnit 5 + Mockito进行单元测试(无需
    @QuarkusTest
  • @QuarkusTest
    仅用于CDI集成测试
  • 使用
    @InjectMock
    替换集成测试中的CDI Bean
  • 使用Dev Services管理数据库/Kafka/Redis — 当Dev Services足够时,避免手动设置Testcontainers
  • 使用
    @QuarkusTestResource
    管理自定义外部服务生命周期
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;
}
谨记:保持代码意图明确、类型安全且可观测。除非已证实必要,否则优先优化可维护性而非微优化。