CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-io-dropwizard--dropwizard-jersey

Dropwizard Jersey Support - Jersey integration module for the Dropwizard Java framework

Pending
Overview
Eval results
Files

error-handling.mddocs/

Error Handling

Standardized error handling with consistent error message format and exception mappers for common exceptions. Provides unified error responses and comprehensive exception mapping for JAX-RS resources.

Capabilities

ErrorMessage

Standardized error message format for consistent client error responses with JSON serialization support.

/**
 * Standardized error message format for HTTP error responses
 * Provides consistent structure for client error handling
 */
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ErrorMessage {
    
    /** Creates error message with 500 status and message */
    public ErrorMessage(String message);
    
    /** Creates error message with specific status code and message */
    public ErrorMessage(int code, String message);
    
    /** Creates error message with status code, message, and details */
    @JsonCreator
    public ErrorMessage(@JsonProperty("code") int code,
                       @JsonProperty("message") String message,
                       @JsonProperty("details") String details);
    
    /** Gets the HTTP status code */
    @JsonProperty("code")
    public Integer getCode();
    
    /** Gets the error message */
    @JsonProperty("message")
    public String getMessage();
    
    /** Gets additional error details */
    @JsonProperty("details") 
    public String getDetails();
}

Usage Examples:

import io.dropwizard.jersey.errors.ErrorMessage;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.Response;

@Path("/api")
public class ApiResource {
    
    @GET
    @Path("/users/{id}")
    public Response getUser(@PathParam("id") String userId) {
        try {
            User user = userService.findById(userId);
            if (user == null) {
                ErrorMessage error = new ErrorMessage(404, "User not found");
                return Response.status(404).entity(error).build();
            }
            return Response.ok(user).build();
        } catch (IllegalArgumentException e) {
            ErrorMessage error = new ErrorMessage(400, "Invalid user ID", e.getMessage());
            return Response.status(400).entity(error).build();
        } catch (Exception e) {
            ErrorMessage error = new ErrorMessage(500, "Internal server error");
            return Response.status(500).entity(error).build();
        }
    }
}

Exception Mappers

Exception mappers that convert common exceptions into appropriate HTTP responses with ErrorMessage format.

/**
 * Base exception mapper with logging support
 * Provides consistent exception handling and logging across mappers
 * @param <E> the exception type to handle
 */
public class LoggingExceptionMapper<E extends Throwable> implements ExceptionMapper<E> {
    
    /** Maps exception to HTTP response with logging */
    public Response toResponse(E exception);
    
    /** Logs the exception with appropriate level */
    protected void logException(E exception);
    
    /** Gets HTTP status code for the exception */
    protected Response.Status getStatus(E exception);
    
    /** Creates error message for the exception */
    protected ErrorMessage createErrorMessage(E exception);
}

/**
 * Maps IllegalStateException to HTTP 500 responses
 */
public class IllegalStateExceptionMapper implements ExceptionMapper<IllegalStateException> {
    
    /** Maps IllegalStateException to 500 Internal Server Error */
    public Response toResponse(IllegalStateException exception);
}

/**
 * Maps EarlyEOFException to appropriate HTTP responses
 * Handles client disconnection scenarios
 */  
public class EarlyEofExceptionMapper implements ExceptionMapper<EarlyEOFException> {
    
    /** Maps EarlyEOFException to appropriate response */
    public Response toResponse(EarlyEOFException exception);
}

Error Entity Writer

Message body writer for serializing ErrorMessage objects to JSON responses.

/**
 * Message body writer for ErrorMessage objects
 * Handles JSON serialization of error responses
 */
public class ErrorEntityWriter implements MessageBodyWriter<ErrorMessage> {
    
    /** Checks if this writer can handle the given type */
    public boolean isWriteable(Class<?> type, Type genericType, 
                              Annotation[] annotations, MediaType mediaType);
    
    /** Writes ErrorMessage to output stream as JSON */
    public void writeTo(ErrorMessage errorMessage, Class<?> type, Type genericType,
                       Annotation[] annotations, MediaType mediaType,
                       MultivaluedMap<String, Object> httpHeaders,
                       OutputStream entityStream) throws IOException;
}

