| name | java-spring-enterprise |
| description | Designs and implements enterprise-grade Java applications using Spring Boot following Clean Architecture and Hexagonal Architecture principles. Use when scaffolding new Spring Boot projects, designing service layers, implementing repositories, configuring dependency injection, handling transactions with @Transactional, optimizing database connection pools with HikariCP, implementing Virtual Threads for high-throughput I/O, setting up observability with Micrometer and OpenTelemetry, or asked about Spring Boot architecture, domain modeling, layer separation, DDD, ports and adapters, performance tuning, or distributed tracing in Java enterprise applications.
|
| license | MIT |
| metadata | {"author":"iamBrzDev","version":"1.0"} |
When to activate
- Scaffolding a new Spring Boot enterprise project
- "How do I structure this feature?" in a Java/Spring context
- Designing a service layer, use case, or domain model
- Configuring
@Transactional — any question about transactions, isolation, or rollback
- Setting up HikariCP connection pool tuning
- Implementing Virtual Threads (Java 21+) for I/O-heavy operations
- Adding observability: Micrometer, Prometheus, Grafana, OpenTelemetry tracing
- Refactoring a fat controller or a service with too many responsibilities
- Any mention of Hexagonal Architecture, ports/adapters, DDD, or use cases in Java
Rules — Non-negotiable
-
No business logic in controllers. Controllers receive HTTP input,
delegate to a use case or service, and return a response. Nothing else.
-
Domain layer has zero Spring dependencies. Entities and domain services
must not import Spring annotations. Domain is framework-agnostic.
-
@Transactional is not a default annotation. Apply it deliberately,
with the correct isolation level. Add readOnly = true for all read operations.
-
Never expose domain entities in API responses. Always map to DTOs or
response records before returning from the controller.
-
HikariCP pool size must be tuned explicitly. The default pool size (10)
is wrong for most enterprise workloads. Always configure based on load profile.
Project Structure — Clean / Hexagonal Architecture
src/main/java/com/company/app/
├── domain/
│ ├── model/ # Entities, Value Objects, Aggregates — NO Spring here
│ ├── port/
│ │ ├── in/ # Use case interfaces (driving ports)
│ │ └── out/ # Repository + external service interfaces (driven ports)
│ └── service/ # Domain services — pure business logic
│
├── application/
│ └── usecase/ # Use case implementations — orchestrate domain + ports
│ ├── CreateOrderUseCase.java
│ └── GetOrderByIdUseCase.java
│
├── adapter/
│ ├── in/
│ │ └── web/ # Controllers, request/response DTOs, mappers
│ └── out/
│ ├── persistence/ # JPA entities, repositories (Spring Data)
│ └── external/ # HTTP clients, message producers
│
└── config/ # Spring configuration, beans, security
Dependency direction
adapter/in → application → domain ← application ← adapter/out
Domain knows nothing about adapters. Application knows nothing about HTTP or JPA.
Use Case Pattern
Every feature is a use case. Controllers call use cases, not services directly.
public interface CreateOrderUseCase {
OrderResponse execute(CreateOrderCommand command);
}
@Service
@RequiredArgsConstructor
public class CreateOrderUseCaseImpl implements CreateOrderUseCase {
private final OrderRepository orderRepository;
private final InventoryPort inventoryPort;
@Override
@Transactional
public OrderResponse execute(CreateOrderCommand command) {
}
}
@RestController
@RequestMapping("/api/v1/orders")
@RequiredArgsConstructor
public class OrderController {
private final CreateOrderUseCase createOrderUseCase;
@PostMapping
public ResponseEntity<OrderResponse> create(@Valid @RequestBody CreateOrderRequest request) {
CreateOrderCommand command = OrderMapper.toCommand(request);
return ResponseEntity.status(HttpStatus.CREATED)
.body(createOrderUseCase.execute(command));
}
}
@Transactional — Correct Usage
@Transactional
public OrderResponse createOrder(CreateOrderCommand command) { ... }
@Transactional(readOnly = true)
public OrderResponse getOrderById(Long id) { ... }
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void processPayment(PaymentCommand command) { ... }
@Transactional
public class OrderService { ... }
@Transactional
public void createOrder(CreateOrderCommand command) {
try { ... }
catch (Exception e) { log.error("failed"); }
}
Virtual Threads (Java 21+)
For I/O-intensive applications, replace platform thread pools with Virtual Threads:
@Configuration
public class ThreadConfig {
@Bean
public TomcatProtocolHandlerCustomizer<?> virtualThreadsProtocolHandler() {
return protocolHandler ->
protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
}
}
When to use Virtual Threads:
- ✅ REST APIs with high concurrency and blocking I/O (DB calls, HTTP clients)
- ✅ Applications doing many simultaneous external service calls
- ❌ CPU-bound workloads (image processing, heavy computation) — no benefit
HikariCP — Connection Pool Tuning
spring:
datasource:
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 3000
idle-timeout: 600000
max-lifetime: 1800000
pool-name: HikariPool-Enterprise
@Component
@Slf4j
public class DataSourceHealthCheck {
@EventListener(ApplicationReadyEvent.class)
public void logPoolConfig(ApplicationReadyEvent event) {
}
}
Observability — Micrometer + OpenTelemetry
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-otel</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
</dependency>
management:
endpoints:
web:
exposure:
include: health, info, prometheus, metrics
metrics:
distribution:
percentiles-histogram:
http.server.requests: true
tracing:
sampling:
probability: 1.0
@Service
@RequiredArgsConstructor
public class OrderMetrics {
private final MeterRegistry meterRegistry;
public void recordOrderCreated(String channel) {
meterRegistry.counter("orders.created", "channel", channel).increment();
}
}
Common mistakes
-
❌ Calling repositories directly from controllers
-
✅ Controllers → Use Cases → Repositories (always through the port interface)
-
❌ @Transactional on a private method (Spring proxy won't intercept it)
-
✅ @Transactional only on public methods of Spring-managed beans
-
❌ Setting maximum-pool-size: 100 without checking DB max connections
-
✅ Pool size = (DB max connections / number of app instances) × 0.8
-
❌ Returning Optional<Entity> from controllers
-
✅ Map to response DTO inside the use case; throw domain exception if not found
-
❌ Placing @Entity JPA classes inside the domain/model/ package
-
✅ JPA entities live in adapter/out/persistence/; domain model is pure Java
Definition of Done
A feature built with this skill is complete only when:
Reference files
Load on demand:
references/hexagonal-architecture-full.md — full project scaffold with all layers
references/transaction-patterns.md — saga pattern, compensation, outbox for distributed transactions
references/observability-grafana.md — Grafana dashboard setup for Spring Boot metrics