0
# HTTP Caching
1
2
HTTP caching support through annotations with comprehensive Cache-Control header configuration for optimizing web service performance. Provides declarative caching configuration with flexible cache control directives.
3
4
## Capabilities
5
6
### CacheControl Annotation
7
8
Comprehensive annotation for configuring HTTP Cache-Control headers with support for all standard cache directives.
9
10
```java { .api }
11
/**
12
* Annotation for adding Cache-Control headers to HTTP responses
13
* Supports all standard HTTP cache control directives
14
*/
15
@Documented
16
@Target(ElementType.METHOD)
17
@Retention(RetentionPolicy.RUNTIME)
18
public @interface CacheControl {
19
20
/**
21
* Marks response as immutable for maximum caching (1 year max-age)
22
* @return true if response should be cached indefinitely
23
*/
24
boolean immutable() default false;
25
26
/**
27
* Controls private cache directive - response only cacheable by private caches
28
* @return true if response must not be stored by shared caches
29
*/
30
boolean isPrivate() default false;
31
32
/**
33
* Controls no-cache directive - response must be revalidated before use
34
* @return true if response must not be used without revalidation
35
*/
36
boolean noCache() default false;
37
38
/**
39
* Controls no-store directive - response must not be stored at all
40
* @return true if response must not be stored in any cache
41
*/
42
boolean noStore() default false;
43
44
/**
45
* Controls no-transform directive - intermediaries must not modify response
46
* @return true if response must not be transformed by intermediaries
47
*/
48
boolean noTransform() default true;
49
50
/**
51
* Controls must-revalidate directive - stale responses require revalidation
52
* @return true if caches must revalidate when response becomes stale
53
*/
54
boolean mustRevalidate() default false;
55
56
/**
57
* Controls proxy-revalidate directive - like must-revalidate but only for shared caches
58
* @return true if only proxies must revalidate when response becomes stale
59
*/
60
boolean proxyRevalidate() default false;
61
62
/**
63
* Sets max-age directive value in specified time units
64
* @return maximum age for response freshness (-1 to disable)
65
*/
66
int maxAge() default -1;
67
68
/**
69
* Time unit for max-age value
70
* @return time unit for maxAge value
71
*/
72
TimeUnit maxAgeUnit() default TimeUnit.SECONDS;
73
74
/**
75
* Sets stale-while-revalidate directive for serving stale content during revalidation
76
* @return time period for serving stale content while revalidating (-1 to disable)
77
*/
78
int staleWhileRevalidate() default -1;
79
80
/**
81
* Time unit for stale-while-revalidate value
82
* @return time unit for staleWhileRevalidate value
83
*/
84
TimeUnit staleWhileRevalidateUnit() default TimeUnit.SECONDS;
85
86
/**
87
* Sets s-max-age directive for shared cache maximum age
88
* @return maximum age for shared caches (-1 to disable)
89
*/
90
int sharedMaxAge() default -1;
91
92
/**
93
* Time unit for s-max-age value
94
* @return time unit for sharedMaxAge value
95
*/
96
TimeUnit sharedMaxAgeUnit() default TimeUnit.SECONDS;
97
}
98
```
99
100
**Usage Examples:**
101
102
```java
103
import io.dropwizard.jersey.caching.CacheControl;
104
import jakarta.ws.rs.*;
105
import java.util.concurrent.TimeUnit;
106
107
@Path("/api")
108
public class CacheableResource {
109
110
// Basic caching - 5 minutes
111
@GET
112
@Path("/data")
113
@CacheControl(maxAge = 5, maxAgeUnit = TimeUnit.MINUTES)
114
public Data getData() {
115
return dataService.getCurrentData();
116
}
117
118
// Immutable content - cached indefinitely
119
@GET
120
@Path("/static/{id}")
121
@CacheControl(immutable = true)
122
public StaticContent getStaticContent(@PathParam("id") String id) {
123
return staticContentService.getById(id);
124
}
125
126
// Private caching only - not cached by proxies
127
@GET
128
@Path("/user/private")
129
@CacheControl(isPrivate = true, maxAge = 30, maxAgeUnit = TimeUnit.MINUTES)
130
public UserData getPrivateUserData() {
131
return userService.getPrivateData();
132
}
133
134
// No caching - always revalidate
135
@GET
136
@Path("/realtime")
137
@CacheControl(noCache = true)
138
public RealtimeData getRealtimeData() {
139
return realtimeService.getCurrentData();
140
}
141
142
// Never store in cache
143
@GET
144
@Path("/sensitive")
145
@CacheControl(noStore = true)
146
public SensitiveData getSensitiveData() {
147
return sensitiveService.getData();
148
}
149
150
// Complex caching with multiple directives
151
@GET
152
@Path("/complex")
153
@CacheControl(
154
maxAge = 1, maxAgeUnit = TimeUnit.HOURS,
155
sharedMaxAge = 30, sharedMaxAgeUnit = TimeUnit.MINUTES,
156
mustRevalidate = true,
157
staleWhileRevalidate = 5, staleWhileRevalidateUnit = TimeUnit.MINUTES
158
)
159
public ComplexData getComplexData() {
160
return complexService.getData();
161
}
162
}
163
```
164
165
### CacheControlledResponseFeature
166
167
Jersey feature that processes @CacheControl annotations and adds appropriate headers to responses.
168
169
```java { .api }
170
/**
171
* Jersey feature that enables @CacheControl annotation processing
172
* Automatically registered by DropwizardResourceConfig
173
*/
174
public class CacheControlledResponseFeature implements Feature {
175
176
/**
177
* Configures the feature with Jersey
178
* @param context Jersey feature context
179
* @return true if feature was successfully configured
180
*/
181
public boolean configure(FeatureContext context);
182
}
183
```
184
185
## Cache Control Strategies
186
187
### Public Content Caching
188
189
```java
190
@Path("/public")
191
public class PublicContentResource {
192
193
// Static assets - cache for 1 year
194
@GET
195
@Path("/assets/{filename}")
196
@CacheControl(immutable = true)
197
public Response getAsset(@PathParam("filename") String filename) {
198
byte[] content = assetService.getAsset(filename);
199
return Response.ok(content)
200
.type(getContentType(filename))
201
.build();
202
}
203
204
// API data - cache for 1 hour
205
@GET
206
@Path("/catalog")
207
@CacheControl(maxAge = 1, maxAgeUnit = TimeUnit.HOURS)
208
public ProductCatalog getCatalog() {
209
return catalogService.getPublicCatalog();
210
}
211
212
// Frequently updated content - cache for 5 minutes
213
@GET
214
@Path("/news")
215
@CacheControl(maxAge = 5, maxAgeUnit = TimeUnit.MINUTES)
216
public List<NewsItem> getNews() {
217
return newsService.getLatestNews();
218
}
219
220
// CDN optimization - different cache times for different levels
221
@GET
222
@Path("/images/{id}")
223
@CacheControl(
224
maxAge = 7, maxAgeUnit = TimeUnit.DAYS, // Browser cache: 7 days
225
sharedMaxAge = 30, sharedMaxAgeUnit = TimeUnit.DAYS // CDN cache: 30 days
226
)
227
public Response getImage(@PathParam("id") String imageId) {
228
byte[] image = imageService.getImage(imageId);
229
return Response.ok(image).type("image/jpeg").build();
230
}
231
}
232
```
233
234
### Private Content Caching
235
236
```java
237
@Path("/user")
238
public class UserContentResource {
239
240
// User-specific data - private cache only
241
@GET
242
@Path("/profile")
243
@CacheControl(isPrivate = true, maxAge = 15, maxAgeUnit = TimeUnit.MINUTES)
244
public UserProfile getUserProfile(@Context SecurityContext security) {
245
String userId = security.getUserPrincipal().getName();
246
return userService.getProfile(userId);
247
}
248
249
// User preferences - private, short cache
250
@GET
251
@Path("/preferences")
252
@CacheControl(isPrivate = true, maxAge = 5, maxAgeUnit = TimeUnit.MINUTES)
253
public UserPreferences getUserPreferences(@Context SecurityContext security) {
254
String userId = security.getUserPrincipal().getName();
255
return userService.getPreferences(userId);
256
}
257
258
// Sensitive data - no caching at all
259
@GET
260
@Path("/financial")
261
@CacheControl(noStore = true)
262
public FinancialData getFinancialData(@Context SecurityContext security) {
263
String userId = security.getUserPrincipal().getName();
264
return financialService.getData(userId);
265
}
266
}
267
```
268
269
### Dynamic Content Caching
270
271
```java
272
@Path("/dynamic")
273
public class DynamicContentResource {
274
275
// Content that changes frequently - must revalidate
276
@GET
277
@Path("/status")
278
@CacheControl(
279
maxAge = 30, maxAgeUnit = TimeUnit.SECONDS,
280
mustRevalidate = true
281
)
282
public SystemStatus getSystemStatus() {
283
return statusService.getCurrentStatus();
284
}
285
286
// Content with ETags for conditional requests
287
@GET
288
@Path("/data/{id}")
289
@CacheControl(maxAge = 10, maxAgeUnit = TimeUnit.MINUTES)
290
public Response getData(@PathParam("id") String id,
291
@Context Request request) {
292
293
Data data = dataService.getById(id);
294
EntityTag etag = new EntityTag(data.getVersion());
295
296
// Check if client has current version
297
Response.ResponseBuilder builder = request.evaluatePreconditions(etag);
298
if (builder != null) {
299
return builder.build(); // 304 Not Modified
300
}
301
302
return Response.ok(data).tag(etag).build();
303
}
304
305
// Stale-while-revalidate for better performance
306
@GET
307
@Path("/expensive")
308
@CacheControl(
309
maxAge = 5, maxAgeUnit = TimeUnit.MINUTES,
310
staleWhileRevalidate = 1, staleWhileRevalidateUnit = TimeUnit.HOURS
311
)
312
public ExpensiveData getExpensiveData() {
313
// Allow serving stale content for 1 hour while revalidating
314
return expensiveService.computeData();
315
}
316
}
317
```
318
319
### Conditional Caching
320
321
```java
322
@Path("/conditional")
323
public class ConditionalCacheResource {
324
325
@GET
326
@Path("/data")
327
public Response getDataWithConditionalCaching(@QueryParam("format") String format) {
328
329
if ("json".equals(format)) {
330
// JSON format - cache for 1 hour
331
Data data = dataService.getData();
332
return Response.ok(data)
333
.header("Cache-Control", "max-age=3600")
334
.build();
335
336
} else if ("xml".equals(format)) {
337
// XML format - cache for 30 minutes
338
Data data = dataService.getData();
339
String xml = xmlService.toXml(data);
340
return Response.ok(xml)
341
.type(MediaType.APPLICATION_XML)
342
.header("Cache-Control", "max-age=1800")
343
.build();
344
345
} else {
346
// Unknown format - no caching
347
return Response.status(400)
348
.entity("Unsupported format")
349
.header("Cache-Control", "no-store")
350
.build();
351
}
352
}
353
354
// Method-level caching with runtime conditions
355
@GET
356
@Path("/contextual/{type}")
357
@CacheControl(maxAge = 1, maxAgeUnit = TimeUnit.HOURS) // Default caching
358
public Response getContextualData(@PathParam("type") String type,
359
@Context HttpHeaders headers) {
360
361
ContextualData data = contextService.getData(type);
362
363
// Override caching based on content type
364
ResponseBuilder builder = Response.ok(data);
365
366
if (data.isHighlyDynamic()) {
367
// Override annotation for dynamic content
368
builder.header("Cache-Control", "no-cache, must-revalidate");
369
} else if (data.isStatic()) {
370
// Extend caching for static content
371
builder.header("Cache-Control", "public, max-age=86400, immutable");
372
}
373
// Otherwise use annotation defaults
374
375
return builder.build();
376
}
377
}
378
```
379
380
## Advanced Caching Patterns
381
382
### Cache Invalidation
383
384
```java
385
@Path("/admin")
386
public class CacheInvalidationResource {
387
388
@POST
389
@Path("/cache/invalidate")
390
public Response invalidateCache(@QueryParam("pattern") String pattern) {
391
// Trigger cache invalidation
392
cacheService.invalidate(pattern);
393
394
return Response.ok()
395
.header("Cache-Control", "no-cache")
396
.entity("Cache invalidated")
397
.build();
398
}
399
400
@PUT
401
@Path("/data/{id}")
402
public Response updateData(@PathParam("id") String id, Data data) {
403
Data updated = dataService.update(id, data);
404
405
// Set cache headers for updated resource
406
return Response.ok(updated)
407
.header("Cache-Control", "max-age=300, must-revalidate")
408
.header("ETag", "\"" + updated.getVersion() + "\"")
409
.build();
410
}
411
}
412
```
413
414
### Multi-tier Caching
415
416
```java
417
@Path("/tiered")
418
public class MultiTierCacheResource {
419
420
// Different cache durations for different tiers
421
@GET
422
@Path("/content/{id}")
423
@CacheControl(
424
maxAge = 5, maxAgeUnit = TimeUnit.MINUTES, // Browser: 5 minutes
425
sharedMaxAge = 1, sharedMaxAgeUnit = TimeUnit.HOURS, // CDN: 1 hour
426
staleWhileRevalidate = 10, staleWhileRevalidateUnit = TimeUnit.MINUTES
427
)
428
public ContentItem getContent(@PathParam("id") String id) {
429
return contentService.getById(id);
430
}
431
432
// Geographic caching considerations
433
@GET
434
@Path("/localized/{region}")
435
@CacheControl(
436
isPrivate = false,
437
maxAge = 30, maxAgeUnit = TimeUnit.MINUTES,
438
sharedMaxAge = 2, sharedMaxAgeUnit = TimeUnit.HOURS
439
)
440
public LocalizedContent getLocalizedContent(@PathParam("region") String region) {
441
return contentService.getLocalizedContent(region);
442
}
443
}
444
```
445
446
## Configuration and Best Practices
447
448
### Cache Configuration
449
450
```java
451
public class CacheConfiguration {
452
453
public void configureCaching(JerseyEnvironment jersey) {
454
// CacheControlledResponseFeature is automatically registered
455
// by DropwizardResourceConfig
456
457
// Additional cache-related configuration
458
jersey.property(ServerProperties.OUTBOUND_CONTENT_LENGTH_BUFFER, 8192);
459
}
460
}
461
```
462
463
### Cache Header Best Practices
464
465
```java
466
public class CacheBestPractices {
467
468
// Static assets - use immutable with versioning
469
@GET
470
@Path("/static/v{version}/{file}")
471
@CacheControl(immutable = true)
472
public Response getVersionedAsset(@PathParam("version") String version,
473
@PathParam("file") String file) {
474
// Immutable because URL changes when content changes
475
return Response.ok(assetService.getAsset(version, file)).build();
476
}
477
478
// API responses - balance freshness with performance
479
@GET
480
@Path("/api/users")
481
@CacheControl(
482
maxAge = 2, maxAgeUnit = TimeUnit.MINUTES, // Fresh for 2 minutes
483
staleWhileRevalidate = 10, staleWhileRevalidateUnit = TimeUnit.MINUTES // Serve stale for 10 minutes while updating
484
)
485
public List<User> getUsers() {
486
return userService.getAllUsers();
487
}
488
489
// Personalized content - private caching only
490
@GET
491
@Path("/personal/dashboard")
492
@CacheControl(
493
isPrivate = true,
494
maxAge = 5, maxAgeUnit = TimeUnit.MINUTES,
495
mustRevalidate = true
496
)
497
public Dashboard getPersonalDashboard(@Context SecurityContext security) {
498
return dashboardService.getPersonalDashboard(security.getUserPrincipal().getName());
499
}
500
501
// Sensitive data - no caching
502
@GET
503
@Path("/sensitive/data")
504
@CacheControl(noStore = true)
505
public SensitiveResponse getSensitiveData() {
506
return sensitiveService.getData();
507
}
508
}
509
```
510
511
### Error Response Caching
512
513
```java
514
@Path("/errors")
515
public class ErrorCacheResource {
516
517
@GET
518
@Path("/data/{id}")
519
public Response getData(@PathParam("id") String id) {
520
try {
521
Data data = dataService.getById(id);
522
523
return Response.ok(data)
524
.header("Cache-Control", "max-age=300") // Cache successful responses
525
.build();
526
527
} catch (NotFoundException e) {
528
return Response.status(404)
529
.entity("Not found")
530
.header("Cache-Control", "max-age=60") // Cache 404s briefly
531
.build();
532
533
} catch (Exception e) {
534
return Response.status(500)
535
.entity("Server error")
536
.header("Cache-Control", "no-cache") // Don't cache server errors
537
.build();
538
}
539
}
540
}
541
```