Java API for RESTful Web Services
—
The server container APIs provide server-side processing capabilities including request/response filtering, asynchronous processing, resource management, and dynamic configuration. These APIs enable cross-cutting concerns like authentication, logging, caching, and custom request processing.
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.container.DynamicFeature;
import javax.ws.rs.core.FeatureContext;
import javax.ws.rs.container.PreMatching;
import javax.ws.rs.container.Suspended;
import javax.ws.rs.container.AsyncResponse;
import javax.ws.rs.container.CompletionCallback;
import javax.ws.rs.container.ConnectionCallback;
import javax.ws.rs.container.TimeoutHandler;
import javax.ws.rs.container.ResourceContext;
import javax.ws.rs.container.ResourceInfo;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.Provider;
import java.io.IOException;
import java.util.concurrent.TimeUnit;Provides access to request processing information on the server side.
public interface ContainerRequestContext {
Object getProperty(String name);
Collection<String> getPropertyNames();
void setProperty(String name, Object object);
void removeProperty(String name);
UriInfo getUriInfo();
void setRequestUri(URI requestUri);
void setRequestUri(URI baseUri, URI requestUri);
Request getRequest();
String getMethod();
void setMethod(String method);
MultivaluedMap<String, String> getHeaders();
String getHeaderString(String name);
Date getDate();
Locale getLanguage();
int getLength();
MediaType getMediaType();
List<MediaType> getAcceptableMediaTypes();
List<Locale> getAcceptableLanguages();
Map<String, Cookie> getCookies();
boolean hasEntity();
InputStream getEntityStream();
void setEntityStream(InputStream input);
SecurityContext getSecurityContext();
void setSecurityContext(SecurityContext context);
void abortWith(Response response);
}Provides access to response processing information on the server side.
public interface ContainerResponseContext {
int getStatus();
void setStatus(int code);
StatusType getStatusInfo();
void setStatusInfo(StatusType statusInfo);
MultivaluedMap<String, Object> getHeaders();
MultivaluedMap<String, String> getStringHeaders();
String getHeaderString(String name);
Set<String> getAllowedMethods();
Date getDate();
Date getLastModified();
URI getLocation();
Set<Link> getLinks();
boolean hasLink(String relation);
Link getLink(String relation);
Link.Builder getLinkBuilder(String relation);
boolean hasEntity();
Object getEntity();
Class<?> getEntityClass();
Type getEntityType();
void setEntity(Object entity);
void setEntity(Object entity, Annotation[] annotations, MediaType mediaType);
Annotation[] getEntityAnnotations();
MediaType getMediaType();
OutputStream getEntityStream();
void setEntityStream(OutputStream outputStream);
}Filters incoming server requests.
@Provider
public interface ContainerRequestFilter {
void filter(ContainerRequestContext requestContext) throws IOException;
}Filters outgoing server responses.
@Provider
public interface ContainerResponseFilter {
void filter(ContainerRequestContext requestContext,
ContainerResponseContext responseContext) throws IOException;
}Filter Implementation Examples:
// Authentication filter
@Provider
@Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
String authorization = requestContext.getHeaderString("Authorization");
if (authorization == null || !authorization.startsWith("Bearer ")) {
requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED)
.entity("Authentication required")
.build());
return;
}
String token = authorization.substring("Bearer ".length());
if (!isValidToken(token)) {
requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED)
.entity("Invalid token")
.build());
}
}
private boolean isValidToken(String token) {
// Token validation logic
return tokenService.validate(token);
}
}
// Logging filter
@Provider
public class LoggingFilter implements ContainerRequestFilter, ContainerResponseFilter {
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
System.out.println("Request: " + requestContext.getMethod() + " " +
requestContext.getUriInfo().getRequestUri());
// Store request timestamp
requestContext.setProperty("request.timestamp", System.currentTimeMillis());
}
@Override
public void filter(ContainerRequestContext requestContext,
ContainerResponseContext responseContext) throws IOException {
Long startTime = (Long) requestContext.getProperty("request.timestamp");
long duration = System.currentTimeMillis() - startTime;
System.out.println("Response: " + responseContext.getStatus() +
" (took " + duration + "ms)");
}
}
// CORS filter
@Provider
@PreMatching
public class CorsFilter implements ContainerRequestFilter, ContainerResponseFilter {
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
if ("OPTIONS".equals(requestContext.getMethod())) {
requestContext.abortWith(Response.ok().build());
}
}
@Override
public void filter(ContainerRequestContext requestContext,
ContainerResponseContext responseContext) throws IOException {
responseContext.getHeaders().add("Access-Control-Allow-Origin", "*");
responseContext.getHeaders().add("Access-Control-Allow-Methods",
"GET, POST, PUT, DELETE, OPTIONS");
responseContext.getHeaders().add("Access-Control-Allow-Headers",
"Content-Type, Authorization");
}
}Marks filters to be executed before resource method matching.
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PreMatching {
}Pre-matching Filter Example:
@Provider
@PreMatching
@Priority(Priorities.HEADER_DECORATOR)
public class PreMatchingFilter implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
// Modify request before resource matching
String userAgent = requestContext.getHeaderString("User-Agent");
if (userAgent != null && userAgent.contains("mobile")) {
// Redirect mobile users to mobile API
String uri = requestContext.getUriInfo().getRequestUri().toString();
String mobileUri = uri.replace("/api/", "/mobile-api/");
requestContext.setRequestUri(URI.create(mobileUri));
}
}
}Allows runtime configuration of providers based on resource information.
public interface DynamicFeature {
void configure(ResourceInfo resourceInfo, FeatureContext context);
}Dynamic Feature Example:
@Provider
public class SecurityDynamicFeature implements DynamicFeature {
@Override
public void configure(ResourceInfo resourceInfo, FeatureContext context) {
Method method = resourceInfo.getResourceMethod();
// Apply authentication filter only to methods annotated with @Secured
if (method.isAnnotationPresent(Secured.class)) {
context.register(AuthenticationFilter.class);
}
// Apply admin filter to admin methods
if (method.isAnnotationPresent(AdminRequired.class)) {
context.register(AdminAuthorizationFilter.class, Priorities.AUTHORIZATION);
}
}
}
// Custom security annotations
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface Secured {
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface AdminRequired {
}
// Usage in resource
@Path("/admin")
public class AdminResource {
@GET
@Path("/users")
@Secured
@AdminRequired
public List<User> getAllUsers() {
return userService.findAll();
}
}Provides a context for configuring providers dynamically.
public interface FeatureContext extends Configurable<FeatureContext> {
// Inherited from Configurable<FeatureContext>:
Configuration getConfiguration();
FeatureContext property(String name, Object value);
FeatureContext register(Class<?> componentClass);
FeatureContext register(Class<?> componentClass, int priority);
FeatureContext register(Class<?> componentClass, Class<?>... contracts);
FeatureContext register(Class<?> componentClass, Map<Class<?>, Integer> contracts);
FeatureContext register(Object component);
FeatureContext register(Object component, int priority);
FeatureContext register(Object component, Class<?>... contracts);
FeatureContext register(Object component, Map<Class<?>, Integer> contracts);
}Provides asynchronous response processing capabilities.
public interface AsyncResponse {
boolean resume(Object response);
boolean resume(Throwable response);
boolean cancel();
boolean cancel(int retryAfter);
boolean cancel(Date retryAfter);
boolean isSuspended();
boolean isCancelled();
boolean isDone();
boolean setTimeout(long time, TimeUnit unit);
void setTimeoutHandler(TimeoutHandler handler);
Collection<Class<?>> register(Class<?> callback);
Map<Class<?>, Collection<Class<?>>> register(Class<?> callback, Class<?>... callbacks);
Collection<Class<?>> register(Object callback);
Map<Class<?>, Collection<Class<?>>> register(Object callback, Object... callbacks);
}Marks a parameter for AsyncResponse injection.
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Suspended {
}Asynchronous Processing Examples:
@Path("/async")
public class AsyncResource {
@Inject
private ExecutorService executorService;
// Simple async processing
@GET
@Path("/users/{id}")
public void getUser(@PathParam("id") String userId,
@Suspended AsyncResponse asyncResponse) {
executorService.submit(() -> {
try {
User user = userService.findById(userId);
asyncResponse.resume(user);
} catch (Exception e) {
asyncResponse.resume(e);
}
});
}
// Async with timeout
@GET
@Path("/expensive-operation")
public void expensiveOperation(@Suspended AsyncResponse asyncResponse) {
// Set 30 second timeout
asyncResponse.setTimeout(30, TimeUnit.SECONDS);
asyncResponse.setTimeoutHandler(ar ->
ar.resume(Response.status(Response.Status.REQUEST_TIMEOUT)
.entity("Operation timed out")
.build())
);
executorService.submit(() -> {
try {
String result = performExpensiveOperation();
asyncResponse.resume(Response.ok(result).build());
} catch (Exception e) {
asyncResponse.resume(Response.serverError()
.entity("Operation failed")
.build());
}
});
}
// Async with callbacks
@POST
@Path("/process")
public void processData(DataRequest request,
@Suspended AsyncResponse asyncResponse) {
// Register callbacks
asyncResponse.register(CompletionCallback.class);
asyncResponse.register(ConnectionCallback.class);
CompletableFuture.supplyAsync(() -> dataProcessor.process(request))
.thenAccept(asyncResponse::resume)
.exceptionally(throwable -> {
asyncResponse.resume(throwable);
return null;
});
}
}public interface CompletionCallback {
void onComplete(Throwable throwable);
}
public interface ConnectionCallback {
void onDisconnect(AsyncResponse disconnected);
}public interface TimeoutHandler {
void handleTimeout(AsyncResponse asyncResponse);
}Callback Implementation Examples:
@Provider
public class AsyncCallbacks implements CompletionCallback, ConnectionCallback {
@Override
public void onComplete(Throwable throwable) {
if (throwable == null) {
System.out.println("Async operation completed successfully");
} else {
System.err.println("Async operation failed: " + throwable.getMessage());
}
}
@Override
public void onDisconnect(AsyncResponse disconnected) {
System.out.println("Client disconnected, cancelling operation");
// Cleanup resources, cancel background tasks, etc.
}
}
// Custom timeout handler
public class CustomTimeoutHandler implements TimeoutHandler {
@Override
public void handleTimeout(AsyncResponse asyncResponse) {
// Custom timeout response
ErrorResponse error = new ErrorResponse("TIMEOUT",
"Request processing timed out");
asyncResponse.resume(Response.status(Response.Status.REQUEST_TIMEOUT)
.entity(error)
.build());
}
}Injectable helper for creating and initializing resource instances.
public interface ResourceContext {
<T> T getResource(Class<T> resourceClass);
<T> T initResource(T resource);
}Injectable interface providing runtime information about matched resource method.
public interface ResourceInfo {
Method getResourceMethod();
Class<?> getResourceClass();
}Resource Management Examples:
@Path("/composite")
public class CompositeResource {
@Context
private ResourceContext resourceContext;
@GET
@Path("/user-orders/{userId}")
public Response getUserOrders(@PathParam("userId") String userId) {
// Get other resource instances
UserResource userResource = resourceContext.getResource(UserResource.class);
OrderResource orderResource = resourceContext.getResource(OrderResource.class);
// Use resources to build composite response
User user = userResource.getUser(userId);
List<Order> orders = orderResource.getUserOrders(userId);
UserOrdersResponse response = new UserOrdersResponse(user, orders);
return Response.ok(response).build();
}
}
// Resource info usage in filter
@Provider
public class ResourceInfoFilter implements ContainerRequestFilter {
@Context
private ResourceInfo resourceInfo;
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
Method method = resourceInfo.getResourceMethod();
Class<?> resourceClass = resourceInfo.getResourceClass();
System.out.println("Processing request for " +
resourceClass.getSimpleName() + "." + method.getName());
// Apply method-specific processing
if (method.isAnnotationPresent(Audited.class)) {
// Enable auditing for this request
requestContext.setProperty("audit.enabled", true);
}
}
}Name binding allows selective application of filters and interceptors.
Name Binding Example:
// Define name binding annotation
@NameBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Compress {
}
// Filter bound to @Compress annotation
@Provider
@Compress
public class CompressionFilter implements ContainerResponseFilter {
@Override
public void filter(ContainerRequestContext requestContext,
ContainerResponseContext responseContext) throws IOException {
String acceptEncoding = requestContext.getHeaderString("Accept-Encoding");
if (acceptEncoding != null && acceptEncoding.contains("gzip")) {
// Apply gzip compression
responseContext.getHeaders().add("Content-Encoding", "gzip");
// Wrap output stream with GZIPOutputStream
}
}
}
// Apply to specific methods
@Path("/data")
public class DataResource {
@GET
@Path("/large")
@Compress // Compression filter will be applied
public LargeDataSet getLargeData() {
return dataService.getLargeDataSet();
}
@GET
@Path("/small")
// No compression for small responses
public SmallData getSmallData() {
return dataService.getSmallData();
}
}JAX-RS provides priority constants for ordering filters and interceptors.
public final class Priorities {
public static final int AUTHENTICATION = 1000;
public static final int AUTHORIZATION = 2000;
public static final int HEADER_DECORATOR = 3000;
public static final int ENTITY_CODER = 4000;
public static final int USER = 5000;
}Priority Usage Example:
@Provider
@Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter {
// Executed first (lowest priority number)
}
@Provider
@Priority(Priorities.AUTHORIZATION)
public class AuthorizationFilter implements ContainerRequestFilter {
// Executed after authentication
}
@Provider
@Priority(Priorities.HEADER_DECORATOR)
public class HeaderDecoratorFilter implements ContainerResponseFilter {
// Executed for response processing
}Install with Tessl CLI
npx tessl i tessl/maven-javax-ws-rs--javax-ws-rs-api