From java-guidelines
Apply Java coding standards, patterns, and best practices whenever you are writing, reviewing, editing, or generating Java code. Trigger this skill when the user asks you to create a Java class, interface, enum, or any .java file; when reviewing existing Java code; when the user mentions Java, Spring, Spring Boot, Maven, Gradle, JUnit, or any Java framework; or when editing files with a .java extension.
How this skill is triggered — by the user, by Claude, or both
Slash command
/java-guidelines:java-guidelinesThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Apply these conventions and patterns consistently across all Java code you write or modify.
Apply these conventions and patterns consistently across all Java code you write or modify. This project uses Spring Boot as the primary framework.
Good comments explain why something is done, not what the code literally does — the code already shows what. Write comments as you would explain something to a capable colleague: brief, clear, and human.
Every public class, interface, enum, and method you create must have a Javadoc comment. Keep it concise — one sentence is often enough. Only add @param, @return, and @throws tags when they add meaning beyond the method signature.
/**
* Handles order placement and lifecycle management.
* Delegates persistence to {@link OrderRepository} and payment to {@link PaymentGateway}.
*/
@Service
public class OrderService { ... }
/**
* Places a new order for the given customer.
*
* @param request the order details including items and delivery address
* @return the persisted order with its assigned ID and initial status
* @throws OutOfStockException if any requested item is unavailable
*/
public Order placeOrder(PlaceOrderRequest request) { ... }
For simple getters, records, and self-explanatory methods a single-line Javadoc is fine:
/** Returns the total price including tax and shipping. */
public BigDecimal getTotal() { ... }
Add an inline comment only when the logic isn't obvious from the code itself. Prefer a well-named helper method over a comment explaining a long block.
// Retry once on transient network failure — the gateway is eventually consistent
try {
paymentGateway.charge(order);
} catch (TransientPaymentException e) {
log.warn("Payment attempt failed, retrying once. orderId={}", order.getId());
paymentGateway.charge(order);
}
Do not comment things the code already says clearly:
// BAD — the code is already self-explanatory
int total = items.size(); // get the number of items
// GOOD — no comment needed
int itemCount = items.size();
UpperCamelCase — OrderService, PaymentGatewaylowerCamelCase — calculateTotal(), maxRetryCountUPPER_SNAKE_CASE — MAX_CONNECTIONS, DEFAULT_TIMEOUT_MScom.example.order.serviceTest — OrderServiceTestBuilder — UserBuilderi, j, k).Always use constructor injection. Spring will inject automatically when there is a single constructor — no @Autowired needed. This keeps dependencies explicit and makes classes easy to test without a Spring context.
@Service
public class OrderService {
private final OrderRepository orderRepository;
private final PaymentGateway paymentGateway;
// Spring injects these automatically — no @Autowired needed
public OrderService(OrderRepository orderRepository, PaymentGateway paymentGateway) {
this.orderRepository = orderRepository;
this.paymentGateway = paymentGateway;
}
}
Never use field injection (@Autowired on a field) — it hides dependencies and breaks testability.
Use the most specific annotation that fits:
| Annotation | Use for |
|---|---|
@Service | Business logic / use-case classes |
@Repository | Data access implementations |
@RestController | HTTP REST endpoints |
@Component | General Spring-managed beans that don't fit the above |
@Configuration | Spring configuration classes that declare @Bean methods |
Keep controllers thin — they translate HTTP to method calls and back, nothing more. All logic lives in the service.
/**
* REST endpoints for order management.
* All business logic is delegated to {@link OrderService}.
*/
@RestController
@RequestMapping("/api/v1/orders")
public class OrderController {
private final OrderService orderService;
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
/**
* Places a new order.
*
* @param request the order details from the request body
* @return the created order with HTTP 201
*/
@PostMapping
public ResponseEntity<OrderResponse> placeOrder(@RequestBody @Valid PlaceOrderRequest request) {
var order = orderService.placeOrder(request);
return ResponseEntity.status(HttpStatus.CREATED).body(OrderResponse.from(order));
}
/**
* Retrieves an order by ID.
*
* @param id the order ID from the URL path
* @return the order if found, or 404
*/
@GetMapping("/{id}")
public ResponseEntity<OrderResponse> getOrder(@PathVariable UUID id) {
var order = orderService.getOrder(id);
return ResponseEntity.ok(OrderResponse.from(order));
}
}
Bind configuration to a typed class rather than injecting @Value fields individually. This groups related config, validates on startup, and is self-documenting.
/**
* Configuration properties for the payment gateway integration.
* Values are read from application.yml under the 'payment' prefix.
*/
@ConfigurationProperties(prefix = "payment")
public record PaymentProperties(String apiUrl, Duration timeout, int maxRetries) {}
# application.yml
payment:
api-url: https://payments.example.com
timeout: 5s
max-retries: 3
Enable with @EnableConfigurationProperties(PaymentProperties.class) on your @Configuration class.
Use a @RestControllerAdvice class to handle exceptions centrally. Controllers should not contain try/catch blocks.
/**
* Translates domain exceptions into consistent HTTP error responses.
* Keeps exception-handling logic out of individual controllers.
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* Handles requests for resources that don't exist.
*/
@ExceptionHandler(OrderNotFoundException.class)
public ResponseEntity<ErrorResponse> handleNotFound(OrderNotFoundException ex) {
log.warn("Resource not found: {}", ex.getMessage());
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(new ErrorResponse(ex.getMessage()));
}
/**
* Catches unexpected errors so the API always returns a structured response.
*/
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleUnexpected(Exception ex) {
log.error("Unexpected error", ex);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new ErrorResponse("An unexpected error occurred"));
}
}
Extend JpaRepository — you get standard CRUD for free. Only add methods for queries the service actually needs.
/**
* Persistence operations for {@link Order}.
* Spring Data generates implementations automatically at runtime.
*/
@Repository
public interface OrderRepository extends JpaRepository<Order, UUID> {
/** Returns all orders placed by the given customer, newest first. */
List<Order> findByCustomerIdOrderByCreatedAtDesc(UUID customerId);
/** Returns orders in the given status, for use in background processing jobs. */
List<Order> findByStatus(OrderStatus status);
}
private by default. Expose only what is needed via well-named methods.final unless you intend them to be extended.Use for objects with many optional fields. In Spring Boot projects, prefer Lombok's @Builder to reduce boilerplate, but write it by hand if Lombok is not available.
/**
* Represents a customer notification to be sent via email or SMS.
* Use {@link Builder} to construct instances — direct construction is not allowed.
*/
public final class Notification {
private final UUID recipientId;
private final String subject;
private final String body;
private final NotificationType type;
private Notification(Builder builder) {
this.recipientId = builder.recipientId;
this.subject = builder.subject;
this.body = builder.body;
this.type = builder.type;
}
/** Builder for {@link Notification}. {@code recipientId} and {@code type} are required. */
public static final class Builder {
private final UUID recipientId;
private final NotificationType type;
private String subject;
private String body;
public Builder(UUID recipientId, NotificationType type) {
this.recipientId = recipientId;
this.type = type;
}
public Builder subject(String subject) { this.subject = subject; return this; }
public Builder body(String body) { this.body = body; return this; }
/** Constructs the {@link Notification}. */
public Notification build() { return new Notification(this); }
}
}
Use when creation logic is complex or the concrete type may vary at runtime.
/**
* Creates {@link PaymentProcessor} instances for a given payment method.
* Add a new case here when a new payment method is supported — no other class needs to change.
*/
@Component
public class PaymentProcessorFactory {
private final StripeProcessor stripeProcessor;
private final PayPalProcessor payPalProcessor;
public PaymentProcessorFactory(StripeProcessor stripeProcessor, PayPalProcessor payPalProcessor) {
this.stripeProcessor = stripeProcessor;
this.payPalProcessor = payPalProcessor;
}
/**
* Returns the processor for the given payment method.
*
* @throws IllegalArgumentException if the payment method is not supported
*/
public PaymentProcessor forMethod(PaymentMethod method) {
return switch (method) {
case STRIPE -> stripeProcessor;
case PAYPAL -> payPalProcessor;
};
}
}
Encapsulate interchangeable algorithms behind a common interface. In Spring, you can inject all implementations as a list and select at runtime.
/**
* Calculates the final price for an order given a base price.
* Implementations represent different pricing rules (e.g. seasonal discount, loyalty reward).
*/
@FunctionalInterface
public interface PricingStrategy {
/** @return the adjusted price — never negative */
BigDecimal calculate(BigDecimal basePrice);
}
Keep business logic in the service. The controller maps HTTP; the service owns the rules; the repository owns the data.
/**
* Manages the full lifecycle of an order from placement through to fulfilment.
*/
@Service
public class OrderService {
private static final Logger log = LoggerFactory.getLogger(OrderService.class);
private final OrderRepository orderRepository;
private final InventoryService inventoryService;
private final PaymentGateway paymentGateway;
public OrderService(
OrderRepository orderRepository,
InventoryService inventoryService,
PaymentGateway paymentGateway) {
this.orderRepository = orderRepository;
this.inventoryService = inventoryService;
this.paymentGateway = paymentGateway;
}
/**
* Places a new order, reserving inventory and charging the customer.
*
* @param request the validated order request
* @return the saved order with its assigned ID
* @throws OutOfStockException if any item in the order is unavailable
*/
public Order placeOrder(PlaceOrderRequest request) {
inventoryService.reserveItems(request.items()); // throws if any item is out of stock
var order = Order.builder()
.customerId(request.customerId())
.items(request.items())
.status(OrderStatus.PENDING)
.build();
var saved = orderRepository.save(order);
log.info("Order placed. orderId={} customerId={}", saved.getId(), saved.getCustomerId());
paymentGateway.charge(saved);
return saved;
}
}
Organise by feature (vertical slices), not by layer. Each feature package is self-contained.
com.example.app
├── order/
│ ├── Order.java # JPA entity / domain model
│ ├── OrderService.java # business logic
│ ├── OrderRepository.java # Spring Data interface
│ ├── OrderController.java # REST endpoints
│ ├── OrderStatus.java # enum for order state
│ └── dto/
│ ├── PlaceOrderRequest.java
│ └── OrderResponse.java
├── payment/
│ ├── PaymentGateway.java # interface — keeps the service decoupled from providers
│ ├── StripeProcessor.java
│ └── PaymentProcessorFactory.java
└── shared/
├── exception/ # GlobalExceptionHandler and custom exceptions
└── config/ # Spring @Configuration classes
Avoid flat service/, controller/, repository/ top-level packages — they make features hard to reason about as a unit.
Use Java records for request/response DTOs — they are immutable and require no boilerplate.
/**
* Request body for placing a new order.
* Validated on arrival at the controller via {@code @Valid}.
*/
public record PlaceOrderRequest(
@NotNull UUID customerId,
@NotEmpty List<@Valid OrderItem> items,
@NotNull DeliveryAddress deliveryAddress
) {}
/**
* API response for a single order. Constructed from an {@link Order} entity.
*/
public record OrderResponse(UUID id, OrderStatus status, BigDecimal total) {
/** Converts an {@link Order} entity into an API response. */
public static OrderResponse from(Order order) {
return new OrderResponse(order.getId(), order.getStatus(), order.getTotal());
}
}
RuntimeException subclasses) for domain errors that the caller cannot recover from locally.GlobalExceptionHandler decide the HTTP status — never set it in the service./**
* Thrown when an order cannot be found by the given ID.
* Mapped to HTTP 404 by {@link GlobalExceptionHandler}.
*/
public class OrderNotFoundException extends RuntimeException {
public OrderNotFoundException(UUID orderId) {
super("Order not found: " + orderId);
}
}
catch (Exception e) {}).try-with-resources for all Closeable / AutoCloseable resources.Use Bean Validation (jakarta.validation) on DTOs and let Spring wire it up automatically.
public record PlaceOrderRequest(
@NotNull(message = "Customer ID is required") UUID customerId,
@NotEmpty(message = "Order must contain at least one item") List<OrderItem> items
) {}
Activate in the controller with @Valid on the request body parameter. Spring will return 400 automatically for constraint violations — you do not need to write that code yourself.
Optional<T> as a return type when a value may be absent — never as a method parameter or field type..get() without first checking — prefer .orElseThrow(), .orElse(), or .ifPresent().orderRepository.findById(id)
.orElseThrow(() -> new OrderNotFoundException(id));
List over ArrayList, Map over HashMap.null for collections — return an empty collection instead.<ClassName>Test.@Test, @BeforeEach, @DisplayName).placeOrder_whenItemOutOfStock_throwsException.@SpringBootTest + @AutoConfigureMockMvc.new the class under test with mocked dependencies.class OrderServiceTest {
// Mocked so tests run without a database or real payment provider
@Mock private OrderRepository orderRepository;
@Mock private PaymentGateway paymentGateway;
private OrderService orderService;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
orderService = new OrderService(orderRepository, paymentGateway);
}
@Test
@DisplayName("placeOrder throws when an item is out of stock")
void placeOrder_whenItemOutOfStock_throwsException() {
// Arrange
var request = new PlaceOrderRequest(CUSTOMER_ID, List.of(OUT_OF_STOCK_ITEM), ADDRESS);
when(inventoryService.isAvailable(OUT_OF_STOCK_ITEM)).thenReturn(false);
// Act & Assert
assertThrows(OutOfStockException.class, () -> orderService.placeOrder(request));
}
}
{} even for single-line if/for/while bodies.@Override.var for local variables when the type is obvious from the right-hand side:
var order = orderRepository.findById(id).orElseThrow(...);
System.out.println.private static final:
private static final Logger log = LoggerFactory.getLogger(OrderService.class);
log.info("Order placed. orderId={} customerId={}", order.getId(), order.getCustomerId());
DEBUG for diagnostic detail, INFO for key lifecycle events, WARN for recoverable issues, ERROR for failures that need attention.null returns from public methods — use Optional or empty collections.Throwable or Error.usrSvc, ord) — spell things out.@Autowired field injection — use constructor injection instead.@ConfigurationProperties and application.yml.npx claudepluginhub landonia/claude-plugins --plugin java-guidelinesEnforces Java coding standards for Spring Boot and Quarkus services: naming, immutability, Optional, streams, exceptions, generics, CDI, reactive patterns, and project layout.
Enforces Java 17+ coding standards for Spring Boot services: naming, immutability, Optional, streams, exceptions, generics, and project layout.
Provides behavioral guidelines to reduce common LLM coding mistakes, focusing on simplicity, surgical changes, assumption surfacing, and verifiable success criteria.