EOF Exception Handling

Specialized handling for early EOF exceptions that occur when clients disconnect during response writing.

/**
 * Writer interceptor that handles EOF exceptions during response writing
 * Prevents server errors when clients disconnect early
 */
public class EofExceptionWriterInterceptor implements WriterInterceptor {
    
    /** Intercepts response writing to handle EOF exceptions */
    public void aroundWriteTo(WriterInterceptorContext context) 
            throws IOException, WebApplicationException;
}

Custom Exception Mappers

Creating Custom Mappers

import io.dropwizard.jersey.errors.LoggingExceptionMapper;
import io.dropwizard.jersey.errors.ErrorMessage;
import jakarta.ws.rs.ext.Provider;

@Provider
public class BusinessExceptionMapper extends LoggingExceptionMapper<BusinessException> {
    
    @Override
    public Response toResponse(BusinessException exception) {
        ErrorMessage error = new ErrorMessage(
            exception.getErrorCode(),
            exception.getMessage(),
            exception.getDetails()
        );
        
        return Response
            .status(exception.getHttpStatus())
            .entity(error)
            .type(MediaType.APPLICATION_JSON)
            .build();
    }
    
    @Override
    protected void logException(BusinessException exception) {
        // Log business exceptions at WARN level
        logger.warn("Business exception: {}", exception.getMessage(), exception);
    }
}

@Provider  
public class ValidationExceptionMapper implements ExceptionMapper<ValidationException> {
    
    @Override
    public Response toResponse(ValidationException exception) {
        List<String> errors = exception.getConstraintViolations()
            .stream()
            .map(violation -> violation.getPropertyPath() + ": " + violation.getMessage())
            .collect(Collectors.toList());
            
        ErrorMessage error = new ErrorMessage(
            400,
            "Validation failed",
            String.join(", ", errors)
        );
        
        return Response.status(400).entity(error).build();
    }
}

Security Exception Handling

@Provider
public class SecurityExceptionMapper implements ExceptionMapper<SecurityException> {
    
    @Override
    public Response toResponse(SecurityException exception) {
        // Don't leak sensitive information
        ErrorMessage error = new ErrorMessage(403, "Access denied");
        
        // Log security exceptions for monitoring
        logger.warn("Security exception: {}", exception.getMessage());
        
        return Response.status(403).entity(error).build();
    }
}

@Provider
public class AuthenticationExceptionMapper implements ExceptionMapper<AuthenticationException> {
    
    @Override
    public Response toResponse(AuthenticationException exception) {
        ErrorMessage error = new ErrorMessage(401, "Authentication required");
        
        return Response.status(401)
            .entity(error)
            .header("WWW-Authenticate", "Bearer")
            .build();
    }
}

Error Response Patterns

Consistent Error Structure

All error responses follow the same JSON structure:

{
  "code": 400,
  "message": "Invalid input data",
  "details": "Field 'email' must be a valid email address"
}

Error Categories

public class ErrorCategories {
    
    // Client errors (4xx)
    public static ErrorMessage badRequest(String message) {
        return new ErrorMessage(400, message);
    }
    
    public static ErrorMessage unauthorized(String message) {
        return new ErrorMessage(401, message != null ? message : "Authentication required");
    }
    
    public static ErrorMessage forbidden(String message) {
        return new ErrorMessage(403, message != null ? message : "Access denied");
    }
    
    public static ErrorMessage notFound(String resource) {
        return new ErrorMessage(404, resource + " not found");
    }
    
    public static ErrorMessage conflict(String message) {
        return new ErrorMessage(409, message);
    }
    
    public static ErrorMessage validationError(String details) {
        return new ErrorMessage(422, "Validation failed", details);
    }
    
    // Server errors (5xx)
    public static ErrorMessage internalError() {
        return new ErrorMessage(500, "Internal server error");
    }
    
    public static ErrorMessage serviceUnavailable(String message) {
        return new ErrorMessage(503, message != null ? message : "Service temporarily unavailable");
    }
}

