0
# Error Handling
1
2
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.
3
4
## Capabilities
5
6
### ErrorMessage
7
8
Standardized error message format for consistent client error responses with JSON serialization support.
9
10
```java { .api }
11
/**
12
* Standardized error message format for HTTP error responses
13
* Provides consistent structure for client error handling
14
*/
15
@JsonInclude(JsonInclude.Include.NON_NULL)
16
public class ErrorMessage {
17
18
/** Creates error message with 500 status and message */
19
public ErrorMessage(String message);
20
21
/** Creates error message with specific status code and message */
22
public ErrorMessage(int code, String message);
23
24
/** Creates error message with status code, message, and details */
25
@JsonCreator
26
public ErrorMessage(@JsonProperty("code") int code,
27
@JsonProperty("message") String message,
28
@JsonProperty("details") String details);
29
30
/** Gets the HTTP status code */
31
@JsonProperty("code")
32
public Integer getCode();
33
34
/** Gets the error message */
35
@JsonProperty("message")
36
public String getMessage();
37
38
/** Gets additional error details */
39
@JsonProperty("details")
40
public String getDetails();
41
}
42
```
43
44
**Usage Examples:**
45
46
```java
47
import io.dropwizard.jersey.errors.ErrorMessage;
48
import jakarta.ws.rs.*;
49
import jakarta.ws.rs.core.Response;
50
51
@Path("/api")
52
public class ApiResource {
53
54
@GET
55
@Path("/users/{id}")
56
public Response getUser(@PathParam("id") String userId) {
57
try {
58
User user = userService.findById(userId);
59
if (user == null) {
60
ErrorMessage error = new ErrorMessage(404, "User not found");
61
return Response.status(404).entity(error).build();
62
}
63
return Response.ok(user).build();
64
} catch (IllegalArgumentException e) {
65
ErrorMessage error = new ErrorMessage(400, "Invalid user ID", e.getMessage());
66
return Response.status(400).entity(error).build();
67
} catch (Exception e) {
68
ErrorMessage error = new ErrorMessage(500, "Internal server error");
69
return Response.status(500).entity(error).build();
70
}
71
}
72
}
73
```
74
75
### Exception Mappers
76
77
Exception mappers that convert common exceptions into appropriate HTTP responses with ErrorMessage format.
78
79
```java { .api }
80
/**
81
* Base exception mapper with logging support
82
* Provides consistent exception handling and logging across mappers
83
* @param <E> the exception type to handle
84
*/
85
public class LoggingExceptionMapper<E extends Throwable> implements ExceptionMapper<E> {
86
87
/** Maps exception to HTTP response with logging */
88
public Response toResponse(E exception);
89
90
/** Logs the exception with appropriate level */
91
protected void logException(E exception);
92
93
/** Gets HTTP status code for the exception */
94
protected Response.Status getStatus(E exception);
95
96
/** Creates error message for the exception */
97
protected ErrorMessage createErrorMessage(E exception);
98
}
99
100
/**
101
* Maps IllegalStateException to HTTP 500 responses
102
*/
103
public class IllegalStateExceptionMapper implements ExceptionMapper<IllegalStateException> {
104
105
/** Maps IllegalStateException to 500 Internal Server Error */
106
public Response toResponse(IllegalStateException exception);
107
}
108
109
/**
110
* Maps EarlyEOFException to appropriate HTTP responses
111
* Handles client disconnection scenarios
112
*/
113
public class EarlyEofExceptionMapper implements ExceptionMapper<EarlyEOFException> {
114
115
/** Maps EarlyEOFException to appropriate response */
116
public Response toResponse(EarlyEOFException exception);
117
}
118
```
119
120
### Error Entity Writer
121
122
Message body writer for serializing ErrorMessage objects to JSON responses.
123
124
```java { .api }
125
/**
126
* Message body writer for ErrorMessage objects
127
* Handles JSON serialization of error responses
128
*/
129
public class ErrorEntityWriter implements MessageBodyWriter<ErrorMessage> {
130
131
/** Checks if this writer can handle the given type */
132
public boolean isWriteable(Class<?> type, Type genericType,
133
Annotation[] annotations, MediaType mediaType);
134
135
/** Writes ErrorMessage to output stream as JSON */
136
public void writeTo(ErrorMessage errorMessage, Class<?> type, Type genericType,
137
Annotation[] annotations, MediaType mediaType,
138
MultivaluedMap<String, Object> httpHeaders,
139
OutputStream entityStream) throws IOException;
140
}
141
```
142
143
### EOF Exception Handling
144
145
Specialized handling for early EOF exceptions that occur when clients disconnect during response writing.
146
147
```java { .api }
148
/**
149
* Writer interceptor that handles EOF exceptions during response writing
150
* Prevents server errors when clients disconnect early
151
*/
152
public class EofExceptionWriterInterceptor implements WriterInterceptor {
153
154
/** Intercepts response writing to handle EOF exceptions */
155
public void aroundWriteTo(WriterInterceptorContext context)
156
throws IOException, WebApplicationException;
157
}
158
```
159
160
## Custom Exception Mappers
161
162
### Creating Custom Mappers
163
164
```java
165
import io.dropwizard.jersey.errors.LoggingExceptionMapper;
166
import io.dropwizard.jersey.errors.ErrorMessage;
167
import jakarta.ws.rs.ext.Provider;
168
169
@Provider
170
public class BusinessExceptionMapper extends LoggingExceptionMapper<BusinessException> {
171
172
@Override
173
public Response toResponse(BusinessException exception) {
174
ErrorMessage error = new ErrorMessage(
175
exception.getErrorCode(),
176
exception.getMessage(),
177
exception.getDetails()
178
);
179
180
return Response
181
.status(exception.getHttpStatus())
182
.entity(error)
183
.type(MediaType.APPLICATION_JSON)
184
.build();
185
}
186
187
@Override
188
protected void logException(BusinessException exception) {
189
// Log business exceptions at WARN level
190
logger.warn("Business exception: {}", exception.getMessage(), exception);
191
}
192
}
193
194
@Provider
195
public class ValidationExceptionMapper implements ExceptionMapper<ValidationException> {
196
197
@Override
198
public Response toResponse(ValidationException exception) {
199
List<String> errors = exception.getConstraintViolations()
200
.stream()
201
.map(violation -> violation.getPropertyPath() + ": " + violation.getMessage())
202
.collect(Collectors.toList());
203
204
ErrorMessage error = new ErrorMessage(
205
400,
206
"Validation failed",
207
String.join(", ", errors)
208
);
209
210
return Response.status(400).entity(error).build();
211
}
212
}
213
```
214
215
### Security Exception Handling
216
217
```java
218
@Provider
219
public class SecurityExceptionMapper implements ExceptionMapper<SecurityException> {
220
221
@Override
222
public Response toResponse(SecurityException exception) {
223
// Don't leak sensitive information
224
ErrorMessage error = new ErrorMessage(403, "Access denied");
225
226
// Log security exceptions for monitoring
227
logger.warn("Security exception: {}", exception.getMessage());
228
229
return Response.status(403).entity(error).build();
230
}
231
}
232
233
@Provider
234
public class AuthenticationExceptionMapper implements ExceptionMapper<AuthenticationException> {
235
236
@Override
237
public Response toResponse(AuthenticationException exception) {
238
ErrorMessage error = new ErrorMessage(401, "Authentication required");
239
240
return Response.status(401)
241
.entity(error)
242
.header("WWW-Authenticate", "Bearer")
243
.build();
244
}
245
}
246
```
247
248
## Error Response Patterns
249
250
### Consistent Error Structure
251
252
All error responses follow the same JSON structure:
253
254
```json
255
{
256
"code": 400,
257
"message": "Invalid input data",
258
"details": "Field 'email' must be a valid email address"
259
}
260
```
261
262
### Error Categories
263
264
```java
265
public class ErrorCategories {
266
267
// Client errors (4xx)
268
public static ErrorMessage badRequest(String message) {
269
return new ErrorMessage(400, message);
270
}
271
272
public static ErrorMessage unauthorized(String message) {
273
return new ErrorMessage(401, message != null ? message : "Authentication required");
274
}
275
276
public static ErrorMessage forbidden(String message) {
277
return new ErrorMessage(403, message != null ? message : "Access denied");
278
}
279
280
public static ErrorMessage notFound(String resource) {
281
return new ErrorMessage(404, resource + " not found");
282
}
283
284
public static ErrorMessage conflict(String message) {
285
return new ErrorMessage(409, message);
286
}
287
288
public static ErrorMessage validationError(String details) {
289
return new ErrorMessage(422, "Validation failed", details);
290
}
291
292
// Server errors (5xx)
293
public static ErrorMessage internalError() {
294
return new ErrorMessage(500, "Internal server error");
295
}
296
297
public static ErrorMessage serviceUnavailable(String message) {
298
return new ErrorMessage(503, message != null ? message : "Service temporarily unavailable");
299
}
300
}
301
```
302
303
### Resource-Level Error Handling
304
305
```java
306
@Path("/orders")
307
public class OrderResource {
308
309
@GET
310
@Path("/{id}")
311
public Response getOrder(@PathParam("id") UUIDParam orderId) {
312
try {
313
UUID id = orderId.get(); // Parameter validation handled automatically
314
315
Order order = orderService.findById(id);
316
if (order == null) {
317
return Response.status(404)
318
.entity(new ErrorMessage(404, "Order not found"))
319
.build();
320
}
321
322
return Response.ok(order).build();
323
324
} catch (SecurityException e) {
325
return Response.status(403)
326
.entity(new ErrorMessage(403, "Access denied"))
327
.build();
328
} catch (ServiceException e) {
329
return Response.status(503)
330
.entity(new ErrorMessage(503, "Order service unavailable"))
331
.build();
332
}
333
}
334
335
@POST
336
public Response createOrder(@Valid CreateOrderRequest request) {
337
try {
338
Order order = orderService.create(request);
339
return Response.status(201)
340
.entity(order)
341
.location(URI.create("/orders/" + order.getId()))
342
.build();
343
344
} catch (InsufficientInventoryException e) {
345
return Response.status(409)
346
.entity(new ErrorMessage(409, "Insufficient inventory", e.getMessage()))
347
.build();
348
} catch (PaymentException e) {
349
return Response.status(402)
350
.entity(new ErrorMessage(402, "Payment required", e.getMessage()))
351
.build();
352
}
353
}
354
}
355
```
356
357
## Integration with Jersey
358
359
### Automatic Registration
360
361
Exception mappers are automatically registered when using DropwizardResourceConfig:
362
363
```java
364
public class MyApplication extends Application<MyConfiguration> {
365
366
@Override
367
public void run(MyConfiguration config, Environment environment) {
368
// Exception mappers are automatically registered
369
JerseyEnvironment jersey = environment.jersey();
370
371
// Register custom exception mappers
372
jersey.register(BusinessExceptionMapper.class);
373
jersey.register(ValidationExceptionMapper.class);
374
jersey.register(SecurityExceptionMapper.class);
375
}
376
}
377
```
378
379
### Error Handling Configuration
380
381
```java
382
public class ErrorHandlingConfiguration {
383
384
public void configure(JerseyEnvironment jersey) {
385
// Register error-related providers
386
jersey.register(ErrorEntityWriter.class);
387
jersey.register(EofExceptionWriterInterceptor.class);
388
389
// Configure error handling behavior
390
jersey.property(ServerProperties.RESPONSE_SET_STATUS_OVER_SEND_ERROR, true);
391
}
392
}
393
```
394
395
## Best Practices
396
397
### Exception Mapper Hierarchy
398
399
```java
400
// Generic base mapper for common exception handling
401
@Provider
402
public class BaseExceptionMapper implements ExceptionMapper<Exception> {
403
404
@Override
405
public Response toResponse(Exception exception) {
406
// Log unexpected exceptions
407
logger.error("Unexpected exception", exception);
408
409
// Return generic error message to avoid information leakage
410
ErrorMessage error = new ErrorMessage(500, "Internal server error");
411
return Response.status(500).entity(error).build();
412
}
413
414
@Priority(Priorities.USER + 1000) // Lower priority than specific mappers
415
public static class Provider {}
416
}
417
418
// Specific mappers for known exceptions
419
@Provider
420
@Priority(Priorities.USER)
421
public class SpecificExceptionMapper implements ExceptionMapper<SpecificException> {
422
// Handle specific exception types with appropriate responses
423
}
424
```
425
426
### Error Logging Strategy
427
428
```java
429
public class ErrorLoggingStrategy {
430
431
// Client errors (4xx) - log at DEBUG/INFO level
432
protected void logClientError(Exception e) {
433
logger.info("Client error: {}", e.getMessage());
434
}
435
436
// Server errors (5xx) - log at ERROR level with full stack trace
437
protected void logServerError(Exception e) {
438
logger.error("Server error", e);
439
}
440
441
// Security errors - log for monitoring
442
protected void logSecurityError(Exception e) {
443
logger.warn("Security error: {}", e.getMessage());
444
// Could integrate with security monitoring systems
445
}
446
}
447
```