SP

spring-boot-4-conventions

Standardizes coding conventions for Spring Framework 7 and Boot 4, emphasizing feature-based package layouts and modern async patterns.

Install

mkdir -p .claude/skills/spring-boot-4-conventions && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/13398" && unzip -o skill.zip -d .claude/skills/spring-boot-4-conventions && rm skill.zip

Installs to .claude/skills/spring-boot-4-conventions

Activation

This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.

Spring Framework 7 / Spring Boot 4 idioms and defaults. Use when writing or reviewing controllers, services, configuration, HTTP clients, async/virtual-thread code, or anything touching Spring's programming model.
213 chars✓ has a “when” trigger
Advanced

Key capabilities

  • Organize packages by feature, not by layer
  • Use constructor injection for dependencies
  • Prefer `@HttpExchange` declarative clients for outbound HTTP
  • Configure virtual threads
  • Use Java 25 features like records and sealed types

How it works

The skill applies Spring Framework 7 and Spring Boot 4 idioms and defaults, guiding package layout, dependency injection, HTTP client choices, and Java 25 feature usage.

Inputs & outputs

You give it
Spring components (controllers, services, configuration), PRs against Spring 6.x patterns
You get back
Spring Framework 7 / Spring Boot 4 compliant code and architecture

When to use spring-boot-4-conventions

  • Write Spring 7 controllers/services
  • Apply feature-based package layout
  • Review Spring Boot 4 PRs
  • Configure virtual threads

About this skill

Spring Framework 7 / Spring Boot 4 conventions

Java 25 + Spring Framework 7 + Spring Boot 4. Prefer the most modern idiom unless an ADR explains otherwise.

Defaults to apply

Package layout — by feature, not by layer

Top-level packages are bounded contexts / features, not technical layers. Inside a feature, split by visibility (api published, private impl hidden). For the private impl sub-packages, apply the following rule:

  • One class of a given type (single entity, single service, etc.) — keep it directly in the feature package (no sub-package).
  • Multiple classes of the same type — use a typed sub-package within the feature:
    • model/ — JPA entities
    • repository/ — Spring Data repositories
    • service/ — service classes / implementations
    • service/ — service implementations
  • Inside api/ — keep the controller (or service interface) at the api/ level; put all request/response DTO records in an api/dto/ sub-package.

All classes in the feature's private sub-packages (model, repository, service, internal) must be public (cross-package visibility is required when code spans multiple sub-packages). Cross-feature access is still forbidden and enforced by ArchUnit — public here means "visible within this feature", not "part of the published API".

The api/ sub-package is the only published surface. Other features depend only on <feature>.api.

com.example.checkout
├── giftcard/                    # feature/domain — one package per bounded context
│   ├── api/                     # published surface: controller, service interfaces, events
│   │   ├── GiftCardController.java
│   │   ├── GiftCardRedemptionService.java
│   │   ├── GiftCardRedeemed.java
│   │   ├── dto/                 # request/response DTOs always in api/dto/
│   │   │   ├── RedeemCommand.java
│   │   │   └── GiftCardRedemptionResponse.java
│   │   └── exception/           # domain exceptions thrown by this feature's services
│   │       └── InsufficientBalanceException.java
│   ├── model/                   # entities (multiple → typed sub-package)
│   │   ├── GiftCardEntity.java
│   │   └── GiftCardTransactionEntity.java
│   ├── repository/              # repositories (multiple → typed sub-package)
│   │   ├── GiftCardRepository.java
│   │   └── GiftCardTransactionRepository.java
│   └── service/                 # service implementations (multiple → typed sub-package)
│       └── GiftCardRedemptionServiceImpl.java
├── order/                       # another feature
│   ├── api/
│   │   └── dto/                 # DTOs always in api/dto/
│   │       └── OrderSummaryResponse.java
│   └── service/                 # single service impl — sub-package still used for consistency
│       └── OrderService.java
└── shared/                      # cross-cutting: error envelope, security config, time
    └── exception/               # GlobalExceptionHandler (@RestControllerAdvice)

Forbidden layouts (do not create these at the application root level):

com.example.checkout
├── controller/        ❌ by-layer at root
├── service/           ❌ by-layer at root
├── repository/        ❌ by-layer at root
└── model/             ❌ by-layer at root

