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

http-caching.mddocs/

HTTP Caching

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.

Capabilities

CacheControl Annotation

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();
    }
}

CacheControlledResponseFeature

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);
}

Cache Control Strategies

Public Content Caching

@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();
    }
}

Private Content Caching

@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);
    }
}

Dynamic Content Caching

@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();
    }
}

Conditional Caching

@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();
    }
}

Advanced Caching Patterns

Cache Invalidation

@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();
    }
}

Multi-tier Caching

@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);
    }
}

Configuration and Best Practices

Cache Configuration

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);
    }
}

Cache Header Best Practices

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();
    }
}

Error Response Caching

@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

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