| name | java-coding-standards |
| description | Coding standards for modern Java (17+) services on the two dominant JVM backend stacks — Spring Boot and Quarkus. Covers naming, immutability, Optional discipline, streams, exceptions, generics, dependency injection, reactive pipelines, configuration, logging, project layout, and a folded Persistence (JPA/Hibernate) section for entity design, N+1 prevention, transactions, pagination, indexing, and connection pooling. Use when writing or reviewing Java in a Spring Boot or Quarkus codebase, or when designing the data-access layer.
|
| version | 1.0.0 |
| metadata | {"risk_class":"low","activation_triggers":["editing *.java sources in a Spring Boot or Quarkus service","build file (pom.xml / build.gradle) declares spring-boot or quarkus","reviewing Java naming, immutability, Optional, streams, generics, or exception handling","authoring records, sealed types, or pattern matching (Java 17+)","designing JPA/Hibernate entities, relationships, repositories, or queries","tuning transactions, pagination, indexing, or HikariCP pooling"]} |
| inspired_by | [{"source":"affaan-m/ecc/skills/java-coding-standards@81af40761939056ab3dc54732fd4f562a27309d0","license":"MIT","relationship":"structural_inspiration","authored_by":"ceo-orchestration framework","authored_at":"2026-07-07T00:00:00.000Z"},{"source":"affaan-m/ecc/skills/jpa-patterns@81af40761939056ab3dc54732fd4f562a27309d0","license":"MIT","relationship":"structural_inspiration","authored_by":"ceo-orchestration framework","authored_at":"2026-07-07T00:00:00.000Z"}] |
| tier | domain:jvm |
| scope_tags | ["java","spring-boot","quarkus","jpa","hibernate","coding-standards"] |
| source | affaan-m/ecc@81af4076 skills/java-coding-standards/ |
| license | MIT |
Java Coding Standards
Conventions for readable, maintainable Java 17+ across the two mainstream JVM
service stacks. Where a rule differs between frameworks it is tagged
[SPRING] (Spring Boot) or [QUARKUS] (Quarkus/CDI); untagged rules are
shared. The goal is intentional, typed, observable code — optimise for
maintainability first, and micro-optimise only when a profiler says so.
When to Activate
- Writing or reviewing Java in a Spring Boot or Quarkus project
- Enforcing naming, immutability, or exception-handling conventions
- Working with records, sealed classes, or pattern matching (Java 17+)
- Reviewing Optional, stream, or generics usage
- Laying out packages / project structure
- Designing the persistence layer — entities, relationships, queries, transactions (see Persistence (JPA) below)
- [QUARKUS] working with CDI scopes, Panache entities, or reactive (
Uni/Multi) pipelines
Detect the framework first
Read the build file before applying any tagged rule:
- Declares
quarkus → apply [QUARKUS] conventions
- Declares
spring-boot → apply [SPRING] conventions
- Neither → apply only the shared rules
Guiding principles
- Clarity beats cleverness. If a reviewer has to pause, simplify.
- Immutable by default; keep shared mutable state to a minimum.
- Fail fast with a meaningful, domain-specific exception.
- One naming and package convention per codebase — no drift.
- [QUARKUS] prefer build-time work over runtime; avoid runtime reflection.
Naming
public class CatalogService {}
public record Money(BigDecimal amount, Currency currency) {}
private final ProductRepository productRepository;
public Product findBySlug(String slug) { ... }
private static final int MAX_PAGE_SIZE = 100;
public class ProductController {}
public class ProductResource {}
Immutability
Favour records and final fields; expose accessors, not setters.
public record ProductDto(Long id, String name, ProductStatus status) {}
public final class Product {
private final Long id;
private final String name;
}
@Entity
public class Product extends PanacheEntity {
public String name;
public ProductStatus status;
}
Optional discipline
Return Optional from find* methods; compose with map/flatMap/orElseThrow
instead of calling get().
Optional<Product> product = productRepository.findBySlug(slug);
Optional<Product> product = Product.find("slug", slug).firstResultOptional();
return product
.map(ProductResponse::from)
.orElseThrow(() -> new ProductNotFoundException(slug));
Do not use Optional for fields or method parameters — only as a return type
that signals "may be absent".
Streams
Use streams for transformation, keep the pipeline short and linear, and reach
for .toList() (Java 16+). If a stream grows nested or hard to read, a plain
loop is the better choice.
List<String> names = products.stream()
.map(Product::name)
.filter(Objects::nonNull)
.toList();
Dependency injection
Constructor injection everywhere — it makes dependencies explicit, supports
final fields, and keeps objects testable without a container.
@Service
public class CatalogService {
private final ProductRepository productRepository;
public CatalogService(ProductRepository productRepository) {
this.productRepository = productRepository;
}
}
@ApplicationScoped
public class CatalogService {
private final ProductRepository productRepository;
@Inject
public CatalogService(ProductRepository productRepository) {
this.productRepository = productRepository;
}
}
Anti-patterns:
- [SPRING]
@Autowired on a private field — untestable without reflection; use the constructor.
- [QUARKUS]
@Singleton where interception or lazy init is needed — it is non-proxyable; use @ApplicationScoped. (Package-private @Inject field injection is acceptable in Quarkus and sidesteps proxy edge cases.)
Reactive pipelines [QUARKUS]
Return Uni/Multi from reactive endpoints and compose non-blocking stages.
The two cardinal sins are blocking inside the pipeline and subscribing twice to
a shared Uni.
@GET
@Path("/{slug}")
public Uni<Product> findBySlug(@PathParam("slug") String slug) {
return Product.find("slug", slug)
.<Product>firstResult()
.onItem().ifNull().failWith(() -> new ProductNotFoundException(slug));
}
public Uni<Shipment> fulfill(FulfillmentRequest req) {
return reserveInventory(req)
.chain(reserved -> createShipment(reserved))
.chain(shipment -> notifyWarehouse(shipment));
}
public Uni<Product> find(String slug) {
Product p = Product.find("slug", slug).firstResult();
return Uni.createFrom().item(p);
}
Exceptions
- Use unchecked exceptions for domain errors; wrap technical exceptions with context.
- Create specific types (
ProductNotFoundException) — not bare RuntimeException.
- Avoid broad
catch (Exception ex) unless you rethrow or log at a single central point.
Handle errors centrally rather than per endpoint:
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ProductNotFoundException.class)
public ResponseEntity<ErrorResponse> handle(ProductNotFoundException ex) {
return ResponseEntity.status(404).body(ErrorResponse.from(ex));
}
}
@Provider
public class ProductNotFoundMapper implements ExceptionMapper<ProductNotFoundException> {
@Override
public Response toResponse(ProductNotFoundException ex) {
return Response.status(404).entity(ErrorResponse.from(ex)).build();
}
}
@ServerExceptionMapper
public RestResponse<ErrorResponse> handle(ProductNotFoundException ex) {
return RestResponse.status(Status.NOT_FOUND, ErrorResponse.from(ex));
}
Generics and type safety
No raw types; declare type parameters, and prefer bounded generics for reusable
utilities.
public <T extends Identifiable> Map<Long, T> indexById(Collection<T> items) { ... }
Null handling
- Prefer
@NonNull; reserve @Nullable for the unavoidable, and document why.
- Validate inputs with Bean Validation (
@NotNull, @NotBlank, @Size).
- [QUARKUS] apply
@Valid on @BeanParam, @RestForm, and request-body params.
Configuration
@ConfigurationProperties(prefix = "catalog")
public record CatalogProperties(int maxPageSize, Duration cacheTtl) {}
@ConfigMapping(prefix = "catalog")
public interface CatalogConfig {
int maxPageSize();
Duration cacheTtl();
}
@ConfigProperty(name = "catalog.max-page-size", defaultValue = "100")
int maxPageSize;
Logging
Use structured, key=value message templates — never string concatenation — and
always pass the throwable as the last argument.
private static final Logger log = LoggerFactory.getLogger(CatalogService.class);
log.info("fetch_product slug={}", slug);
log.error("fetch_product_failed slug={}", slug, ex);
private static final Logger log = Logger.getLogger(CatalogService.class);
log.infof("fetch_product slug=%s", slug);
log.errorf(ex, "fetch_product_failed slug=%s", slug);
Project structure
// [SPRING]
src/main/java/com/example/app/
config/ controller/ service/ repository/ domain/ dto/ util/
src/main/resources/application.yml
src/test/java/... # mirrors main
// [QUARKUS]
src/main/java/com/example/app/
config/ # @ConfigMapping, @ConfigProperty, producers
resource/ # JAX-RS resources (not "controller")
service/ repository/ domain/ dto/ mapper/
src/main/resources/
application.properties # YAML supported via quarkus-config-yaml
src/test/java/... # mirrors main
Formatting and member order
- One public top-level type per file; pick 2- or 4-space indent and hold it.
- Member order: constants, fields, constructors, then public → protected → private methods.
- Keep methods short and single-purpose; extract helpers over deep nesting.
Code smells to avoid
- Long parameter lists → pass a DTO or use a builder
- Deep nesting → early returns
- Magic numbers → named constants
- Static mutable state → inject a dependency instead
- Silent
catch blocks → log and act, or rethrow
- [QUARKUS]
@Singleton where @ApplicationScoped is meant — breaks proxying/interception
- [QUARKUS] mixing the reactive and classic RESTEasy stacks — pick one
- [QUARKUS] Panache active-record and the repository pattern in one bounded context — pick one
Persistence (JPA)
Applies primarily to [SPRING] + Spring Data JPA / Hibernate; the entity-design
and query-shape rules carry over to [QUARKUS] Panache as well. Keep entities
lean, queries intentional, and transactions short.
Entity design + auditing
@Entity
@Table(name = "products", indexes = {
@Index(name = "idx_products_slug", columnList = "slug", unique = true)
})
@EntityListeners(AuditingEntityListener.class)
public class ProductEntity {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 200)
private String name;
@Column(nullable = false, unique = true, length = 120)
private String slug;
@Enumerated(EnumType.STRING)
private ProductStatus status = ProductStatus.ACTIVE;
@CreatedDate private Instant createdAt;
@LastModifiedDate private Instant updatedAt;
}
@Configuration
@EnableJpaAuditing
class JpaConfig {}
Always map enums with EnumType.STRING — ORDINAL silently corrupts data when
someone reorders the enum.
Relationships and N+1 prevention
The N+1 query is the defining JPA performance bug. Default every association to
LAZY, never put EAGER on a collection, and pull what a read path needs with
an explicit JOIN FETCH or a DTO projection.
@OneToMany(mappedBy = "product", cascade = CascadeType.ALL, orphanRemoval = true)
private List<VariantEntity> variants = new ArrayList<>();
@Query("select p from ProductEntity p left join fetch p.variants where p.id = :id")
Optional<ProductEntity> findWithVariants(@Param("id") Long id);
Repositories and projections
public interface ProductRepository extends JpaRepository<ProductEntity, Long> {
Optional<ProductEntity> findBySlug(String slug);
@Query("select p from ProductEntity p where p.status = :status")
Page<ProductEntity> findByStatus(@Param("status") ProductStatus status, Pageable pageable);
}
public interface ProductSummary {
Long getId();
String getName();
ProductStatus getStatus();
}
Page<ProductSummary> findAllBy(Pageable pageable);
Transactions
- Annotate service methods (not repositories) with
@Transactional.
- Use
@Transactional(readOnly = true) on read paths — it lets Hibernate skip dirty-checking.
- Keep transactions short; pick propagation deliberately; never do I/O or remote calls inside one.
@Transactional
public Product updateStatus(Long id, ProductStatus status) {
ProductEntity entity = repo.findById(id)
.orElseThrow(() -> new ProductNotFoundException(id));
entity.setStatus(status);
return Product.from(entity);
}
Pagination
PageRequest page = PageRequest.of(pageNumber, pageSize, Sort.by("createdAt").descending());
Page<ProductEntity> results = repo.findByStatus(ProductStatus.ACTIVE, page);
For deep pagination, prefer keyset ("seek") pagination — add id > :lastId to
the JPQL with matching ordering — over large OFFSET values, which the database
must still scan and discard.
Indexing and write batching
- Index the columns you actually filter and join on (
status, slug, foreign keys).
- Use composite indexes that match the query's leading columns (
status, created_at).
- Never
select *; project only the columns a path needs.
- Batch inserts/updates with
saveAll plus hibernate.jdbc.batch_size.
Connection pooling (HikariCP)
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.validation-timeout=5000
Size the pool to the workload, not to a round number — an oversized pool starves
the database of connections and hides slow queries behind queueing.
Second-level cache
The first-level cache lives on the EntityManager; do not hold entities across
transactions expecting them to stay attached. Reach for a second-level cache only
for genuinely read-heavy, rarely-changing entities, and only after you have
validated an eviction strategy.
Migrations
Own your schema with Flyway or Liquibase; never let Hibernate ddl-auto touch a
production database. Keep migrations additive and idempotent, and plan any column
drop as a separate, reversible step.
Testing data access
Use @DataJpaTest against a real engine via Testcontainers so tests mirror
production dialect and constraints. Verify query efficiency (and catch N+1
regressions) by asserting on emitted SQL:
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.orm.jdbc.bind=TRACE
Testing expectations
Shared — JUnit 5 + AssertJ for fluent assertions, Mockito for mocking (avoid
partial mocks), deterministic tests with no sleep.
@WebMvcTest(ProductController.class)
@DataJpaTest
@SpringBootTest
@ExtendWith(MockitoExtension.class)
@QuarkusTest
Changelog
- 1.0.0 — Initial skill. JVM coding standards spanning Spring Boot and
Quarkus (naming, immutability, Optional, streams, DI, reactive, exceptions,
config, logging, layout), with the JPA/Hibernate persistence guidance folded
in as the Persistence (JPA) section.
Skill-Import-Attestation: reviewed-by=AE9B236FDAF0462874060C6BCFCFACF00335DC74; sha256=9d5a9f4cf62f41f2f8d1e247c475c70b3b11e964f69fafa31d6d5e63484fcbb2