The forbidden layouts are top-level by-layer packages. Typed sub-packages (model/, repository/, service/) are allowed — and encouraged when there are multiple classes — inside a feature/domain package.

Why:

  • Features change together; layers don't. By-feature keeps the diff for one change inside one package.
  • Typed sub-packages within a feature improve navigation when a bounded context grows beyond 4–5 private classes.
  • ArchUnit enforces that no other feature reaches into model/, repository/, or service/ sub-packages (see archunit-rules).
  • It maps 1:1 to the module boundaries enforced by archunit-rules.

Cross-feature interaction:

  • Other features depend only on <feature>.api.
  • Prefer events (ApplicationEventPublisher) for fire-and-forget integration.
  • Direct dependencies between features are explicit and asserted by ArchUnit (e.g. order may depend on giftcard.api, never the reverse).

Dependency injection

  • Constructor injection only. No field @Autowired. No setter injection except where Spring forces it (e.g., a pre-existing framework callback).
  • One final field per dependency. Write the constructor explicitly — Lombok is forbidden (see No Lombok below).
@Service
public class CheckoutService {
    private final OrderRepository orders;
    private final GiftCardClient giftCards;

    public CheckoutService(OrderRepository orders, GiftCardClient giftCards) {
        this.orders = orders;
        this.giftCards = giftCards;
    }
}

HTTP clients

  • Outbound HTTP: prefer @HttpExchange declarative clients built on RestClient. Use WebClient only when the call is genuinely reactive end-to-end.
  • Do not use RestTemplate in new code.
  • Each external API gets its own client interface in the infra package.
@HttpExchange(url = "/cards", accept = "application/json")
public interface GiftCardClient {
    @GetExchange("/{code}")
    GiftCard fetch(@PathVariable String code);
}

Web layer

  • Controllers are @RestController. Return DTO records, not entities.
  • Use Java record for request/response DTOs.
  • Validate input at the controller boundary:
    • Annotate the controller class with @Validated.
    • Annotate every @RequestBody parameter with @Valid.
    • Annotate every @PathVariable and @RequestParam with the appropriate Bean Validation constraint (@Positive, @NotBlank, @Max, etc.).
    • Always add tests that exercise each constraint; a constraint with no test is untested behaviour.
  • Never use Pageable as a controller parameter. Pageable is a Spring Data internal type; exposing it on the API surface leaks persistence concerns, makes parameters implicit to callers, and prevents OpenAPI tooling from generating correct query-param docs. Instead:
    • Declare explicit @RequestParam fields (page, size, sort params).
    • Apply Bean Validation constraints directly (@PositiveOrZero, @Positive, @Max(100)).
    • Construct PageRequest.of(page, size) inside the method and pass it to the service.
    @GetMapping
    FooPageResponse list(
        @RequestParam(defaultValue = "0") @PositiveOrZero int page,
        @RequestParam(defaultValue = "25") @Positive @Max(100) int size) {
      return fooService.getPage(PageRequest.of(page, size));
    }
    
  • Avoid ResponseEntity<T> as a return type unless varying the HTTP status code at runtime is unavoidable. For the common cases:
    • Add a response header (e.g. ETag, Location) → inject HttpServletResponse and call response.setHeader(...), then return the DTO directly.
    • 201 Created with Location → use @ResponseStatus(HttpStatus.CREATED) on the method + set the Location header via HttpServletResponse.
    • ResponseEntity is acceptable only in handlers that need to conditionally vary the status (e.g. 304 Not Modified based on If-None-Match).
  • Map exceptions via @RestControllerAdvice to a single, documented error envelope.
// in <feature>/api/dto/
public record ApplyGiftCardRequest(@NotBlank String code, @Min(0) int orderTotalCents) {}
public record ApplyGiftCardResponse(int redeemedCents, int newOrderTotalCents) {}

Persistence

  • Spring Data JPA is the default. Use @Query for non-trivial reads; never rely on derived queries longer than three predicates.
  • Always paginate list endpoints (Pageable). Reject unbounded queries.
  • Use @ServiceConnection with Testcontainers in tests instead of application-test.yml overrides.
  • Use Instant for audit timestamps (created_at, updated_at), not LocalDateTime. Instant is an absolute UTC point in time; LocalDateTime is ambiguous across DST transitions and timezone changes. Hibernate 6 maps Instant to DATETIME/TIMESTAMP columns using UTC normalization — no schema change required.

