Awesome-claude-code-toolkit springboot-patterns
Spring Boot patterns including JPA repositories, REST controllers, layered services, and configuration
install
source · Clone the upstream repo
git clone https://github.com/rohitg00/awesome-claude-code-toolkit
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/rohitg00/awesome-claude-code-toolkit "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/springboot-patterns" ~/.claude/skills/rohitg00-awesome-claude-code-toolkit-springboot-patterns && rm -rf "$T"
manifest:
skills/springboot-patterns/SKILL.mdsource content
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
on entity relationships by defaultFetchType.EAGER - Returning JPA entities directly from controllers instead of DTOs
- Missing
on read-only service methods@Transactional(readOnly = true) - Catching generic
instead of specific typesException - Hardcoding configuration values instead of using
or@Value@ConfigurationProperties
Checklist
- Controllers are thin and delegate to services
- All JPA relationships use
by defaultFetchType.LAZY - DTOs used for request/response, never raw entities
-
applied at service level with correct read/write scoping@Transactional - Validation annotations (
,@Valid
,@NotNull
) on request DTOs@Size - Global exception handler returns
(RFC 7807)ProblemDetail - Entity graphs or
used to avoid N+1 queriesJOIN FETCH - Integration tests use
with test containers@SpringBootTest