Claude-skill-registry java-spring-service
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/java-spring-service" ~/.claude/skills/majiayu000-claude-skill-registry-java-spring-service && rm -rf "$T"
skills/data/java-spring-service/SKILL.mdSpring Boot Service Generator
Table of Contents
- Purpose
- When to Use
- Quick Start
- Instructions
- Examples
- Requirements
- Best Practices Checklist
- Output Format
- Error Handling
Purpose
Generates complete, production-ready Spring Boot services following industry best practices: proper layered architecture (Controller/Service/Repository), dependency injection, exception handling with @ControllerAdvice, OpenAPI/Swagger documentation, configuration management, and comprehensive logging.
When to Use
Use this skill when you need to:
- Create new Spring Boot REST APIs
- Generate CRUD services with JPA repositories
- Set up layered architecture (Controller/Service/Repository)
- Implement RESTful endpoints with proper HTTP methods
- Add OpenAPI/Swagger documentation
- Configure Spring Data JPA entities and repositories
- Create DTOs and mappers for API contracts
- Implement global exception handling with @ControllerAdvice
- Set up dependency injection with constructor injection
- Generate complete Spring Boot microservices
- Add pagination and sorting to endpoints
- Configure database migrations with Flyway
- Bootstrap new Spring Boot projects with best practices
Quick Start
Describe the service you need and get a complete implementation:
# Create a complete CRUD service Create a Spring Boot service for User management with CRUD operations # Create a specific endpoint Create a Spring Boot REST endpoint for processing orders
Instructions
Step 1: Understand Service Requirements
Identify what needs to be created:
- Entity/domain model (JPA entity)
- Repository layer (Spring Data)
- Service layer (business logic)
- Controller layer (REST API)
- DTOs (request/response objects)
- Exception handling
- Configuration
- API documentation
Step 2: Generate Domain Model (Entity)
Create JPA entity with proper annotations:
@Entity @Table(name = "users") @Data @NoArgsConstructor @AllArgsConstructor @Builder public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false, length = 100) private String name; @Column(nullable = false, unique = true, length = 255) private String email; @Column(name = "phone_number", length = 20) private String phoneNumber; @Enumerated(EnumType.STRING) @Column(nullable = false) private UserStatus status; @CreatedDate @Column(nullable = false, updatable = false) private LocalDateTime createdAt; @LastModifiedDate @Column(nullable = false) private LocalDateTime updatedAt; @Version private Long version; }
Key Annotations:
- @Entity, @Table - JPA entity mapping
- @Id, @GeneratedValue - Primary key generation
- @Column - Column constraints and naming
- @Enumerated - Enum mapping
- @CreatedDate, @LastModifiedDate - Audit fields
- @Version - Optimistic locking
- @Data (Lombok) - Getters, setters, toString, equals, hashCode
- @Builder (Lombok) - Builder pattern
Step 3: Create DTOs (Data Transfer Objects)
Separate internal models from API contracts:
// Request DTO @Data @NoArgsConstructor @AllArgsConstructor @Builder public class CreateUserRequest { @NotBlank(message = "Name is required") @Size(min = 2, max = 100, message = "Name must be between 2 and 100 characters") private String name; @NotBlank(message = "Email is required") @Email(message = "Email must be valid") private String email; @Pattern(regexp = "^\\+?[1-9]\\d{1,14}$", message = "Phone number must be in E.164 format") private String phoneNumber; } // Response DTO @Data @NoArgsConstructor @AllArgsConstructor @Builder public class UserResponse { private Long id; private String name; private String email; private String phoneNumber; private UserStatus status; private LocalDateTime createdAt; private LocalDateTime updatedAt; } // Mapper @Component public class UserMapper { public User toEntity(CreateUserRequest request) { return User.builder() .name(request.getName()) .email(request.getEmail()) .phoneNumber(request.getPhoneNumber()) .status(UserStatus.ACTIVE) .build(); } public UserResponse toResponse(User user) { return UserResponse.builder() .id(user.getId()) .name(user.getName()) .email(user.getEmail()) .phoneNumber(user.getPhoneNumber()) .status(user.getStatus()) .createdAt(user.getCreatedAt()) .updatedAt(user.getUpdatedAt()) .build(); } }
Step 4: Create Repository Layer
Use Spring Data JPA for data access:
@Repository public interface UserRepository extends JpaRepository<User, Long> { Optional<User> findByEmail(String email); List<User> findByStatus(UserStatus status); @Query("SELECT u FROM User u WHERE u.name LIKE %:searchTerm% OR u.email LIKE %:searchTerm%") Page<User> searchUsers(@Param("searchTerm") String searchTerm, Pageable pageable); boolean existsByEmail(String email); @Modifying @Query("UPDATE User u SET u.status = :status WHERE u.id = :id") int updateUserStatus(@Param("id") Long id, @Param("status") UserStatus status); }
Key Features:
- Extends JpaRepository for CRUD operations
- Custom query methods (findByEmail, findByStatus)
- @Query for complex queries
- Pagination support with Pageable
- @Modifying for update/delete queries
Step 5: Create Service Layer
Implement business logic with transactions:
@Service @RequiredArgsConstructor @Slf4j public class UserService { private final UserRepository userRepository; private final UserMapper userMapper; private final ApplicationEventPublisher eventPublisher; @Transactional(readOnly = true) public UserResponse getUserById(Long id) { log.debug("Fetching user with id: {}", id); User user = userRepository.findById(id) .orElseThrow(() -> new UserNotFoundException(id)); return userMapper.toResponse(user); } @Transactional(readOnly = true) public Page<UserResponse> getAllUsers(Pageable pageable) { log.debug("Fetching all users: page={}, size={}", pageable.getPageNumber(), pageable.getPageSize()); return userRepository.findAll(pageable) .map(userMapper::toResponse); } @Transactional public UserResponse createUser(CreateUserRequest request) { log.info("Creating user: email={}", request.getEmail()); if (userRepository.existsByEmail(request.getEmail())) { throw new DuplicateEmailException(request.getEmail()); } User user = userMapper.toEntity(request); User savedUser = userRepository.save(user); eventPublisher.publishEvent(new UserCreatedEvent(savedUser)); log.info("User created successfully: id={}", savedUser.getId()); return userMapper.toResponse(savedUser); } @Transactional public UserResponse updateUser(Long id, UpdateUserRequest request) { log.info("Updating user: id={}", id); User user = userRepository.findById(id) .orElseThrow(() -> new UserNotFoundException(id)); updateUserFields(user, request); User updatedUser = userRepository.save(user); log.info("User updated successfully: id={}", id); return userMapper.toResponse(updatedUser); } @Transactional public void deleteUser(Long id) { log.info("Deleting user: id={}", id); if (!userRepository.existsById(id)) { throw new UserNotFoundException(id); } userRepository.deleteById(id); log.info("User deleted successfully: id={}", id); } private void updateUserFields(User user, UpdateUserRequest request) { if (request.getName() != null) { user.setName(request.getName()); } if (request.getPhoneNumber() != null) { user.setPhoneNumber(request.getPhoneNumber()); } } }
Best Practices:
- @Transactional for database operations
- readOnly = true for read operations (performance)
- Comprehensive logging with SLF4J
- Event publishing for decoupled architecture
- Proper exception handling
- Field-by-field updates for PATCH operations
Step 6: Create Controller Layer
Expose REST API endpoints:
@RestController @RequestMapping("/api/v1/users") @RequiredArgsConstructor @Validated @Tag(name = "User Management", description = "APIs for managing users") public class UserController { private final UserService userService; @GetMapping("/{id}") @Operation(summary = "Get user by ID", description = "Retrieves a user by their unique identifier") @ApiResponses({ @ApiResponse(responseCode = "200", description = "User found"), @ApiResponse(responseCode = "404", description = "User not found") }) public ResponseEntity<UserResponse> getUser( @PathVariable @Positive Long id) { UserResponse user = userService.getUserById(id); return ResponseEntity.ok(user); } @GetMapping @Operation(summary = "Get all users", description = "Retrieves a paginated list of users") public ResponseEntity<Page<UserResponse>> getAllUsers( @RequestParam(defaultValue = "0") @Min(0) int page, @RequestParam(defaultValue = "20") @Min(1) @Max(100) int size, @RequestParam(defaultValue = "id") String sortBy, @RequestParam(defaultValue = "ASC") Sort.Direction direction) { Pageable pageable = PageRequest.of(page, size, Sort.by(direction, sortBy)); Page<UserResponse> users = userService.getAllUsers(pageable); return ResponseEntity.ok(users); } @PostMapping @Operation(summary = "Create user", description = "Creates a new user") @ApiResponses({ @ApiResponse(responseCode = "201", description = "User created"), @ApiResponse(responseCode = "400", description = "Invalid request"), @ApiResponse(responseCode = "409", description = "Email already exists") }) public ResponseEntity<UserResponse> createUser( @Valid @RequestBody CreateUserRequest request) { UserResponse createdUser = userService.createUser(request); URI location = ServletUriComponentsBuilder .fromCurrentRequest() .path("/{id}") .buildAndExpand(createdUser.getId()) .toUri(); return ResponseEntity.created(location).body(createdUser); } @PutMapping("/{id}") @Operation(summary = "Update user", description = "Updates an existing user") public ResponseEntity<UserResponse> updateUser( @PathVariable @Positive Long id, @Valid @RequestBody UpdateUserRequest request) { UserResponse updatedUser = userService.updateUser(id, request); return ResponseEntity.ok(updatedUser); } @DeleteMapping("/{id}") @Operation(summary = "Delete user", description = "Deletes a user by ID") @ApiResponse(responseCode = "204", description = "User deleted") public ResponseEntity<Void> deleteUser(@PathVariable @Positive Long id) { userService.deleteUser(id); return ResponseEntity.noContent().build(); } }
Key Features:
- @RestController for REST endpoints
- @RequestMapping for base path and versioning
- OpenAPI annotations (@Operation, @ApiResponse)
- Proper HTTP status codes (200, 201, 204, 404, etc.)
- Request validation (@Valid, @Positive, @Min, @Max)
- Pagination and sorting support
- Location header for created resources
Step 7: Create Global Exception Handler
Centralized exception handling:
@RestControllerAdvice @Slf4j public class GlobalExceptionHandler { @ExceptionHandler(UserNotFoundException.class) public ResponseEntity<ErrorResponse> handleUserNotFound( UserNotFoundException ex, WebRequest request) { log.error("User not found: {}", ex.getMessage()); ErrorResponse error = ErrorResponse.builder() .timestamp(LocalDateTime.now()) .status(HttpStatus.NOT_FOUND.value()) .error("Not Found") .message(ex.getMessage()) .path(getRequestPath(request)) .build(); return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error); } @ExceptionHandler(DuplicateEmailException.class) public ResponseEntity<ErrorResponse> handleDuplicateEmail( DuplicateEmailException ex, WebRequest request) { log.error("Duplicate email: {}", ex.getMessage()); ErrorResponse error = ErrorResponse.builder() .timestamp(LocalDateTime.now()) .status(HttpStatus.CONFLICT.value()) .error("Conflict") .message(ex.getMessage()) .path(getRequestPath(request)) .build(); return ResponseEntity.status(HttpStatus.CONFLICT).body(error); } @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<ValidationErrorResponse> handleValidationErrors( MethodArgumentNotValidException ex, WebRequest request) { log.error("Validation failed: {}", ex.getMessage()); Map<String, String> errors = ex.getBindingResult() .getFieldErrors() .stream() .collect(Collectors.toMap( FieldError::getField, error -> error.getDefaultMessage() != null ? error.getDefaultMessage() : "Invalid value" )); ValidationErrorResponse response = ValidationErrorResponse.builder() .timestamp(LocalDateTime.now()) .status(HttpStatus.BAD_REQUEST.value()) .error("Validation Failed") .message("Request validation failed") .path(getRequestPath(request)) .errors(errors) .build(); return ResponseEntity.badRequest().body(response); } @ExceptionHandler(Exception.class) public ResponseEntity<ErrorResponse> handleGenericException( Exception ex, WebRequest request) { log.error("Unexpected error occurred", ex); ErrorResponse error = ErrorResponse.builder() .timestamp(LocalDateTime.now()) .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) .error("Internal Server Error") .message("An unexpected error occurred") .path(getRequestPath(request)) .build(); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(error); } private String getRequestPath(WebRequest request) { return ((ServletWebRequest) request).getRequest().getRequestURI(); } } @Data @Builder public class ErrorResponse { private LocalDateTime timestamp; private int status; private String error; private String message; private String path; } @Data @Builder public class ValidationErrorResponse extends ErrorResponse { private Map<String, String> errors; }
Step 8: Create Custom Exceptions
Domain-specific exceptions:
public class UserNotFoundException extends RuntimeException { public UserNotFoundException(Long id) { super("User not found with ID: " + id); } } public class DuplicateEmailException extends RuntimeException { public DuplicateEmailException(String email) { super("User already exists with email: " + email); } }
Step 9: Add Configuration
Application configuration files:
application.yml:
spring: application: name: user-service datasource: url: ${DATABASE_URL:jdbc:postgresql://localhost:5432/userdb} username: ${DATABASE_USERNAME:postgres} password: ${DATABASE_PASSWORD:password} driver-class-name: org.postgresql.Driver jpa: hibernate: ddl-auto: validate show-sql: false properties: hibernate: format_sql: true dialect: org.hibernate.dialect.PostgreSQLDialect jdbc: batch_size: 20 order_inserts: true order_updates: true flyway: enabled: true locations: classpath:db/migration baseline-on-migrate: true server: port: ${PORT:8080} error: include-message: always include-binding-errors: always logging: level: root: INFO com.example.userservice: DEBUG pattern: console: "%d{yyyy-MM-dd HH:mm:ss} - %msg%n" springdoc: api-docs: path: /api-docs swagger-ui: path: /swagger-ui.html enabled: true
Java Config:
@Configuration @EnableJpaAuditing public class JpaConfig { // JPA auditing configuration } @Configuration public class OpenApiConfig { @Bean public OpenAPI customOpenAPI() { return new OpenAPI() .info(new Info() .title("User Service API") .version("1.0") .description("API for managing users") .contact(new Contact() .name("API Support") .email("support@example.com"))) .servers(List.of( new Server().url("http://localhost:8080") .description("Development server") )); } }
Step 10: Add Database Migration
Flyway migration script:
db/migration/V1__create_users_table.sql:
CREATE TABLE users ( id BIGSERIAL PRIMARY KEY, name VARCHAR(100) NOT NULL, email VARCHAR(255) NOT NULL UNIQUE, phone_number VARCHAR(20), status VARCHAR(20) NOT NULL, created_at TIMESTAMP NOT NULL, updated_at TIMESTAMP NOT NULL, version BIGINT NOT NULL DEFAULT 0 ); CREATE INDEX idx_users_email ON users(email); CREATE INDEX idx_users_status ON users(status);
Examples
Example 1: Complete CRUD Service
Generated Structure:
src/main/java/com/example/userservice/ ├── UserServiceApplication.java ├── config/ │ ├── JpaConfig.java │ └── OpenApiConfig.java ├── controller/ │ └── UserController.java ├── dto/ │ ├── CreateUserRequest.java │ ├── UpdateUserRequest.java │ └── UserResponse.java ├── entity/ │ └── User.java ├── exception/ │ ├── DuplicateEmailException.java │ ├── UserNotFoundException.java │ └── GlobalExceptionHandler.java ├── mapper/ │ └── UserMapper.java ├── repository/ │ └── UserRepository.java └── service/ └── UserService.java src/main/resources/ ├── application.yml └── db/migration/ └── V1__create_users_table.sql
Example 2: Custom Service with Business Logic
Requirement: Order processing service with inventory validation
Generated OrderService:
@Service @RequiredArgsConstructor @Slf4j public class OrderService { private final OrderRepository orderRepository; private final InventoryService inventoryService; private final PaymentService paymentService; private final NotificationService notificationService; private final OrderMapper orderMapper; @Transactional public OrderResponse processOrder(CreateOrderRequest request) { log.info("Processing new order for customer: {}", request.getCustomerId()); // Validate inventory validateInventoryAvailability(request.getItems()); // Create order Order order = orderMapper.toEntity(request); order.setStatus(OrderStatus.PENDING); // Calculate total BigDecimal total = calculateOrderTotal(order); order.setTotal(total); // Save order Order savedOrder = orderRepository.save(order); // Process payment try { paymentService.processPayment( savedOrder.getId(), total, request.getPaymentDetails() ); savedOrder.setStatus(OrderStatus.CONFIRMED); } catch (PaymentException e) { log.error("Payment failed for order: {}", savedOrder.getId(), e); savedOrder.setStatus(OrderStatus.PAYMENT_FAILED); throw e; } // Update inventory inventoryService.reserveItems(request.getItems()); // Send confirmation notificationService.sendOrderConfirmation(savedOrder); log.info("Order processed successfully: orderId={}", savedOrder.getId()); return orderMapper.toResponse(savedOrder); } private void validateInventoryAvailability( List<OrderItemRequest> items) { items.forEach(item -> { if (!inventoryService.isAvailable( item.getProductId(), item.getQuantity())) { throw new InsufficientInventoryException( item.getProductId()); } }); } private BigDecimal calculateOrderTotal(Order order) { return order.getItems().stream() .map(item -> item.getPrice() .multiply(BigDecimal.valueOf(item.getQuantity()))) .reduce(BigDecimal.ZERO, BigDecimal::add); } }
Requirements
Dependencies (Maven pom.xml)
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.2.0</version> </parent> <dependencies> <!-- Spring Boot Starters --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> <!-- Database --> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.flywaydb</groupId> <artifactId>flyway-core</artifactId> </dependency> <!-- OpenAPI/Swagger --> <dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> <version>2.3.0</version> </dependency> <!-- Lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <scope>provided</scope> </dependency> <!-- Testing --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
Running the Service
# Build mvn clean package # Run java -jar target/user-service-1.0.0.jar # Or with Maven mvn spring-boot:run # Access Swagger UI open http://localhost:8080/swagger-ui.html # Access API docs curl http://localhost:8080/api-docs
Best Practices Checklist
Architecture:
- Clear layer separation (Controller/Service/Repository)
- DTOs for API contracts (no entities in controllers)
- Dependency injection via constructor
- Single responsibility per class
API Design:
- RESTful endpoints with proper HTTP methods
- Correct HTTP status codes (200, 201, 204, 400, 404, etc.)
- API versioning (/api/v1/)
- Pagination for list endpoints
- Location header for POST requests
Data Access:
- @Transactional on service methods
- readOnly = true for read operations
- Proper exception handling (no empty catches)
- Database migrations with Flyway/Liquibase
Validation:
- Bean validation annotations (@NotNull, @Size, etc.)
- @Valid on request parameters
- Custom validators for complex rules
- Meaningful validation error messages
Documentation:
- OpenAPI/Swagger annotations
- API descriptions and examples
- README with setup instructions
- Code comments for complex logic
Error Handling:
- @RestControllerAdvice for global handling
- Custom exceptions for domain errors
- Consistent error response format
- Proper logging of errors
Logging:
- SLF4J with appropriate levels
- Structured logging (JSON in production)
- Log method entry/exit for important operations
- Log with context (IDs, user info)
Security:
- Input validation and sanitization
- No sensitive data in logs
- HTTPS in production
- Authentication/authorization if needed
Output Format
When generating a Spring Boot service, provide:
- Project structure with all files
- Complete code for each layer
- Configuration files (application.yml, pom.xml)
- Database migrations (Flyway scripts)
- Setup instructions for running the service
- API documentation with example curl commands
Example output:
## Generated Spring Boot Service: User Management **Structure Created:** - Entity: User - Repository: UserRepository - Service: UserService - Controller: UserController - DTOs: CreateUserRequest, UpdateUserRequest, UserResponse - Exceptions: UserNotFoundException, DuplicateEmailException - Global Exception Handler - Configuration (JPA, OpenAPI) - Database migration **Endpoints:** - GET /api/v1/users - List all users - GET /api/v1/users/{id} - Get user by ID - POST /api/v1/users - Create user - PUT /api/v1/users/{id} - Update user - DELETE /api/v1/users/{id} - Delete user **Setup:** 1. Configure database in application.yml 2. Run: mvn spring-boot:run 3. Access Swagger UI: http://localhost:8080/swagger-ui.html **Example Request:** ```bash curl -X POST http://localhost:8080/api/v1/users \ -H "Content-Type: application/json" \ -d '{"name":"John Doe","email":"john@example.com"}'
## Error Handling If service generation cannot be completed: 1. **Missing requirements:** Ask for entity details, fields, relationships 2. **Ambiguous business logic:** Request clarification on rules and validations 3. **Database type unclear:** Ask which database (PostgreSQL, MySQL, etc.) 4. **Spring version conflict:** Verify Spring Boot version requirements Always provide complete, working code that follows Spring Boot best practices.