kumaran-is

java-spring-api

0
0
# Install this skill:
npx skills add kumaran-is/claude-code-onboarding --skill "java-spring-api"

Install specific skill from multi-skill repository

# Description

Patterns and templates for Java 21 Spring Boot 3.4 WebFlux REST API development. Activate when creating controllers, services, repositories, DTOs, or tests in a Spring Boot project.

# SKILL.md


name: java-spring-api
description: Patterns and templates for Java 21 Spring Boot 3.4 WebFlux REST API development. Activate when creating controllers, services, repositories, DTOs, or tests in a Spring Boot project.
allowed-tools: Bash, Read, Write, Edit


Java 21 + Spring Boot 3.4 WebFlux REST API Skill

Quick Scaffold β€” New Spring Boot Project

# Using Spring Initializr via curl
curl https://start.spring.io/starter.zip \
  -d type=gradle-project \
  -d language=java \
  -d javaVersion=21 \
  -d bootVersion=3.5.0 \
  -d dependencies=webflux,r2dbc,postgresql,flyway,validation,actuator,lombok \
  -d groupId=com.company \
  -d artifactId=my-service \
  -d name=my-service \
  -o my-service.zip && unzip my-service.zip -d my-service

build.gradle.kts Essentials

plugins {
    java
    id("org.springframework.boot") version "3.5.0"
    id("io.spring.dependency-management") version "1.1.6"
}

java { sourceCompatibility = JavaVersion.VERSION_21 }

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-webflux")
    implementation("org.springframework.boot:spring-boot-starter-data-r2dbc")
    implementation("org.springframework.boot:spring-boot-starter-validation")
    implementation("org.springframework.boot:spring-boot-starter-actuator")
    runtimeOnly("org.postgresql:r2dbc-postgresql")
    runtimeOnly("org.postgresql:postgresql")  // for Flyway
    implementation("org.flywaydb:flyway-core")
    implementation("org.flywaydb:flyway-database-postgresql")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
    testImplementation("io.projectreactor:reactor-test")
}

File Templates

DTO (record)

public record CreateUserRequest(
    @NotBlank String email,
    @NotBlank @Size(max = 100) String firstName,
    @NotBlank @Size(max = 100) String lastName
) {}

public record UserResponse(
    UUID id, String email, String firstName, String lastName, Instant createdAt
) {}

R2DBC Entity

@Table("users")
public class UserEntity {
    @Id private UUID id;
    private String email;
    private String firstName;
    private String lastName;
    private Instant createdAt;
    private Instant updatedAt;
    // getters, setters, builder
}

Repository

public interface UserRepository extends ReactiveCrudRepository<UserEntity, UUID> {
    Mono<UserEntity> findByEmail(String email);
    Flux<UserEntity> findByLastName(String lastName);
}

Service

@Service
@RequiredArgsConstructor
public class UserService {
    private final UserRepository userRepository;

    public Mono<UserResponse> findById(UUID id) {
        return userRepository.findById(id)
                .map(this::toResponse)
                .switchIfEmpty(Mono.error(new NotFoundException("User not found")));
    }

    public Mono<UserResponse> create(CreateUserRequest request) {
        var entity = new UserEntity();
        entity.setEmail(request.email());
        entity.setFirstName(request.firstName());
        entity.setLastName(request.lastName());
        entity.setCreatedAt(Instant.now());
        entity.setUpdatedAt(Instant.now());
        return userRepository.save(entity).map(this::toResponse);
    }

    private UserResponse toResponse(UserEntity e) {
        return new UserResponse(e.getId(), e.getEmail(), e.getFirstName(), e.getLastName(), e.getCreatedAt());
    }
}

Controller

@RestController
@RequestMapping("/api/v1/users")
@RequiredArgsConstructor
public class UserController {
    private final UserService userService;

    @GetMapping("/{id}")
    public Mono<ResponseEntity<UserResponse>> getById(@PathVariable UUID id) {
        return userService.findById(id)
                .map(ResponseEntity::ok)
                .switchIfEmpty(Mono.just(ResponseEntity.notFound().build()));
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public Mono<UserResponse> create(@Valid @RequestBody CreateUserRequest request) {
        return userService.create(request);
    }

    @GetMapping
    public Flux<UserResponse> getAll() {
        return userService.findAll();
    }
}

Test

@SpringBootTest
@AutoConfigureWebTestClient
class UserControllerTest {

    @Autowired WebTestClient webTestClient;

    @Test
    void createUser_shouldReturn201() {
        var request = new CreateUserRequest("[email protected]", "John", "Doe");

        webTestClient.post().uri("/api/v1/users")
                .contentType(MediaType.APPLICATION_JSON)
                .bodyValue(request)
                .exchange()
                .expectStatus().isCreated()
                .expectBody()
                .jsonPath("$.email").isEqualTo("[email protected]");
    }
}

application.yml Template

spring:
  r2dbc:
    url: r2dbc:postgresql://localhost:5432/mydb
    username: ${DB_USERNAME:postgres}
    password: ${DB_PASSWORD:postgres}
  flyway:
    url: jdbc:postgresql://localhost:5432/mydb
    user: ${DB_USERNAME:postgres}
    password: ${DB_PASSWORD:postgres}
  webflux:
    base-path: /

server:
  port: 8080

management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics

# Supported AI Coding Agents

This skill is compatible with the SKILL.md standard and works with all major AI coding agents:

Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.