Install any skill in seconds. Free to start, no credit card required.
Get Started Free →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.
.claude/skills/java-coding-standards/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-15 | ✗→✓ | ▲ Improved | — | — |
| case-05 | ✗→✓ | ▲ Improved | — | — |
| case-18 | ✗→✓ | ▲ Improved | — | — |
| case-07 | ✗→✓ | ▲ Improved | — | — |
| case-03 | ✗→✓ | ▲ Improved | — | — |
Standards for readable, maintainable Java (17+) code in Spring Boot and Quarkus services.
Before applying standards, determine the framework from the build file:
quarkus → apply QUARKUS] conventionsspring-boot → apply SPRING] conventionsThe 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.
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: 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: 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: 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: [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: 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()
MarketNotFoundException)catch (Exception ex) unless rethrowing/logging centrallyjavathrow new MarketNotFoundException(slug);
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)); }
javapublic <T extends Identifiable> Map<Long, T> indexById(Collection<T> items) { ... }
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/ # @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)@Singleton where @ApplicationScoped is intended — breaks proxying and interceptionquarkus-resteasy-reactive and quarkus-resteasy (classic) — pick one stackjava// [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
@Nullable only when unavoidable; otherwise use @NonNull@NotNull, @NotBlank) on inputs@Valid on @BeanParam, @RestForm, and request body parametersjava// [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;
@WebMvcTest for controller slices, @DataJpaTest for repository slices@SpringBootTest reserved for full integration tests@MockBean for replacing beans in Spring context@QuarkusTest)@QuarkusTest reserved for CDI integration tests@InjectMock for replacing CDI beans in integration tests@QuarkusTestResource for custom external service lifecyclejava// [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.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-24 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-25 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-23 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-15 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-03 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 25 cases were attempted. The headline lift of +20 percentage points is the difference between those two pass rates over the 25 comparable cases.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.