springboot-patterns

Document and implement Spring Boot architectural patterns for RESTful services.

2.5k|877|Updated Feb 4, 2026
One-click install
npx skills add https://github.com/rohitg00/awesome-claude-code-toolkit --skill springboot-patterns-rohitg00
Or copy as Structured Prompt for Agent
Please help me install this Agent Skill.
Skill: springboot-patterns
Source: https://github.com/rohitg00/awesome-claude-code-toolkit/tree/main/skills/springboot-patterns
Command: npx skills add https://github.com/rohitg00/awesome-claude-code-toolkit --skill springboot-patterns-rohitg00

SYSTEM DOCUMENTATION & REQUIREMENTS

Spring Boot Patterns

Layered Architecture

src/main/java/com/example/app/
  config/          # @Configuration beans
  controller/      # @RestController (thin, delegates to service)
  service/         # @Service (business logic)
  repository/      # @Repository (data access via JPA)
  model/
    entity/        # @Entity JPA classes
    dto/           # Request/response DTOs
    mapper/        # MapStruct or manual mapping
  exception/       # @ControllerAdvice, custom exceptions
  security/        # SecurityFilterChain, JWT filters

Controllers handle HTTP concerns. Services contain business logic. Repositories handle persistence.

REST Controller

@RestController
@RequestMapping("/api/v1/orders")
@RequiredArgsConstructor
public class OrderController {

    private final OrderService orderService;

    @GetMapping
    public Page<OrderResponse> list(
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "20") int size) {
        return orderService.findAll(PageRequest.of(page, size));
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public OrderResponse create(@Valid @RequestBody CreateOrderRequest request) {
        return orderService.create(request);
    }

    @GetMapping("/{id}")
    public OrderResponse getById(@PathVariable UUID id) {
        return orderService.findById(id);
    }
}

JPA Entity and Repository

@Entity
@Table(name = "orders")
@Getter @Setter @NoArgsConstructor
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private UUID id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "customer_id", nullable = false)
    private Customer customer;

    @OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<OrderItem> items = new ArrayList<>();

    @Enumerated(EnumType.STRING)
    private OrderStatus status = OrderStatus.PENDING;

    @CreationTimestamp
    private Instant createdAt;
}

public interface OrderRepository extends JpaRepository<Order, UUID> {
    @Query("SELECT o FROM Order o JOIN FETCH o.items WHERE o.customer.id = :customerId")
    List<Order> findByCustomerWithItems(@Param("customerId") UUID customerId);

    @EntityGraph(attributePaths = {"customer", "items"})
    Optional<Order> findWithDetailsById(UUID id);
}

Service Layer

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class OrderService {

    private final OrderRepository orderRepository;
    private final OrderMapper orderMapper;
    private final EventPublisher eventPublisher;

    public OrderResponse findById(UUID id) {
        Order order = orderRepository.findWithDetailsById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Order", id));
        return orderMapper.toResponse(order);
    }

    @Transactional
    public OrderResponse create(CreateOrderRequest request) {
        Order order = orderMapper.toEntity(request);
        order = orderRepository.save(order);
        eventPublisher.publish(new OrderCreatedEvent(order.getId()));
        return orderMapper.toResponse(order);
    }
}

Global Exception Handler

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(ResourceNotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ProblemDetail handleNotFound(ResourceNotFoundException ex) {
        return ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, ex.getMessage());
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ProblemDetail handleValidation(MethodArgumentNotValidException ex) {
        ProblemDetail detail = ProblemDetail.forStatus(HttpStatus.BAD_REQUEST);
        Map<String, String> errors = ex.getFieldErrors().stream()
                .collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));
        detail.setProperty("errors", errors);
        return detail;
    }
}

Anti-Patterns

  • Injecting repositories directly into controllers (bypassing service layer)
  • Using FetchType.EAGER on entity relationships by default
  • Returning JPA entities directly from controllers instead of DTOs
  • Missing @Transactional(readOnly = true) on read-only service methods
  • Catching generic Exception instead of specific types
  • Hardcoding configuration values instead of using @Value or @ConfigurationProperties

Checklist

  • [ ] Controllers are thin and delegate to services
  • [ ] All JPA relationships use FetchType.LAZY by default
  • [ ] DTOs used for request/response, never raw entities
  • [ ] @Transactional applied at service level with correct read/write scoping
  • [ ] Validation annotations (@Valid, @NotNull, @Size) on request DTOs
  • [ ] Global exception handler returns ProblemDetail (RFC 7807)
  • [ ] Entity graphs or JOIN FETCH used to avoid N+1 queries
  • [ ] Integration tests use @SpringBootTest with test containers

Frequently Asked Questions about springboot-patterns

High-intent search queries and answers about installing and using this skill.

FAQPage Schema
How do I structure a Spring Boot RESTful service with JPA repositories and layered services?

Structure Spring Boot RESTful services by organizing code into distinct layers: thin controllers for HTTP, transactional services for business logic, JPA repositories for data access, and separate DTOs to ensure consistent request and response boundaries.

What is the best way to handle errors in Spring Boot REST APIs using RFC 7807?

Handle Spring Boot REST API errors using a @RestControllerAdvice global exception handler that catches specific exceptions and returns ProblemDetail objects, satisfying RFC 7807 compliance with standardized HTTP status codes and validation error mappings.

How do I avoid N+1 queries in Spring Boot JPA repositories?

Avoid N+1 queries in Spring Boot JPA repositories by using FetchType.LAZY on entity relationships and applying @EntityGraph or JOIN FETCH in your repository queries to eagerly load required associations only when needed.

Why should I use DTOs instead of JPA entities in Spring Boot controllers?

Use DTOs instead of JPA entities in Spring Boot controllers to enforce clear module boundaries, prevent accidental exposure of sensitive database fields, decouple API contracts from persistence logic, and ensure consistent request validation.

Can I inject JPA repositories directly into Spring Boot REST controllers?

Injecting JPA repositories directly into Spring Boot REST controllers is an anti-pattern that bypasses the service layer, mixing data access with HTTP concerns and breaking clear module boundaries required for maintainable, scalable applications.

When do I need @Transactional(readOnly = true) on Spring Boot service methods?

Apply @Transactional(readOnly = true) on Spring Boot service methods that only fetch data to optimize JPA persistence context performance, ensure clear read/write scoping, and avoid accidental database modifications during standard query operations.