💻 Développement

dev-java-spring-advisor

Développement Java avec Spring Boot, Spring Security et l'écosystème Spring.

⚡ Installation & lancement en 1 commande

Copiez-collez dans votre terminal : le skill s'installe dans ~/.claude/skills et Claude Code se lance directement dessus.

macOS / Linux
curl -fsSL https://raw.githubusercontent.com/khalilbenaz/claude-skills-collection/main/install.sh | sh -s -- dev-java-spring-advisor --launch
Windows (PowerShell)
iex "& { $(iwr -useb https://raw.githubusercontent.com/khalilbenaz/claude-skills-collection/main/install.ps1) } dev-java-spring-advisor -Launch"

🚀 Déjà installé ?

claude "/dev-java-spring-advisor"

Ou tapez /dev-java-spring-advisor dans une session Claude Code, ou décrivez simplement votre besoin — le skill se déclenche automatiquement via le skill-router.

🔑 Déclencheurs automatiques

Le skill s'active automatiquement quand votre demande contient :

JavaSpring BootSpringJPAHibernateMavenGradleSpring Securitymicroservices Java

📦 Installation manuelle

git clone https://github.com/khalilbenaz/claude-skills-collection.git cp -r claude-skills-collection/skills/dev-java-spring-advisor ~/.claude/skills/

Payload du plugin : skills/dev-java-spring-advisor · source éditable : dev-skills/java-spring-advisor

📖 Manuel

Java Spring Advisor

Workflow

1. Initialisation du projet

# Spring Initializr CLI (Spring Boot 3.x, Java 21)
curl https://start.spring.io/starter.zip \
  -d type=maven-project \
  -d language=java \
  -d bootVersion=3.3.0 \
  -d groupId=com.example \
  -d artifactId=myapp \
  -d javaVersion=21 \
  -d dependencies=web,data-jpa,security,actuator,validation,flyway \
  -o myapp.zip && unzip myapp.zip

Structure des profils :

src/main/resources/
  application.yml          # valeurs communes
  application-dev.yml      # dev local (H2, logs DEBUG)
  application-prod.yml     # prod (connexion pool, logs INFO)

Typer la configuration avec @ConfigurationProperties plutôt que @Value :

@ConfigurationProperties(prefix = "app.payment")
public record PaymentProperties(String apiUrl, Duration timeout, int maxRetries) {}

2. Architecture — Critères de choix

ContexteApproche recommandée
API CRUD simpleCouches Controller → Service → Repository
Domaine métier richeHexagonale (ports/adapters) + DDD
Gros projet (>5 équipes)Multi-modules Maven par bounded context
Microservices1 module = 1 deployable, API contract-first OpenAPI

Organisation par feature (pas par couche technique) dans les projets moyens/grands :

com.example.payment/
  PaymentController.java
  PaymentService.java
  PaymentRepository.java
  PaymentDto.java         # record Java 21
  Payment.java            # entité JPA

3. Data access — JPA & transactions

Problème N+1 — détecter et corriger :

// Mauvais : génère N+1 requêtes
List<Order> orders = orderRepo.findAll();
orders.forEach(o -> o.getItems().size()); // lazy load en boucle

// Correct : fetch join explicite
@Query("SELECT o FROM Order o JOIN FETCH o.items WHERE o.status = :status")
List<Order> findWithItems(@Param("status") OrderStatus status);

// Ou via @EntityGraph
@EntityGraph(attributePaths = {"items", "customer"})
List<Order> findByStatus(OrderStatus status);

Règles @Transactional :

Migration de schéma avec Flyway :

db/migration/
  V1__create_orders.sql
  V2__add_payment_status.sql

4. REST API

Handler d'erreur global :

@RestControllerAdvice
public class GlobalExceptionHandler {

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

    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ProblemDetail handleValidation(MethodArgumentNotValidException ex) {
        var pd = ProblemDetail.forStatus(HttpStatus.BAD_REQUEST);
        pd.setProperty("errors", ex.getBindingResult().getFieldErrors()
            .stream().map(e -> e.getField() + ": " + e.getDefaultMessage()).toList());
        return pd;
    }
}

DTO avec record Java 21 + validation :

public record CreateOrderRequest(
    @NotBlank String customerId,
    @NotEmpty List<@Valid OrderItemRequest> items,
    @PositiveOrZero BigDecimal discount
) {}

Documentation OpenAPI (SpringDoc) :

