Dropwizard Jersey Support - Jersey integration module for the Dropwizard Java framework
—
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.
Comprehensive annotation for configuring HTTP Cache-Control headers with support for all standard cache directives.
/**
* Annotation for adding Cache-Control headers to HTTP responses
* Supports all standard HTTP cache control directives
*/
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CacheControl {
/**
* Marks response as immutable for maximum caching (1 year max-age)
* @return true if response should be cached indefinitely
*/
boolean immutable() default false;
/**
* Controls private cache directive - response only cacheable by private caches
* @return true if response must not be stored by shared caches
*/
boolean isPrivate() default false;
/**
* Controls no-cache directive - response must be revalidated before use
* @return true if response must not be used without revalidation
*/
boolean noCache() default false;
/**
* Controls no-store directive - response must not be stored at all
* @return true if response must not be stored in any cache
*/
boolean noStore() default false;
/**
* Controls no-transform directive - intermediaries must not modify response
* @return true if response must not be transformed by intermediaries
*/
boolean noTransform() default true;
/**
* Controls must-revalidate directive - stale responses require revalidation
* @return true if caches must revalidate when response becomes stale
*/
boolean mustRevalidate() default false;
/**
* Controls proxy-revalidate directive - like must-revalidate but only for shared caches
* @return true if only proxies must revalidate when response becomes stale
*/
boolean proxyRevalidate() default false;
/**
* Sets max-age directive value in specified time units
* @return maximum age for response freshness (-1 to disable)
*/
int maxAge() default -1;
/**
* Time unit for max-age value
* @return time unit for maxAge value
*/
TimeUnit maxAgeUnit() default TimeUnit.SECONDS;
/**
* Sets stale-while-revalidate directive for serving stale content during revalidation
* @return time period for serving stale content while revalidating (-1 to disable)
*/
int staleWhileRevalidate() default -1;
/**
* Time unit for stale-while-revalidate value
* @return time unit for staleWhileRevalidate value
*/
TimeUnit staleWhileRevalidateUnit() default TimeUnit.SECONDS;
/**
* Sets s-max-age directive for shared cache maximum age
* @return maximum age for shared caches (-1 to disable)
*/
int sharedMaxAge() default -1;
/**
* Time unit for s-max-age value
* @return time unit for sharedMaxAge value
*/
TimeUnit sharedMaxAgeUnit() default TimeUnit.SECONDS;
}Usage Examples:
import io.dropwizard.jersey.caching.CacheControl;
import jakarta.ws.rs.*;
import java.util.concurrent.TimeUnit;
@Path("/api")
public class CacheableResource {
// Basic caching - 5 minutes
@GET
@Path("/data")
@CacheControl(maxAge = 5, maxAgeUnit = TimeUnit.MINUTES)
public Data getData() {
return dataService.getCurrentData();
}
// Immutable content - cached indefinitely
@GET
@Path("/static/{id}")
@CacheControl(immutable = true)
public StaticContent getStaticContent(@PathParam("id") String id) {
return staticContentService.getById(id);
}
// Private caching only - not cached by proxies
@GET
@Path("/user/private")
@CacheControl(isPrivate = true, maxAge = 30, maxAgeUnit = TimeUnit.MINUTES)
public UserData getPrivateUserData() {
return userService.getPrivateData();
}
// No caching - always revalidate
@GET
@Path("/realtime")
@CacheControl(noCache = true)
public RealtimeData getRealtimeData() {
return realtimeService.getCurrentData();
}
// Never store in cache
@GET
@Path("/sensitive")
@CacheControl(noStore = true)
public SensitiveData getSensitiveData() {
return sensitiveService.getData();
}
// Complex caching with multiple directives
@GET
@Path("/complex")
@CacheControl(
maxAge = 1, maxAgeUnit = TimeUnit.HOURS,
sharedMaxAge = 30, sharedMaxAgeUnit = TimeUnit.MINUTES,
mustRevalidate = true,
staleWhileRevalidate = 5, staleWhileRevalidateUnit = TimeUnit.MINUTES
)
public ComplexData getComplexData() {
return complexService.getData();
}
}Jersey feature that processes @CacheControl annotations and adds appropriate headers to responses.
/**
* Jersey feature that enables @CacheControl annotation processing
* Automatically registered by DropwizardResourceConfig
*/
public class CacheControlledResponseFeature implements Feature {
/**
* Configures the feature with Jersey
* @param context Jersey feature context
* @return true if feature was successfully configured
*/
public boolean configure(FeatureContext context);
}@Path("/public")
public class PublicContentResource {
// Static assets - cache for 1 year
@GET
@Path("/assets/{filename}")
@CacheControl(immutable = true)
public Response getAsset(@PathParam("filename") String filename) {
byte[] content = assetService.getAsset(filename);
return Response.ok(content)
.type(getContentType(filename))
.build();
}
// API data - cache for 1 hour
@GET
@Path("/catalog")
@CacheControl(maxAge = 1, maxAgeUnit = TimeUnit.HOURS)
public ProductCatalog getCatalog() {
return catalogService.getPublicCatalog();
}
// Frequently updated content - cache for 5 minutes
@GET
@Path("/news")
@CacheControl(maxAge = 5, maxAgeUnit = TimeUnit.MINUTES)
public List<NewsItem> getNews() {
return newsService.getLatestNews();
}
// CDN optimization - different cache times for different levels
@GET
@Path("/images/{id}")
@CacheControl(
maxAge = 7, maxAgeUnit = TimeUnit.DAYS, // Browser cache: 7 days
sharedMaxAge = 30, sharedMaxAgeUnit = TimeUnit.DAYS // CDN cache: 30 days
)
public Response getImage(@PathParam("id") String imageId) {
byte[] image = imageService.getImage(imageId);
return Response.ok(image).type("image/jpeg").build();
}
}@Path("/user")
public class UserContentResource {
// User-specific data - private cache only
@GET
@Path("/profile")
@CacheControl(isPrivate = true, maxAge = 15, maxAgeUnit = TimeUnit.MINUTES)
public UserProfile getUserProfile(@Context SecurityContext security) {
String userId = security.getUserPrincipal().getName();
return userService.getProfile(userId);
}
// User preferences - private, short cache
@GET
@Path("/preferences")
@CacheControl(isPrivate = true, maxAge = 5, maxAgeUnit = TimeUnit.MINUTES)
public UserPreferences getUserPreferences(@Context SecurityContext security) {
String userId = security.getUserPrincipal().getName();
return userService.getPreferences(userId);
}
// Sensitive data - no caching at all
@GET
@Path("/financial")
@CacheControl(noStore = true)
public FinancialData getFinancialData(@Context SecurityContext security) {
String userId = security.getUserPrincipal().getName();
return financialService.getData(userId);
}
}@Path("/dynamic")
public class DynamicContentResource {
// Content that changes frequently - must revalidate
@GET
@Path("/status")
@CacheControl(
maxAge = 30, maxAgeUnit = TimeUnit.SECONDS,
mustRevalidate = true
)
public SystemStatus getSystemStatus() {
return statusService.getCurrentStatus();
}
// Content with ETags for conditional requests
@GET
@Path("/data/{id}")
@CacheControl(maxAge = 10, maxAgeUnit = TimeUnit.MINUTES)
public Response getData(@PathParam("id") String id,
@Context Request request) {
Data data = dataService.getById(id);
EntityTag etag = new EntityTag(data.getVersion());
// Check if client has current version
Response.ResponseBuilder builder = request.evaluatePreconditions(etag);
if (builder != null) {
return builder.build(); // 304 Not Modified
}
return Response.ok(data).tag(etag).build();
}
// Stale-while-revalidate for better performance
@GET
@Path("/expensive")
@CacheControl(
maxAge = 5, maxAgeUnit = TimeUnit.MINUTES,
staleWhileRevalidate = 1, staleWhileRevalidateUnit = TimeUnit.HOURS
)
public ExpensiveData getExpensiveData() {
// Allow serving stale content for 1 hour while revalidating
return expensiveService.computeData();
}
}@Path("/conditional")
public class ConditionalCacheResource {
@GET
@Path("/data")
public Response getDataWithConditionalCaching(@QueryParam("format") String format) {
if ("json".equals(format)) {
// JSON format - cache for 1 hour
Data data = dataService.getData();
return Response.ok(data)
.header("Cache-Control", "max-age=3600")
.build();
} else if ("xml".equals(format)) {
// XML format - cache for 30 minutes
Data data = dataService.getData();
String xml = xmlService.toXml(data);
return Response.ok(xml)
.type(MediaType.APPLICATION_XML)
.header("Cache-Control", "max-age=1800")
.build();
} else {
// Unknown format - no caching
return Response.status(400)
.entity("Unsupported format")
.header("Cache-Control", "no-store")
.build();
}
}
// Method-level caching with runtime conditions
@GET
@Path("/contextual/{type}")
@CacheControl(maxAge = 1, maxAgeUnit = TimeUnit.HOURS) // Default caching
public Response getContextualData(@PathParam("type") String type,
@Context HttpHeaders headers) {
ContextualData data = contextService.getData(type);
// Override caching based on content type
ResponseBuilder builder = Response.ok(data);
if (data.isHighlyDynamic()) {
// Override annotation for dynamic content
builder.header("Cache-Control", "no-cache, must-revalidate");
} else if (data.isStatic()) {
// Extend caching for static content
builder.header("Cache-Control", "public, max-age=86400, immutable");
}
// Otherwise use annotation defaults
return builder.build();
}
}@Path("/admin")
public class CacheInvalidationResource {
@POST
@Path("/cache/invalidate")
public Response invalidateCache(@QueryParam("pattern") String pattern) {
// Trigger cache invalidation
cacheService.invalidate(pattern);
return Response.ok()
.header("Cache-Control", "no-cache")
.entity("Cache invalidated")
.build();
}
@PUT
@Path("/data/{id}")
public Response updateData(@PathParam("id") String id, Data data) {
Data updated = dataService.update(id, data);
// Set cache headers for updated resource
return Response.ok(updated)
.header("Cache-Control", "max-age=300, must-revalidate")
.header("ETag", "\"" + updated.getVersion() + "\"")
.build();
}
}@Path("/tiered")
public class MultiTierCacheResource {
// Different cache durations for different tiers
@GET
@Path("/content/{id}")
@CacheControl(
maxAge = 5, maxAgeUnit = TimeUnit.MINUTES, // Browser: 5 minutes
sharedMaxAge = 1, sharedMaxAgeUnit = TimeUnit.HOURS, // CDN: 1 hour
staleWhileRevalidate = 10, staleWhileRevalidateUnit = TimeUnit.MINUTES
)
public ContentItem getContent(@PathParam("id") String id) {
return contentService.getById(id);
}
// Geographic caching considerations
@GET
@Path("/localized/{region}")
@CacheControl(
isPrivate = false,
maxAge = 30, maxAgeUnit = TimeUnit.MINUTES,
sharedMaxAge = 2, sharedMaxAgeUnit = TimeUnit.HOURS
)
public LocalizedContent getLocalizedContent(@PathParam("region") String region) {
return contentService.getLocalizedContent(region);
}
}public class CacheConfiguration {
public void configureCaching(JerseyEnvironment jersey) {
// CacheControlledResponseFeature is automatically registered
// by DropwizardResourceConfig
// Additional cache-related configuration
jersey.property(ServerProperties.OUTBOUND_CONTENT_LENGTH_BUFFER, 8192);
}
}public class CacheBestPractices {
// Static assets - use immutable with versioning
@GET
@Path("/static/v{version}/{file}")
@CacheControl(immutable = true)
public Response getVersionedAsset(@PathParam("version") String version,
@PathParam("file") String file) {
// Immutable because URL changes when content changes
return Response.ok(assetService.getAsset(version, file)).build();
}
// API responses - balance freshness with performance
@GET
@Path("/api/users")
@CacheControl(
maxAge = 2, maxAgeUnit = TimeUnit.MINUTES, // Fresh for 2 minutes
staleWhileRevalidate = 10, staleWhileRevalidateUnit = TimeUnit.MINUTES // Serve stale for 10 minutes while updating
)
public List<User> getUsers() {
return userService.getAllUsers();
}
// Personalized content - private caching only
@GET
@Path("/personal/dashboard")
@CacheControl(
isPrivate = true,
maxAge = 5, maxAgeUnit = TimeUnit.MINUTES,
mustRevalidate = true
)
public Dashboard getPersonalDashboard(@Context SecurityContext security) {
return dashboardService.getPersonalDashboard(security.getUserPrincipal().getName());
}
// Sensitive data - no caching
@GET
@Path("/sensitive/data")
@CacheControl(noStore = true)
public SensitiveResponse getSensitiveData() {
return sensitiveService.getData();
}
}@Path("/errors")
public class ErrorCacheResource {
@GET
@Path("/data/{id}")
public Response getData(@PathParam("id") String id) {
try {
Data data = dataService.getById(id);
return Response.ok(data)
.header("Cache-Control", "max-age=300") // Cache successful responses
.build();
} catch (NotFoundException e) {
return Response.status(404)
.entity("Not found")
.header("Cache-Control", "max-age=60") // Cache 404s briefly
.build();
} catch (Exception e) {
return Response.status(500)
.entity("Server error")
.header("Cache-Control", "no-cache") // Don't cache server errors
.build();
}
}
}Install with Tessl CLI
npx tessl i tessl/maven-io-dropwizard--dropwizard-jersey