From java-services
Enforces Java 21 production code standards including Records, sealed classes, pattern matching, Optional<T>, hexagonal architecture, and exception hierarchies for code in src/main only (NOT test code). Use when writing Java production code, generating services, or implementing business logic under src/main/.
npx claudepluginhub andercore-labs/claudes-kitchen --plugin java-servicesThis skill uses the workspace's default tool permissions.
**SCOPE:** Production code under `src/main/` only. Test code has separate standards.
Implements structured self-debugging workflow for AI agent failures: capture errors, diagnose patterns like loops or context overflow, apply contained recoveries, and generate introspection reports.
Monitors deployed URLs for regressions in HTTP status, console errors, performance metrics, content, network, and APIs after deploys, merges, or upgrades.
Provides React and Next.js patterns for component composition, compound components, state management, data fetching, performance optimization, forms, routing, and accessible UIs.
SCOPE: Production code under src/main/ only. Test code has separate standards.
Domain Record (value object):
public record UserId(String value) {
public UserId {
Objects.requireNonNull(value, "UserId must not be null");
}
}
Domain entity:
public record UserEntity(UserId id, String email, Instant createdAt) {}
Application port (interface):
public interface UserPort {
Optional<UserEntity> findById(UserId id);
UserEntity save(UserEntity user);
}
Application service:
@Service
public class UserService implements UserUseCase {
private final UserPort userPort;
public UserService(UserPort userPort) {
this.userPort = userPort;
}
public UserEntity createUser(CreateUserCommand cmd) {
return userPort.save(new UserEntity(new UserId(UUID.randomUUID().toString()), cmd.email(), Instant.now()));
}
}
Code generation | service implementation | business logic | REST controllers | exception handling
| Layer | Responsibility | Naming | Dependencies |
|---|---|---|---|
| Domain | Entities, value objects, rules | XxxEntity, XxxRecord, XxxId | None (pure) |
| Application | Use cases, ports, orchestration | XxxService, XxxPort, XxxUseCase | Domain only |
| Inbound | Controllers, consumers, DTOs | XxxController, XxxConsumer, XxxDto | Application interfaces |
| Outbound | Adapters, repositories, clients | XxxRepository, XxxAdapter, XxxClient | Application ports |
Dependency rule: Inbound/Outbound → Application → Domain (never reverse)
Package structure — feature-by-package preferred:
com.example/
├── order/ ← feature package (preferred)
│ ├── domain/
│ ├── application/
│ ├── inbound/
│ └── outbound/
└── user/
└── ... ← same structure per feature
# Layered (controller/service/repository) acceptable for small services only
// domain/UserEntity.java — no Spring, no framework
public record UserEntity(UserId id, String email, Instant createdAt) {}
// application/UserPort.java — interface in Application layer
public interface UserPort {
Optional<UserEntity> findById(UserId id);
}
// application/UserService.java — implements UseCase, depends on Port
@Service
public class UserService implements UserUseCase {
private final UserPort userPort; // interface, not concrete
}
// outbound/UserRepository.java — implements Port
@Repository
public class UserRepository implements UserPort {
// Spring Data or JDBC — framework here, not in domain
}
No Spring annotations in Domain layer. Domain is framework-agnostic.
| Pattern | Status |
|---|---|
class with getters/setters for value objects | ✗ Use record |
Lombok @Value for immutable VO | ✗ Use record |
| Builder with mutation | ✗ Derive new record instance |
| Mutable fields in value objects | ✗ Records are inherently immutable |
Use Lombok to reduce boilerplate — but scoped correctly:
| Annotation | Status | When |
|---|---|---|
@Builder / @SuperBuilder | ✓ | Complex object creation (JPA entities, command objects) |
@RequiredArgsConstructor | ✓ | Constructor injection shorthand |
@Slf4j | ✓ | Logging in any class |
@Data on POJO | ✓ conditional | Non-JPA, non-domain classes only |
@Data on JPA @Entity | ✗ CRITICAL | Breaks lazy loading + equals/hashCode |
@Data on domain value objects | ✗ | Use record |
@EqualsAndHashCode on @Entity | ✗ | Use surrogate key or business key manually |
// ✓ Lombok on JPA entity
@Entity
@Builder
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class OrderJpaEntity {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private UUID orderId;
private String status;
}
// ✓ Lombok shorthand for DI
@Service
@RequiredArgsConstructor
@Slf4j
public class OrderService {
private final OrderPort orderPort;
public void process(UUID id) {
log.info("Processing order {}", id);
}
}
var KeywordUse var for local variable declarations when the type is evident from context:
// ✓ Type obvious from RHS
var order = orderPort.findById(id).orElseThrow(() -> new OrderNotFoundException(id));
var emails = users.stream().map(UserEntity::email).toList();
// ✗ Type not obvious — explicit type adds clarity
Map<UUID, List<Order>> ordersByCustomer = orderPort.groupByCustomer(); // keep explicit
| Pattern | Status |
|---|---|
var when RHS makes type clear | ✓ |
var for loop variables | ✓ |
var when type is ambiguous/non-obvious | ✗ Use explicit type |
// ✓ Value object as record
public record Money(BigDecimal amount, Currency currency) {
public Money {
Objects.requireNonNull(amount);
if (amount.compareTo(BigDecimal.ZERO) < 0) throw new DomainException("Negative amount");
}
public Money add(Money other) { return new Money(amount.add(other.amount), currency); }
}
// ✓ Domain modeling with sealed hierarchy
public sealed interface OrderStatus permits OrderStatus.Pending, OrderStatus.Shipped, OrderStatus.Cancelled {
record Pending(Instant createdAt) implements OrderStatus {}
record Shipped(Instant shippedAt, String trackingId) implements OrderStatus {}
record Cancelled(Instant cancelledAt, String reason) implements OrderStatus {}
}
// ✓ switch expression with guards
String describe(OrderStatus status) {
return switch (status) {
case OrderStatus.Pending p when p.createdAt().isBefore(Instant.now().minus(7, DAYS)) -> "stale";
case OrderStatus.Pending p -> "pending";
case OrderStatus.Shipped s -> "shipped: " + s.trackingId();
case OrderStatus.Cancelled c -> "cancelled: " + c.reason();
};
}
// ✓ instanceof pattern variable
if (event instanceof OrderCreatedEvent e) {
process(e.orderId());
}
| Pattern | Status |
|---|---|
return null | ✗ Use Optional.empty() |
Optional.get() without check | ✗ Use orElseThrow() / orElse() |
Optional<Optional<T>> | ✗ Use flatMap() |
| Null parameter accepted | ✗ Validate at boundary, reject null |
// ✓ Port returns Optional
Optional<UserEntity> findById(UserId id);
// ✓ Service unwraps with semantic throw
UserEntity user = userPort.findById(id)
.orElseThrow(() -> new UserNotFoundException(id));
| Pattern | Status |
|---|---|
Imperative for loop over collection | ✗ Use stream() |
| Side effects inside stream pipeline | ✗ No mutation, no I/O in stream |
stream().forEach() for transformation | ✗ Use map() + collect() |
null elements in stream | ✗ Filter before streaming |
// ✓ Stream pipeline
List<String> emails = users.stream()
.filter(u -> u.active())
.map(UserEntity::email)
.collect(Collectors.toUnmodifiableList());
// ✓ Multiline SQL or JSON
String query = """
SELECT id, email, created_at
FROM users
WHERE active = true
ORDER BY created_at DESC
""";
| Pattern | Status |
|---|---|
| Platform threads for blocking I/O | ✗ Use virtual threads |
| Virtual threads for CPU-bound work | ✗ Use platform threads / ForkJoinPool |
synchronized blocks in virtual threads | ✗ Use ReentrantLock (avoid pinning) |
// ✓ Virtual thread executor for I/O-bound tasks
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(() -> fetchFromExternalApi(id));
}
| Pattern | Status |
|---|---|
record for value objects/DTOs | ✓ |
List.copyOf() / Collections.unmodifiableList() | ✓ |
final fields | ✓ |
| Setters on domain objects | ✗ |
Static mutable state (static List, static Map) | ✗ |
Collections.sort() on injected list | ✗ Copy first |
// domain/exception/DomainException.java
public abstract class DomainException extends RuntimeException {
public DomainException(String message) { super(message); }
public abstract String userFriendlyMessage();
}
// domain/exception/UserNotFoundException.java
public class UserNotFoundException extends DomainException {
private final UserId userId;
public UserNotFoundException(UserId userId) {
super("User not found: " + userId.value());
this.userId = userId;
}
@Override public String userFriendlyMessage() {
return "The requested user does not exist.";
}
}
// application/exception/ApplicationException.java
public abstract class ApplicationException extends RuntimeException {
public ApplicationException(String message) { super(message); }
public abstract String userFriendlyMessage();
}
// outbound/exception/InfrastructureException.java
public class InfrastructureException extends RuntimeException {
public InfrastructureException(String message, Throwable cause) { super(message, cause); }
}
Controller catches, domain/application throws:
@ExceptionHandler(DomainException.class)
public ResponseEntity<ErrorDto> handle(DomainException ex) {
return ResponseEntity.badRequest().body(new ErrorDto(ex.userFriendlyMessage()));
}
| Never Allowed | Use Instead |
|---|---|
return null | Optional.empty() or throw |
Raw types (List, Map) | List<T>, Map<K, V> |
@SuppressWarnings("unchecked") without comment | Fix type issue or add explanation |
| Static mutable state | Inject dependencies |
Old instanceof without pattern var | instanceof Foo f |
throws Exception / checked exceptions in domain | Unchecked DomainException |
Catch-and-swallow (catch (Exception e) {}) | Re-throw or handle meaningfully |
Abbreviations (usr, mgr, repo) | user, manager, repository |
| Spring annotations in Domain layer | Framework-free domain |
Mutable fields without final | final on all non-mutating fields |
| Component | Pattern | Example |
|---|---|---|
| Value object | XxxRecord or record Xxx | MoneyRecord, UserId |
| Domain entity | XxxEntity | UserEntity, OrderEntity |
| Type-safe ID | XxxId (record) | UserId, OrderId |
| Application service | XxxService | UserService |
| Use case interface | XxxUseCase | CreateOrderUseCase |
| Port interface | XxxPort | UserPort, PaymentPort |
| Inbound controller | XxxController | UserController |
| Inbound consumer | XxxConsumer | OrderEventConsumer |
| Inbound DTO | XxxDto | CreateUserDto, UserResponseDto |
| Outbound adapter | XxxAdapter | StripePaymentAdapter |
| Outbound repository | XxxRepository | UserRepository |
| Methods | camelCase, verb phrase | findById, createOrder |
| Constants | SCREAMING_SNAKE_CASE | MAX_RETRIES, DEFAULT_TIMEOUT_MS |
Use MapStruct to eliminate manual mapping boilerplate between entities and DTOs:
// ✓ MapStruct mapper as Spring component
@Mapper(componentModel = "spring")
public interface OrderMapper {
OrderResponseDto toDto(OrderEntity entity);
OrderEntity toDomain(CreateOrderRequest request);
List<OrderResponseDto> toDtoList(List<OrderEntity> entities);
}
// ✓ Inject and use in controller/service
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderMapper mapper;
public OrderResponseDto findById(UUID id) {
return mapper.toDto(orderPort.findById(id).orElseThrow());
}
}
| Rule | Status |
|---|---|
@Mapper(componentModel = "spring") | ✓ Spring DI integration |
| Manual field-by-field mapping | ✗ Use MapStruct |
| ModelMapper or Dozer | ✗ MapStruct only (compile-time safe) |
Style: Follow Google Java Style Guide as primary code style.
// ✓ Prefer method references
List<String> emails = users.stream().map(UserEntity::email).toList();
// ✓ Use standard functional interfaces
Function<UserId, Optional<UserEntity>> finder = userPort::findById;
Predicate<UserEntity> isActive = UserEntity::active;
Supplier<UserId> idGenerator = () -> new UserId(UUID.randomUUID().toString());
CHECK:
1. Hexagonal layers → Domain/Application/Inbound/Outbound separation
2. Dependency direction → Inbound/Outbound depend on Application, never reverse
3. No Spring in Domain → framework-free domain classes
4. Records for value objects → no class-based VOs with setters
5. Sealed classes → algebraic domain types use sealed hierarchy
6. Pattern matching → switch expressions with guards, instanceof patterns
7. Optional<T> → no null returns at boundaries; orElseThrow() / orElse()
8. Stream API → no imperative for loops; no side effects in pipelines
9. Exception hierarchy → DomainException / ApplicationException / InfrastructureException
10. userFriendlyMessage() → present on domain/application exceptions
11. No checked exceptions → domain/application layers throw unchecked only
12. No catch-and-swallow → every catch re-throws or handles meaningfully
13. Immutability → records for VOs, final fields, unmodifiable collections
14. No null returns → Optional.empty() or throw
15. No raw types → all generics parameterized
16. No static mutable state → inject dependencies
17. Virtual threads → I/O-bound tasks use Thread.ofVirtual() or virtual executor
18. Naming conventions → suffixes per role, no abbreviations, constants SCREAMING_SNAKE_CASE
19. Text blocks → multiline SQL/JSON use """..."""
20. Lombok → @Data ABSENT on @Entity; @Builder/@Slf4j/@RequiredArgsConstructor permitted
21. MapStruct → entity↔DTO mapping uses @Mapper(componentModel = "spring"); no manual mapping
22. var keyword → used for local vars where type is clear from RHS
23. Package structure → feature-by-package (preferred) or layered (small services only)
24. All pass → Generate code
ANY fail → REJECT with violation
| Phase | Action |
|---|---|
| 1. Scan | Identify Java production files in src/main/ |
| 2. Validate | Run ALL 20 verification protocol checks → gather violations |
| 3. Report | ✓ ALL pass → Done | ✗ ANY fail → REJECT with violations |
| 4. Fix | Violations → Regenerate → Re-validate |
| 5. Store Metrics | After ALL validation passes, call mcp__agent-orchestrator__store-skill-metrics |
Output Format:
{
"discipline": "java-production-code",
"timestamp": "ISO8601",
"violations": [
{
"file": "src/main/java/com/example/domain/User.java",
"line": 14,
"check": "Records for value objects",
"violation": "Class-based value object with setters",
"severity": "critical",
"fix": "Convert to record"
}
],
"metrics": {
"initialViolations": 3,
"finalViolations": 0,
"fixRate": 1.0
},
"summary": { "critical": 0, "errors": 0, "warnings": 0 }
}
Metrics call:
mcp__agent-orchestrator__store-skill-metrics({
sessionId: "[from context]",
skill: "java-services:production-code-recipe",
initialViolations: N,
iterations: N,
fixesApplied: N,
finalViolations: 0,
validationPassed: true,
durationSeconds: N,
metadata: { language: "java21" }
})