Resource-Level Error Handling

@Path("/orders")
public class OrderResource {
    
    @GET
    @Path("/{id}")
    public Response getOrder(@PathParam("id") UUIDParam orderId) {
        try {
            UUID id = orderId.get(); // Parameter validation handled automatically
            
            Order order = orderService.findById(id);
            if (order == null) {
                return Response.status(404)
                    .entity(new ErrorMessage(404, "Order not found"))
                    .build();
            }
            
            return Response.ok(order).build();
            
        } catch (SecurityException e) {
            return Response.status(403)
                .entity(new ErrorMessage(403, "Access denied"))
                .build();
        } catch (ServiceException e) {
            return Response.status(503)
                .entity(new ErrorMessage(503, "Order service unavailable"))
                .build();
        }
    }
    
    @POST
    public Response createOrder(@Valid CreateOrderRequest request) {
        try {
            Order order = orderService.create(request);
            return Response.status(201)
                .entity(order)
                .location(URI.create("/orders/" + order.getId()))
                .build();
                
        } catch (InsufficientInventoryException e) {
            return Response.status(409)
                .entity(new ErrorMessage(409, "Insufficient inventory", e.getMessage()))
                .build();
        } catch (PaymentException e) {
            return Response.status(402)
                .entity(new ErrorMessage(402, "Payment required", e.getMessage()))
                .build();
        }
    }
}

Integration with Jersey

Automatic Registration

Exception mappers are automatically registered when using DropwizardResourceConfig:

public class MyApplication extends Application<MyConfiguration> {
    
    @Override
    public void run(MyConfiguration config, Environment environment) {
        // Exception mappers are automatically registered
        JerseyEnvironment jersey = environment.jersey();
        
        // Register custom exception mappers
        jersey.register(BusinessExceptionMapper.class);
        jersey.register(ValidationExceptionMapper.class);
        jersey.register(SecurityExceptionMapper.class);
    }
}

Error Handling Configuration

public class ErrorHandlingConfiguration {
    
    public void configure(JerseyEnvironment jersey) {
        // Register error-related providers
        jersey.register(ErrorEntityWriter.class);
        jersey.register(EofExceptionWriterInterceptor.class);
        
        // Configure error handling behavior
        jersey.property(ServerProperties.RESPONSE_SET_STATUS_OVER_SEND_ERROR, true);
    }
}

Best Practices

Exception Mapper Hierarchy

// Generic base mapper for common exception handling
@Provider
public class BaseExceptionMapper implements ExceptionMapper<Exception> {
    
    @Override
    public Response toResponse(Exception exception) {
        // Log unexpected exceptions
        logger.error("Unexpected exception", exception);
        
        // Return generic error message to avoid information leakage
        ErrorMessage error = new ErrorMessage(500, "Internal server error");
        return Response.status(500).entity(error).build();
    }
    
    @Priority(Priorities.USER + 1000) // Lower priority than specific mappers
    public static class Provider {}
}

// Specific mappers for known exceptions
@Provider
@Priority(Priorities.USER)
public class SpecificExceptionMapper implements ExceptionMapper<SpecificException> {
    // Handle specific exception types with appropriate responses
}

Error Logging Strategy

public class ErrorLoggingStrategy {
    
    // Client errors (4xx) - log at DEBUG/INFO level
    protected void logClientError(Exception e) {
        logger.info("Client error: {}", e.getMessage());
    }
    
    // Server errors (5xx) - log at ERROR level with full stack trace
    protected void logServerError(Exception e) {
        logger.error("Server error", e);
    }
    
    // Security errors - log for monitoring
    protected void logSecurityError(Exception e) {
        logger.warn("Security error: {}", e.getMessage());
        // Could integrate with security monitoring systems
    }
}

Install with Tessl CLI

npx tessl i tessl/maven-io-dropwizard--dropwizard-jersey

docs

error-handling.md

framework-configuration.md

http-caching.md

index.md

jsr310-parameters.md

optional-handling.md

parameter-handling.md

session-management.md

validation.md

tile.json