# application.yml
springdoc:
  api-docs.path: /api-docs
  swagger-ui.path: /swagger-ui.html
  show-actuator: false

5. Spring Security (Spring Boot 3.x)

Configuration minimale JWT Resource Server :

@Configuration
@EnableMethodSecurity
public class SecurityConfig {

    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
            .csrf(AbstractHttpConfigurer::disable)
            .sessionManagement(s -> s.sessionCreationPolicy(STATELESS))
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/actuator/health").permitAll()
                .anyRequest().authenticated())
            .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
            .build();
    }
}

Sécurisation par méthode :

@PreAuthorize("hasRole('ADMIN') or #userId == authentication.name")
public UserDto getUser(String userId) { ... }

CORS : configurer via CorsConfigurationSource bean, pas via @CrossOrigin sur chaque contrôleur.

6. Messaging

RabbitMQ — pattern listener avec retry :

@RabbitListener(queues = "payment.queue",
    containerFactory = "retryContainerFactory")
public void handlePayment(PaymentEvent event) {
    paymentService.process(event);
}

Kafka — configuration producer idempotent :

spring.kafka.producer:
  enable-idempotence: true
  acks: all
  retries: 3

Dead-letter queue : configurer x-dead-letter-exchange sur la queue principale + consumer séparé sur la DLQ pour replay manuel.

7. Testing — Pyramide

E2E (WireMock + Testcontainers)  ← peu nombreux, lents
Integration (@SpringBootTest)     ← couverture des flux critiques
Slice (@WebMvcTest, @DataJpaTest) ← rapides, ciblés
Unit (JUnit 5 + Mockito)          ← majoritaires

Test slice REST :

@WebMvcTest(OrderController.class)
class OrderControllerTest {

    @Autowired MockMvc mvc;
    @MockBean OrderService orderService;

    @Test
    void createOrder_returns201() throws Exception {
        given(orderService.create(any())).willReturn(new OrderDto("123"));
        mvc.perform(post("/orders")
                .contentType(APPLICATION_JSON)
                .content("""{"customerId":"C1","items":[]}"""))
            .andExpect(status().isCreated())
            .andExpect(jsonPath("$.id").value("123"));
    }
}

Testcontainers (PostgreSQL) :

@Testcontainers
@SpringBootTest
class OrderRepositoryIT {

    @Container
    static PostgreSQLContainer<?> pg = new PostgreSQLContainer<>("postgres:16");

    @DynamicPropertySource
    static void props(DynamicPropertyRegistry r) {
        r.add("spring.datasource.url", pg::getJdbcUrl);
        r.add("spring.datasource.username", pg::getUsername);
        r.add("spring.datasource.password", pg::getPassword);
    }
}

8. Observabilité

management:
  endpoints.web.exposure.include: health,info,prometheus,metrics
  endpoint.health.show-details: when-authorized
  metrics.export.prometheus.enabled: true

Corréler logs + traces :

// Micrometer Tracing auto-injecte traceId/spanId dans MDC si Zipkin/OTLP est configuré
// Ajouter dans logback-spring.xml :
// %X{traceId} %X{spanId}

Virtual threads (Java 21 + Spring Boot 3.2+) pour améliorer le throughput I/O-bound :

spring.threads.virtual.enabled: true

Anti-patterns & pièges

PiègeConséquenceCorrection
@Autowired sur champTests unitaires impossibles sans contexte SpringInjection par constructeur (final + @RequiredArgsConstructor)
@Transactional sur méthode privateTransaction silencieusement ignorée (proxy CGLIB)Déplacer sur méthode public ou extraire dans un autre bean
Appel interne this.method() transactionnelBypass du proxy → pas de transactionInjecter le bean lui-même ou restructurer
Entité JPA exposée dans le ControllerCouplage fort, risque de lazy init exceptionToujours mapper vers DTO avant de sérialiser
FetchType.EAGER par défaut sur @OneToManyFull table join à chaque chargementGarder LAZY + fetch join explicite quand nécessaire
@SpringBootTest pour toutSuite de tests lente (contexte complet)Préférer @WebMvcTest / @DataJpaTest slices
Secrets dans application.yml commitésFuite de credentialsVariables d'environnement ou Spring Cloud Config / Vault
Ignorer readOnly = trueFlush inutile + dirty checking sur toutes les entités@Transactional(readOnly = true) sur toutes les méthodes de lecture

Bonnes pratiques 2026