or run

tessl search
Log in

Version

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
mavenpkg:maven/io.quarkus/quarkus-resteasy-reactive@3.15.x

docs

examples

advanced-patterns.mdcommon-scenarios.mdedge-cases.md
index.md
tile.json

tessl/maven-io-quarkus--quarkus-resteasy-reactive

tessl install tessl/maven-io-quarkus--quarkus-resteasy-reactive@3.15.0

A Jakarta REST implementation utilizing build time processing and Vert.x for high-performance REST endpoints with reactive capabilities in cloud-native environments.

edge-cases.mddocs/examples/

Edge Cases and Error Handling

Handling corner cases, errors, and exceptional scenarios.

Null and Empty Handling

Optional Parameters

@GET
public Uni<List<Item>> list(
    @RestQuery Optional<String> category,
    @RestQuery Optional<Integer> minPrice
) {
    // Handle missing parameters
    String cat = category.orElse("all");
    int price = minPrice.orElse(0);
    
    return itemService.findFiltered(cat, price);
}

Empty Collections

@GET
public Uni<RestResponse<List<Item>>> list() {
    return itemService.findAll()
        .onItem().transform(items -> {
            if (items.isEmpty()) {
                return RestResponse.status(200, Collections.emptyList());
            }
            return RestResponse.ok(items);
        });
}

Null Responses

@GET
@Path("/{id}")
public Uni<RestResponse<Item>> get(@RestPath Long id) {
    return itemService.findById(id)
        .onItem().ifNull().continueWith(() -> 
            RestResponse.<Item>notFound()
        )
        .onItem().ifNotNull().transform(item -> 
            RestResponse.ok(item)
        );
}

Timeout Handling

Request Timeout

@GET
@Path("/{id}")
public Uni<RestResponse<Item>> get(@RestPath Long id) {
    return itemService.findById(id)
        .ifNoItem().after(Duration.ofSeconds(5))
        .failWith(new TimeoutException("Request timeout"))
        .onItem().transform(item -> RestResponse.ok(item))
        .onFailure(TimeoutException.class)
        .recoverWithItem(RestResponse.status(504, "Gateway Timeout"));
}

Cascading Timeouts

@GET
@Path("/{id}/full")
public Uni<FullData> getFull(@RestPath Long id) {
    Uni<Item> itemUni = itemService.findById(id)
        .ifNoItem().after(Duration.ofSeconds(2)).fail();
    
    Uni<Reviews> reviewsUni = reviewService.findByItemId(id)
        .ifNoItem().after(Duration.ofSeconds(2)).fail();
    
    return Uni.combine().all().unis(itemUni, reviewsUni)
        .asTuple()
        .ifNoItem().after(Duration.ofSeconds(5)).fail()
        .onItem().transform(tuple -> 
            new FullData(tuple.getItem1(), tuple.getItem2())
        )
        .onFailure(TimeoutException.class)
        .recoverWithItem(new FullData(null, null));
}

Validation Errors

Bean Validation

public class CreateItemRequest {
    @NotBlank(message = "Name is required")
    @Size(min = 3, max = 100, message = "Name must be 3-100 characters")
    public String name;
    
    @NotNull(message = "Price is required")
    @Min(value = 0, message = "Price must be positive")
    public BigDecimal price;
}

@ServerExceptionMapper
public RestResponse<ValidationErrorResponse> handleValidation(
    ConstraintViolationException ex
) {
    List<FieldError> errors = ex.getConstraintViolations().stream()
        .map(violation -> new FieldError(
            violation.getPropertyPath().toString(),
            violation.getMessage(),
            violation.getInvalidValue()
        ))
        .toList();
    
    return RestResponse.status(400, 
        new ValidationErrorResponse("Validation failed", errors)
    );
}

Custom Validation

@POST
public Uni<RestResponse<Item>> create(CreateItemRequest request) {
    // Custom validation
    if (request.price.compareTo(BigDecimal.ZERO) < 0) {
        return Uni.createFrom().item(
            RestResponse.status(400, "Price cannot be negative")
        );
    }
    
    if (request.name.contains("invalid")) {
        return Uni.createFrom().item(
            RestResponse.status(400, "Invalid name")
        );
    }
    
    return itemService.create(request)
        .onItem().transform(item -> RestResponse.status(201, item));
}

Concurrent Modification

Optimistic Locking

@PUT
@Path("/{id}")
public Uni<RestResponse<Item>> update(
    @RestPath Long id,
    @RestHeader("If-Match") Optional<String> ifMatch,
    UpdateItemRequest request
) {
    return itemService.findById(id)
        .onItem().ifNull().failWith(new NotFoundException())
        .chain(existing -> {
            // Check version
            if (ifMatch.isPresent() && 
                !existing.getVersion().equals(ifMatch.get())) {
                return Uni.createFrom().item(
                    RestResponse.<Item>status(412, "Item was modified")
                );
            }
            
            return itemService.update(id, request)
                .onItem().transform(updated -> RestResponse.ok(updated));
        });
}

File Upload Errors

Size Validation

