ในยุคที่การพัฒนาแอปพลิเคชันเปลี่ยนไปสู่แนวทาง Reactive มากขึ้น Spring WebFlux กลายเป็นตัวเลือกสำคัญของนักพัฒนา Java ที่ต้องการรองรับการประมวลผลแบบ Asynchronous และ Non-Blocking ซึ่งมีประโยชน์มากเมื่อรับโหลดจำนวนมาก
บทความนี้จะพาคุณไปเรียนรู้การจัดการข้อผิดพลาด (Error Handling) ใน Spring WebFlux โดยใช้แนวทางแบบ Functional ที่ใช้ร่วมกับ RouterFunction และ HandlerFunction ซึ่งต่างจากแนว MVC แบบเดิม
🔍 ทำไมต้องจัดการ Error แบบ Functional?
- เหมาะสำหรับ WebFlux Routing ที่ใช้ RouterFunction
- รองรับ Mono และ Flux ได้อย่างสมบูรณ์
- แยกการจัดการ Error ออกจาก logic หลัก
- รองรับรูปแบบ JSON Response ที่กำหนดเอง
🧱 โครงสร้าง Router และ Handler
@Configuration public class RouterConfig { @Bean public RouterFunction<ServerResponse> route(MyHandler handler) { return RouterFunctions.route() .GET("/api/hello", handler::hello) .GET("/api/user/{id}", handler::getUser) .build(); } }
@Component public class MyHandler { public Mono<ServerResponse> hello(ServerRequest request) { return ServerResponse.ok().bodyValue("Hello WebFlux!"); } public Mono<ServerResponse> getUser(ServerRequest request) { String id = request.pathVariable("id"); if (id.equals("0")) { throw new NotFoundException("User ID not found"); } return ServerResponse.ok().bodyValue("User ID: " + id); } }
🛑 สร้าง Custom Exception
public class NotFoundException extends RuntimeException { public NotFoundException(String message) { super(message); } }
⚙️ สร้าง Global Error Handler
@Component @Order(-2) public class GlobalErrorHandler extends AbstractErrorWebExceptionHandler { public GlobalErrorHandler(ErrorAttributes errorAttributes, ApplicationContext applicationContext, ServerCodecConfigurer configurer) { super(errorAttributes, new WebProperties.Resources(), applicationContext); super.setMessageReaders(configurer.getReaders()); super.setMessageWriters(configurer.getWriters()); } @Override protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) { return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse); } private Mono<ServerResponse> renderErrorResponse(ServerRequest request) { Map<String, Object> errorProps = getErrorAttributes(request, ErrorAttributeOptions.defaults()); return ServerResponse .status((int) errorProps.getOrDefault("status", 500)) .contentType(MediaType.APPLICATION_JSON) .bodyValue(Map.of( "error", errorProps.get("error"), "message", errorProps.get("message"), "path", errorProps.get("path"), "status", errorProps.get("status") )); } }
✨ Custom ErrorAttributes (Optional)
@Component public class CustomErrorAttributes extends DefaultErrorAttributes { @Override public Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) { Throwable error = getError(request); return Map.of( "message", error.getMessage(), "error", error.getClass().getSimpleName(), "timestamp", Instant.now().toString(), "status", 500 ); } }
💬 ตัวอย่าง Response เมื่อเกิด Error
{ "error": "NotFoundException", "message": "User ID not found", "status": 500, "path": "/api/user/0" }
🌐 ใช้ร่วมกับ WebClient
webClient.get() .uri("/external-api") .retrieve() .onStatus(HttpStatus::is4xxClientError, resp -> Mono.error(new RuntimeException("Client error"))) .onStatus(HttpStatus::is5xxServerError, resp -> Mono.error(new RuntimeException("Server error"))) .bodyToMono(String.class);
📊 สรุปแนวทาง
- แยก Logic Error ออกจาก Handler อย่างชัดเจน
- ปรับแต่งรูปแบบ Error Response ได้ตามต้องการ
- ไม่ต้องพึ่ง @ControllerAdvice แบบใน MVC
- ทำงานร่วมกับ Mono/Flux ได้ลื่นไหล
📸 ภาพประกอบแนะนำ
- WebFlux Error Lifecycle
- GlobalErrorHandler Diagram
- Custom Error Response Flow

🔎 คำค้น SEO
Spring WebFlux Error Handling, Functional Router WebFlux, GlobalErrorWebExceptionHandler, Spring Boot Reactive Exception, Spring ErrorAttributes Custom, JSON error response Spring WebFlux, Spring WebFlux Mono Flux Error, Functional Exception Flow