Virtual threads

  • Spring Boot 4 enables virtual threads for the web server by default. Keep it on. Do not introduce custom Executors that pin to platform threads without measuring.

Configuration

  • One @ConfigurationProperties record per bounded module. Validate with @Validated.
  • No @Value for grouped settings. Use @Value only for ad-hoc single primitives.
@ConfigurationProperties("app.giftcard")
@Validated
public record GiftCardProperties(@NotBlank String baseUrl, @Min(1) int timeoutMs) {}

Observability

  • Structured logging via Logger + key/value pairs (Boot 4 native StructuredLoggingFormatter).
  • Metrics via MeterRegistry; one counter per business outcome (gift_card.redeemed, gift_card.rejected).

AOT-friendly

  • No reflection on application code.
  • Beans are @Component, @Service, etc. at compile time. No runtime registration.
  • Anything dynamic is registered via a RuntimeHintsRegistrar and noted in the design doc.

Anti-patterns to flag in review

  • Field injection (@Autowired on a field).
  • Lombok in any form (@Data, @Getter, @Setter, @Builder, @RequiredArgsConstructor, @Slf4j, @SneakyThrows, etc.) — see No Lombok below.
  • Top-level packages named controller, service, repository, model, dto, util (by-layer layout at the application root) — use feature/domain packages instead (see Package layout). Note: model/, repository/, and service/ sub-packages within a feature package are allowed when there are multiple classes of that type.
  • RestTemplate in new code.
  • Returning entities from controllers.
  • Untyped Map<String, Object> request/response bodies.
  • Catching Exception then rethrowing RuntimeException with no message.
  • @SpringBootTest for code that a slice test (@WebMvcTest, @DataJpaTest) can cover.
  • Pageable as a controller method parameter — use

Content truncated.

When not to use it

  • When using Lombok in new code
  • When using `RestTemplate` in new code
  • When using field `@Autowired` or setter injection

Limitations

  • Lombok is forbidden in new code.
  • `RestTemplate` is not to be used in new code.
  • Field `@Autowired` and setter injection are not preferred.

How it compares

This skill enforces modern Spring conventions like feature-first package organization and declarative HTTP clients, moving away from older patterns like layer-based packages and `RestTemplate`.

Compared to similar skills

spring-boot-4-conventions side by side with the closest alternatives in the catalog.

SkillInstallsUpdatedSafetyDifficulty
spring-boot-4-conventions (this skill)03moNo flagsAdvanced
java-pro344moNo flagsAdvanced
backend-microservice-development23moNo flagsIntermediate
microservice-infrastructure43moNo flagsIntermediate

Try saying

Example prompts that trigger this skill in your AI assistant.

You might also like

java-pro

sickn33

Master Java 21+ with modern features like virtual threads, pattern matching, and Spring Boot 3.x. Expert in the latest Java ecosystem including GraalVM, Project Loom, and cloud-native patterns. Use PROACTIVELY for Java development, microservices architecture, or performance optimization.

3492

backend-microservice-development

TencentBlueKing

后端微服务开发规范,涵盖目录结构、分层架构(API/Service/DAO)、依赖注入、配置管理、Spring Boot 最佳实践。当用户进行后端开发、创建新微服务、编写 Kotlin/Java 代码或设计服务架构时使用。

213

microservice-infrastructure

TencentBlueKing

微服务基础设施指南,涵盖条件配置、事件驱动架构、服务间通信、国际化与日志等微服务架构的核心基础设施。当用户实现服务间调用、配置多环境、实现异步通信、处理国际化或规范日志输出时使用。

411

common-technical-practices

TencentBlueKing

通用技术实践指南,涵盖 AOP 切面、分布式锁、重试机制、参数校验、性能监控、定时任务、审计日志等后端开发中的常见技术实践。当用户需要实现横切关注点、处理并发控制、配置重试策略、添加性能监控或实现审计功能时使用。

26

clean-architecture

darshanchaudharii

Provides implementation patterns for Clean Architecture, Hexagonal Architecture (Ports & Adapters), and Domain-Driven Design in Java 21+ Spring Boot 3.5+ applications. Use when structuring layered architectures, separating domain logic from frameworks, implementing ports and adapters, creating entit

00

Context7 MCP Rule

dlwlrmjlo

Always use the Context7 MCP server when foundational planning or architecture decisions are being made.

00

Search skills

Search the agent skills registry