Dropwizard Jersey Support - Jersey integration module for the Dropwizard Java framework
—
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.
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 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);
}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;
}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;
}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();
}
}@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();
}
}All error responses follow the same JSON structure:
{
"code": 400,
"message": "Invalid input data",
"details": "Field 'email' must be a valid email address"
}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");
}
}@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();
}
}
}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);
}
}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);
}
}// 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
}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