@POST
@Path("/upload")
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Blocking
public Response upload(@RestForm FileUpload file) {
    // Check file size
    if (file.size() > 10 * 1024 * 1024) {
        return Response.status(413)
            .entity(Map.of(
                "error", "File too large",
                "maxSize", "10MB",
                "actualSize", file.size()
            ))
            .build();
    }
    
    // Check content type
    List<String> allowedTypes = List.of("image/jpeg", "image/png", "image/gif");
    if (!allowedTypes.contains(file.contentType())) {
        return Response.status(415)
            .entity(Map.of(
                "error", "Unsupported media type",
                "allowed", allowedTypes,
                "received", file.contentType()
            ))
            .build();
    }
    
    try {
        byte[] content = Files.readAllBytes(file.uploadedFile());
        String fileId = fileService.save(file.fileName(), content);
        return Response.status(201)
            .entity(Map.of("fileId", fileId))
            .build();
    } catch (IOException e) {
        return Response.status(500)
            .entity(Map.of("error", "Failed to process file"))
            .build();
    }
}

Rate Limiting

Too Many Requests

@ServerRequestFilter
public Optional<Response> rateLimit(
    @Context SecurityContext ctx,
    UriInfo uriInfo
) {
    String userId = ctx.getUserPrincipal().getName();
    String endpoint = uriInfo.getPath();
    
    if (!rateLimiter.tryAcquire(userId, endpoint)) {
        return Optional.of(
            Response.status(429)
                .header("Retry-After", "60")
                .entity(Map.of(
                    "error", "Rate limit exceeded",
                    "retryAfter", 60
                ))
                .build()
        );
    }
    
    return Optional.empty();
}

Network Errors

Retry with Backoff

@GET
@Path("/{id}")
public Uni<Item> get(@RestPath Long id) {
    return externalClient.getItem(id)
        .onFailure(IOException.class).retry()
        .withBackOff(Duration.ofSeconds(1), Duration.ofSeconds(10))
        .atMost(3)
        .onFailure().recoverWithItem(() -> {
            logger.error("Failed to fetch item after retries: {}", id);
            return null;
        });
}

Circuit Breaker

@GET
@Path("/external")
public Uni<RestResponse<Data>> getExternal() {
    return externalClient.getData()
        .onFailure().transform(ex -> {
            if (circuitBreaker.isOpen()) {
                return new ServiceUnavailableException("Circuit breaker open");
            }
            return ex;
        })
        .onItem().invoke(() -> circuitBreaker.recordSuccess())
        .onFailure().invoke(() -> circuitBreaker.recordFailure())
        .onItem().transform(data -> RestResponse.ok(data))
        .onFailure(ServiceUnavailableException.class)
        .recoverWithItem(RestResponse.status(503, "Service unavailable"));
}

Database Errors

Connection Failures

@GET
public Uni<List<Item>> list() {
    return itemService.findAll()
        .onFailure(PersistenceException.class).retry()
        .withBackOff(Duration.ofMillis(100))
        .atMost(3)
        .onFailure().recoverWithItem(Collections.emptyList());
}

Transaction Rollback

@POST
@Transactional
public Uni<RestResponse<Order>> createOrder(CreateOrderRequest request) {
    return orderService.create(request)
        .chain(order -> inventoryService.reserve(order.getItems())
            .onItem().transform(() -> order)
        )
        .onItem().transform(order -> RestResponse.status(201, order))
        .onFailure().invoke(ex -> {
            logger.error("Order creation failed, transaction will rollback", ex);
        })
        .onFailure().recoverWithItem(
            RestResponse.status(500, "Failed to create order")
        );
}

Authentication Errors

Token Expiration

@ServerExceptionMapper
public RestResponse<ErrorResponse> handleExpiredToken(
    ExpiredJwtException ex
) {
    return RestResponse.status(401, new ErrorResponse(
        "Token expired",
        "AUTH_TOKEN_EXPIRED"
    ));
}

@ServerExceptionMapper
public RestResponse<ErrorResponse> handleInvalidToken(
    InvalidJwtException ex
) {
    return RestResponse.status(401, new ErrorResponse(
        "Invalid token",
        "AUTH_TOKEN_INVALID"
    ));
}

Malformed Requests

JSON Parse Errors

@ServerExceptionMapper
public RestResponse<ErrorResponse> handleJsonError(
    JsonProcessingException ex
) {
    return RestResponse.status(400, new ErrorResponse(
        "Invalid JSON: " + ex.getMessage(),
        "JSON_PARSE_ERROR"
    ));
}

Invalid Date Formats

@ServerExceptionMapper
public RestResponse<ErrorResponse> handleDateError(
    DateTimeParseException ex
) {
    return RestResponse.status(400, new ErrorResponse(
        "Invalid date format. Expected: yyyy-MM-dd",
        "INVALID_DATE_FORMAT"
    ));
}

Resource Cleanup

Ensure Cleanup

@GET
@Path("/download/{id}")
public Response download(@RestPath String id, @Context Closer closer) {
    try {
        InputStream stream = fileService.openStream(id);
        closer.add(stream);  // Ensure cleanup
        
        return Response.ok(stream)
            .type("application/octet-stream")
            .build();
    } catch (IOException e) {
        return Response.status(500)
            .entity("Failed to open file")
            .build();
    }
}

Next Steps

  • Review Common Scenarios
  • Check Advanced Patterns
  • Explore Reference Documentation