spring-webflux
Spring WebFlux for reactive programming in Spring Boot 3.x. Covers Mono/Flux, reactive operators, WebClient, functional endpoints, R2DBC integration, reactive error handling, and testing. Use for non-blocking high-concurrency apps. USE WHEN: user mentions "spring webflux", "reactive Spring", "Mono", "Flux", "WebClient reactive", "functional endpoints", "R2DBC", "non-blocking", "StepVerifier", "reactive streams" DO NOT USE FOR: traditional blocking MVC - use `spring-web` skill, simple REST APIs - use `spring-rest` skill, batch processing - use `spring-batch` skill
What this skill does
# Spring WebFlux
> **Full Reference**: See [advanced.md](advanced.md) for SSE with Sinks, Testing with StepVerifier, and Context Propagation patterns.
## Quick Start
```java
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@GetMapping("/{id}")
public Mono<UserResponse> getUser(@PathVariable Long id) {
return userService.findById(id);
}
@GetMapping
public Flux<UserResponse> getAllUsers() {
return userService.findAll();
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Mono<UserResponse> createUser(@RequestBody Mono<CreateUserRequest> request) {
return request.flatMap(userService::create);
}
}
```
---
## Mono & Flux Basics
### Mono (0 or 1 element)
```java
Mono<String> empty = Mono.empty();
Mono<String> just = Mono.just("Hello");
Mono<String> fromCallable = Mono.fromCallable(() -> expensiveOperation());
Mono<String> defer = Mono.defer(() -> Mono.just(dynamicValue()));
Mono<String> fromOptional = Mono.justOrEmpty(optionalValue);
Mono<String> fromFuture = Mono.fromFuture(completableFuture);
```
### Flux (0 to N elements)
```java
Flux<Integer> just = Flux.just(1, 2, 3);
Flux<Integer> fromIterable = Flux.fromIterable(List.of(1, 2, 3));
Flux<Integer> range = Flux.range(1, 10);
Flux<Long> interval = Flux.interval(Duration.ofSeconds(1));
Flux<Integer> generate = Flux.generate(
() -> 0,
(state, sink) -> {
sink.next(state);
if (state == 10) sink.complete();
return state + 1;
}
);
```
---
## Reactive Operators
### Transformation
```java
// map - transform each element
users.map(user -> new UserResponse(user.getId(), user.getName()));
// flatMap - async transformation (parallel)
users.flatMap(user -> orderRepository.findByUserId(user.getId()));
// flatMapSequential - maintains order
users.flatMapSequential(user -> orderRepository.findByUserId(user.getId()));
// concatMap - sequential, one at a time
users.concatMap(user -> orderRepository.findByUserId(user.getId()));
// switchMap - cancels previous when new arrives
searchTerms.switchMap(term -> searchService.search(term));
```
### Filtering
```java
// filter
users.filter(user -> user.getStatus() == Status.ACTIVE);
// filterWhen - async filter
users.filterWhen(user -> permissionService.canAccess(user.getId()));
// distinct / distinctUntilChanged
items.distinct();
values.distinctUntilChanged();
// take / skip
users.skip((long) page * size).take(size);
```
### Combining
```java
// zip - combine by position
Flux.zip(users, orders, UserWithOrders::new);
// merge - interleave from multiple sources
Flux.merge(source1, source2);
// concat - sequential
Flux.concat(first, second);
// zipWith on Mono
userRepository.findById(userId)
.zipWith(profileRepository.findByUserId(userId))
.map(tuple -> new UserWithProfile(tuple.getT1(), tuple.getT2()));
```
### Error Handling
```java
// onErrorReturn - default value on error
userRepository.findById(id).onErrorReturn(new User("default"));
// onErrorResume - fallback Publisher
primaryRepository.findById(id)
.onErrorResume(e -> fallbackRepository.findById(id));
// onErrorResume with specific type
userRepository.findById(id)
.onErrorResume(NotFoundException.class, e -> Mono.empty())
.onErrorResume(TimeoutException.class, e -> cacheRepository.findById(id));
// onErrorMap - transform exception
userRepository.findById(id)
.onErrorMap(DataAccessException.class,
e -> new ServiceException("Database error", e));
// retryWhen - advanced retry
userRepository.findById(id)
.retryWhen(Retry.backoff(3, Duration.ofSeconds(1))
.filter(e -> e instanceof TransientException));
```
### Side Effects
```java
userRepository.findById(id)
.doOnSubscribe(s -> log.info("Subscribed"))
.doOnNext(user -> log.info("Found user: {}", user.getId()))
.doOnError(e -> log.error("Error: {}", e.getMessage()))
.doFinally(signalType -> log.info("Finally: {}", signalType));
```
---
## WebClient
### Configuration
```java
@Configuration
public class WebClientConfig {
@Bean
public WebClient webClient(WebClient.Builder builder) {
HttpClient httpClient = HttpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
.responseTimeout(Duration.ofSeconds(30))
.doOnConnected(conn -> conn
.addHandlerLast(new ReadTimeoutHandler(30, TimeUnit.SECONDS))
.addHandlerLast(new WriteTimeoutHandler(10, TimeUnit.SECONDS)));
return builder
.baseUrl("https://api.example.com")
.clientConnector(new ReactorClientHttpConnector(httpClient))
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.build();
}
}
```
### Usage
```java
@Service
@RequiredArgsConstructor
public class ExternalApiService {
private final WebClient webClient;
public Mono<UserDto> getUser(Long id) {
return webClient.get()
.uri("/users/{id}", id)
.retrieve()
.bodyToMono(UserDto.class);
}
public Mono<UserDto> getUserSafe(Long id) {
return webClient.get()
.uri("/users/{id}", id)
.retrieve()
.onStatus(HttpStatusCode::is4xxClientError, response ->
response.bodyToMono(ErrorResponse.class)
.flatMap(error -> Mono.error(new ClientException(error.getMessage()))))
.onStatus(HttpStatusCode::is5xxServerError, response ->
Mono.error(new ServerException("Server error")))
.bodyToMono(UserDto.class);
}
// Parallel calls
public Mono<AggregatedData> getAggregatedData(Long userId) {
return Mono.zip(
getUser(userId),
getOrders(userId).collectList(),
getProfile(userId)
).map(tuple -> new AggregatedData(tuple.getT1(), tuple.getT2(), tuple.getT3()));
}
}
```
---
## Functional Endpoints
```java
@Configuration
public class RouterConfig {
@Bean
public RouterFunction<ServerResponse> userRoutes(UserHandler handler) {
return RouterFunctions.route()
.path("/api/users", builder -> builder
.GET("", handler::getAll)
.GET("/{id}", handler::getById)
.POST("", handler::create)
.PUT("/{id}", handler::update)
.DELETE("/{id}", handler::delete)
)
.build();
}
}
@Component
@RequiredArgsConstructor
public class UserHandler {
private final UserService userService;
public Mono<ServerResponse> getById(ServerRequest request) {
Long id = Long.parseLong(request.pathVariable("id"));
return userService.findById(id)
.flatMap(user -> ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(user))
.switchIfEmpty(ServerResponse.notFound().build());
}
public Mono<ServerResponse> create(ServerRequest request) {
return request.bodyToMono(CreateUserRequest.class)
.flatMap(userService::create)
.flatMap(user -> ServerResponse.created(
URI.create("/api/users/" + user.getId()))
.bodyValue(user));
}
}
```
---
## Best Practices
| Do | Don't |
|----|-------|
| Keep chain fully reactive | Use .block() in handlers |
| Use appropriate operators (flatMap vs concatMap) | Mix blocking and reactive |
| Handle errors with onError* operators | Ignore errors |
| Use StepVerifier for testing | Test with .block() |
| Propagate Context for MDC/security | Use ThreadLocal |
## Production Checklist
- [ ] Timeout configured on WebClient
- [ ] Error handling complete
- [ ] Retry logic for transient errors
- [ ] Backpressure strategy defined
- [ ] Context propagation for logging
- [ ] Reactive metrics
- [ ] Test with StepVerifiRelated in General
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.