Skip to content

Filters & Interceptors Examples

Here’s a clean separation of Filters and Interceptors with their types:


FILTERS (Work with Headers, URIs, Methods)

Section titled “FILTERS (Work with Headers, URIs, Methods)”

Filters operate at the HTTP metadata level. They cannot modify the message body (the actual data payload).

TypeInterfaceWhen It RunsWhat It Can Do
Request FilterContainerRequestFilterBefore the resource method executesRead/modify headers, URI, method; abort request early
Response FilterContainerResponseFilterAfter the resource method executes, before response is sentRead/modify response headers; modify status code
TypeInterfaceWhen It RunsWhat It Can Do
Request FilterClientRequestFilterBefore sending request to remote serverModify headers, URI, method; abort the request
Response FilterClientResponseFilterAfter receiving response from remote serverRead/modify response headers; process response
// Server Request Filter - Authentication
@Provider
public class AuthFilter implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext ctx) {
String token = ctx.getHeaderString("Authorization");
if (token == null) {
ctx.abortWith(Response.status(401).build()); // Abort early
}
}
}
// Server Response Filter - Add custom header
@Provider
public class HeaderFilter implements ContainerResponseFilter {
@Override
public void filter(ContainerRequestContext req, ContainerResponseContext res) {
res.getHeaders().add("X-Powered-By", "Quarkus");
}
}
// Client Request Filter - Add auth token
@Provider
public class ClientTokenFilter implements ClientRequestFilter {
@Override
public void filter(ClientRequestContext ctx) {
ctx.getHeaders().add("Authorization", "Bearer my-token");
}
}

INTERCEPTORS (Work with Message Body/Streams)

Section titled “INTERCEPTORS (Work with Message Body/Streams)”

Interceptors operate at the entity stream level. They can read and modify the actual data being sent or received.

TypeInterfaceWhen It RunsWhat It Can Do
Reader InterceptorReaderInterceptorAround reading the request bodyRead/modify the request entity stream before deserialization
Writer InterceptorWriterInterceptorAround writing the response bodyModify the response entity stream before serialization
TypeInterfaceWhen It RunsWhat It Can Do
Reader InterceptorReaderInterceptorAround reading the response bodyRead/modify response entity stream after receiving
Writer InterceptorWriterInterceptorAround writing the request bodyModify request entity stream before sending
// Server Writer Interceptor - Compress response body
@Provider
public class CompressionInterceptor implements WriterInterceptor {
@Override
public void aroundWriteTo(WriterInterceptorContext context)
throws IOException, WebApplicationException {
// Get the output stream
OutputStream output = context.getOutputStream();
// Wrap it with compression
GZIPOutputStream gzipOutput = new GZIPOutputStream(output);
context.setOutputStream(gzipOutput);
// Add compression header
context.getHeaders().add("Content-Encoding", "gzip");
// Proceed with writing
context.proceed();
}
}
// Server Reader Interceptor - Log request body
@Provider
public class LoggingReaderInterceptor implements ReaderInterceptor {
@Override
public Object aroundReadFrom(ReaderInterceptorContext context)
throws IOException, WebApplicationException {
// Read the input stream
InputStream input = context.getInputStream();
String body = new String(input.readAllBytes());
System.out.println("Request body: " + body);
// Reset the stream for actual processing
context.setInputStream(new ByteArrayInputStream(body.getBytes()));
// Proceed with reading
return context.proceed();
}
}

AspectFiltersInterceptors
Work WithHeaders, URI, MethodMessage body (data stream)
Can Abort Request✅ Yes❌ No
Can Modify Body❌ No✅ Yes
Run AtBefore/after resource methodDuring read/write of body
Common UsesAuth, logging, CORS, rate limitingCompression, encryption, logging payloads

You can also use DynamicFeature to conditionally apply filters to specific endpoints:

@Provider
public class MyDynamicFeature implements DynamicFeature {
@Override
public void configure(ResourceInfo resourceInfo, FeatureContext context) {
// Only apply logging filter to methods with @Log annotation
if (resourceInfo.getResourceMethod().isAnnotationPresent(Log.class)) {
context.register(new LoggingFilter());
}
}
}

  • Filter = “I care about the envelope” (headers, metadata)
  • Interceptor = “I care about the letter inside” (the actual data)

Here’s a real-world scenario combining both Filters and Interceptors in a single Quarkus application:

Section titled “Here’s a real-world scenario combining both Filters and Interceptors in a single Quarkus application:”

The Scenario: Secure API with Logging & Encryption

Section titled “The Scenario: Secure API with Logging & Encryption”

Imagine you’re building an API that:

  • Authenticates users via API keys (Filter)
  • Logs all requests and responses (Filter)
  • Encrypts sensitive request/response data (Interceptor)
  • Compresses large responses (Interceptor)

package com.example.resource;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
@Path("/api/users")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class UserResource {
@POST
@Path("/create")
public Response createUser(User user) {
System.out.println(">>> Creating user: " + user.getName());
// Simulate saving to database
user.setId(123L);
return Response.status(201)
.entity(user)
.build();
}
@GET
@Path("/{id}")
public Response getUser(@PathParam("id") Long id) {
System.out.println(">>> Getting user with ID: " + id);
// Simulate fetching from database
User user = new User(id, "John Doe", "john@example.com");
return Response.ok(user).build();
}
}
// Simple POJO
class User {
private Long id;
private String name;
private String email;
public User() {}
public User(Long id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
// Getters and setters...
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}

package com.example.filter;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerRequestFilter;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.Provider;
import java.io.IOException;
@Provider
public class AuthenticationFilter implements ContainerRequestFilter {
private static final String VALID_API_KEY = "secret-api-key-123";
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
// Check if it's a public endpoint (skip auth for health checks)
String path = requestContext.getUriInfo().getPath();
if (path.equals("/health")) {
return; // Skip authentication
}
// Get API key from header
String apiKey = requestContext.getHeaderString("X-API-Key");
if (apiKey == null || !apiKey.equals(VALID_API_KEY)) {
// Abort the request - this prevents resource method from executing
requestContext.abortWith(
Response.status(401)
.header("WWW-Authenticate", "API-Key")
.entity("{\"error\": \"Invalid or missing API Key\"}")
.build()
);
}
System.out.println("✅ Authentication passed");
}
}

Logging Filter - Logs request and response

Section titled “Logging Filter - Logs request and response”
package com.example.filter;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerRequestFilter;
import jakarta.ws.rs.container.ContainerResponseContext;
import jakarta.ws.rs.container.ContainerResponseFilter;
import jakarta.ws.rs.ext.Provider;
import java.io.IOException;
@Provider
public class LoggingFilter implements ContainerRequestFilter, ContainerResponseFilter {
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
System.out.println("=" .repeat(50));
System.out.println("📤 REQUEST:");
System.out.println(" Method: " + requestContext.getMethod());
System.out.println(" URI: " + requestContext.getUriInfo().getRequestUri());
System.out.println(" Headers: " + requestContext.getHeaders());
}
@Override
public void filter(ContainerRequestContext requestContext,
ContainerResponseContext responseContext) throws IOException {
System.out.println("📥 RESPONSE:");
System.out.println(" Status: " + responseContext.getStatus());
System.out.println(" Headers: " + responseContext.getHeaders());
System.out.println("=" .repeat(50));
}
}
package com.example.filter;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerResponseContext;
import jakarta.ws.rs.container.ContainerResponseFilter;
import jakarta.ws.rs.ext.Provider;
import java.io.IOException;
@Provider
public class CORSFilter implements ContainerResponseFilter {
@Override
public void filter(ContainerRequestContext request,
ContainerResponseContext response) throws IOException {
response.getHeaders().add("Access-Control-Allow-Origin", "*");
response.getHeaders().add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
response.getHeaders().add("Access-Control-Allow-Headers", "X-API-Key, Content-Type");
}
}

Encryption Interceptor - Encrypts/Decrypts data

Section titled “Encryption Interceptor - Encrypts/Decrypts data”
package com.example.interceptor;
import jakarta.ws.rs.ext.Provider;
import jakarta.ws.rs.ext.ReaderInterceptor;
import jakarta.ws.rs.ext.ReaderInterceptorContext;
import jakarta.ws.rs.ext.WriterInterceptor;
import jakarta.ws.rs.ext.WriterInterceptorContext;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
@Provider
public class EncryptionInterceptor implements ReaderInterceptor, WriterInterceptor {
private static final String SECRET_KEY = "simple-key-123"; // In real app, use proper encryption
@Override
public Object aroundReadFrom(ReaderInterceptorContext context)
throws IOException {
System.out.println("🔓 Decrypting request body...");
// Read the encrypted body
InputStream input = context.getInputStream();
String encrypted = new String(input.readAllBytes(), StandardCharsets.UTF_8);
// Simple "decryption" (Base64 decode + reverse for demo)
String decrypted = decrypt(encrypted);
System.out.println(" Decrypted: " + decrypted);
// Replace the input stream with decrypted data
context.setInputStream(
new ByteArrayInputStream(decrypted.getBytes(StandardCharsets.UTF_8))
);
return context.proceed();
}
@Override
public void aroundWriteTo(WriterInterceptorContext context)
throws IOException {
System.out.println("🔐 Encrypting response body...");
// Get the output stream
ByteArrayOutputStream baos = new ByteArrayOutputStream();
context.setOutputStream(baos);
// Let the normal writing happen
context.proceed();
// Get the written data and encrypt it
String response = baos.toString(StandardCharsets.UTF_8);
String encrypted = encrypt(response);
System.out.println(" Original: " + response);
System.out.println(" Encrypted: " + encrypted);
// Write encrypted data to real output stream
context.getOutputStream().write(encrypted.getBytes(StandardCharsets.UTF_8));
}
private String encrypt(String data) {
// Simple demo: Base64 encode + reverse
String reversed = new StringBuilder(data).reverse().toString();
return Base64.getEncoder().encodeToString(reversed.getBytes());
}
private String decrypt(String data) {
// Simple demo: Base64 decode + reverse
String decoded = new String(Base64.getDecoder().decode(data));
return new StringBuilder(decoded).reverse().toString();
}
}

Compression Interceptor - Compresses large responses

Section titled “Compression Interceptor - Compresses large responses”
package com.example.interceptor;
import jakarta.ws.rs.ext.Provider;
import jakarta.ws.rs.ext.WriterInterceptor;
import jakarta.ws.rs.ext.WriterInterceptorContext;
import java.io.IOException;
import java.util.zip.GZIPOutputStream;
@Provider
public class CompressionInterceptor implements WriterInterceptor {
@Override
public void aroundWriteTo(WriterInterceptorContext context)
throws IOException {
System.out.println("🗜️ Compressing response...");
// Only compress large responses (size > 1024 bytes)
// In real implementation, you'd check content length
// Wrap output stream with GZIP compression
GZIPOutputStream gzipOutput = new GZIPOutputStream(context.getOutputStream());
context.setOutputStream(gzipOutput);
// Add encoding header
context.getHeaders().add("Content-Encoding", "gzip");
// Proceed with writing
context.proceed();
// Finish compression
gzipOutput.finish();
}
}

Terminal window
# Send a request with API key and encrypted data
curl -X POST http://localhost:8080/api/users/create \
-H "X-API-Key: secret-api-key-123" \
-H "Content-Type: application/json" \
-d '{"name":"Alice","email":"alice@example.com"}'
==================================================
📤 REQUEST:
Method: POST
URI: http://localhost:8080/api/users/create
Headers: {X-API-Key=[secret-api-key-123], Content-Type=[application/json]}
✅ Authentication passed
🔓 Decrypting request body...
Decrypted: {"name":"Alice","email":"alice@example.com"}
>>> Creating user: Alice
🔐 Encrypting response body...
Original: {"id":123,"name":"Alice","email":"alice@example.com"}
Encrypted: bW9jLmVsYW1wbGVAaW1leGFjZS5jb20iLCJuYW1lIjoiQWxpY2UiLCJpZCI6MTIzfQ==
🗜️ Compressing response...
📥 RESPONSE:
Status: 201
Headers: {Access-Control-Allow-Origin=[*], Content-Encoding=[gzip], ...}
==================================================

HTTP Request (Raw JSON)
[Filter: Authentication] → Checks X-API-Key header
[Filter: Logging] → Logs request details
[Interceptor: Encryption] → Decrypts the request body
[Resource Method] → Creates user (business logic)
[Interceptor: Encryption] → Encrypts the response body
[Interceptor: Compression] → Compresses the encrypted data
[Filter: CORS] → Adds CORS headers
[Filter: Logging] → Logs response details
HTTP Response (Compressed, Encrypted JSON)

ComponentWhat It DoesOrder
Authentication FilterValidates API key1️⃣ First
Logging Filter (Request)Logs incoming request2️⃣ Second
Encryption InterceptorDecrypts request body3️⃣ Third
Resource MethodBusiness logic4️⃣ Fourth
Encryption InterceptorEncrypts response body5️⃣ Fifth
Compression InterceptorCompresses response6️⃣ Sixth
CORS FilterAdds CORS headers7️⃣ Seventh
Logging Filter (Response)Logs outgoing response8️⃣ Eighth

Client-Side Example (Calling External API)

Section titled “Client-Side Example (Calling External API)”
package com.example.client;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import org.eclipse.microprofile.rest.client.inject.RegisterRestProvider;
@Path("/external/api")
@RegisterRestProvider(ClientAuthFilter.class) // Filter
@RegisterRestProvider(ClientEncryptionInterceptor.class) // Interceptor
public interface ExternalServiceClient {
@POST
@Path("/data")
String sendData(String encryptedData);
}
// Client Authentication Filter
@Provider
public class ClientAuthFilter implements ClientRequestFilter {
@Override
public void filter(ClientRequestContext ctx) {
ctx.getHeaders().add("X-API-Key", "client-secret-key");
}
}
// Client Encryption Interceptor
@Provider
public class ClientEncryptionInterceptor implements WriterInterceptor {
@Override
public void aroundWriteTo(WriterInterceptorContext ctx) throws IOException {
// Encrypt data before sending to external API
// Similar to server version...
ctx.proceed();
}
}

This example shows how Filters and Interceptors work together in a real Quarkus application. Filters handle headers/metadata (auth, logging, CORS), while Interceptors handle the actual data (encryption, compression).


Absolutely! Controlling the execution order is crucial when you have multiple filters and interceptors. Let me show you how to control the order using @Priority annotations.


Understanding Priority in Quarkus/Jakarta REST

Section titled “Understanding Priority in Quarkus/Jakarta REST”
Priority ValueWhen It RunsExecution Order
Lower numberRuns EARLIERFor request filters: Smallest → Largest
Higher numberRuns LATERFor response filters: Largest → Smallest (reverse)

Key Rule:

  • Request Filters: Execute in ascending priority order (1 → 100 → 1000)
  • Response Filters: Execute in descending priority order (1000 → 100 → 1)
  • Interceptors: Execute in ascending priority order (1 → 100 → 1000)

Let’s assign priorities to our previous example:

package com.example.filter;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerRequestFilter;
import jakarta.ws.rs.container.ContainerResponseContext;
import jakarta.ws.rs.container.ContainerResponseFilter;
import jakarta.ws.rs.ext.Provider;
import jakarta.annotation.Priority;
import java.io.IOException;
// PRIORITY = 1 (HIGHEST - Runs FIRST for Request, LAST for Response)
@Provider
@Priority(1)
public class AuthenticationFilter implements ContainerRequestFilter, ContainerResponseFilter {
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
System.out.println("🔥 [Priority 1] Authentication Filter - REQUEST (First)");
// Auth logic...
}
@Override
public void filter(ContainerRequestContext requestContext,
ContainerResponseContext responseContext) throws IOException {
System.out.println("🔥 [Priority 1] Authentication Filter - RESPONSE (Last)");
// Auth cleanup...
}
}
package com.example.filter;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerRequestFilter;
import jakarta.ws.rs.container.ContainerResponseContext;
import jakarta.ws.rs.container.ContainerResponseFilter;
import jakarta.ws.rs.ext.Provider;
import jakarta.annotation.Priority;
import java.io.IOException;
// PRIORITY = 100 (MEDIUM)
@Provider
@Priority(100)
public class LoggingFilter implements ContainerRequestFilter, ContainerResponseFilter {
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
System.out.println("📝 [Priority 100] Logging Filter - REQUEST (Middle)");
// Logging logic...
}
@Override
public void filter(ContainerRequestContext requestContext,
ContainerResponseContext responseContext) throws IOException {
System.out.println("📝 [Priority 100] Logging Filter - RESPONSE (Middle)");
// Logging logic...
}
}
package com.example.filter;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerResponseContext;
import jakarta.ws.rs.container.ContainerResponseFilter;
import jakarta.ws.rs.ext.Provider;
import jakarta.annotation.Priority;
import java.io.IOException;
// PRIORITY = 1000 (LOWEST - Runs LAST for Request, FIRST for Response)
@Provider
@Priority(1000)
public class CORSFilter implements ContainerResponseFilter {
@Override
public void filter(ContainerRequestContext request,
ContainerResponseContext response) throws IOException {
System.out.println("🌐 [Priority 1000] CORS Filter - RESPONSE (First)");
response.getHeaders().add("Access-Control-Allow-Origin", "*");
}
}
package com.example.interceptor;
import jakarta.ws.rs.ext.Provider;
import jakarta.ws.rs.ext.ReaderInterceptor;
import jakarta.ws.rs.ext.ReaderInterceptorContext;
import jakarta.ws.rs.ext.WriterInterceptor;
import jakarta.ws.rs.ext.WriterInterceptorContext;
import jakarta.annotation.Priority;
import java.io.*;
// PRIORITY = 50 (Runs before Logging, after Auth)
@Provider
@Priority(50)
public class EncryptionInterceptor implements ReaderInterceptor, WriterInterceptor {
@Override
public Object aroundReadFrom(ReaderInterceptorContext context) throws IOException {
System.out.println("🔓 [Priority 50] Encryption Interceptor - REQUEST (After Auth, Before Logging)");
// Decryption logic...
return context.proceed();
}
@Override
public void aroundWriteTo(WriterInterceptorContext context) throws IOException {
System.out.println("🔐 [Priority 50] Encryption Interceptor - RESPONSE (After Logging, Before CORS)");
// Encryption logic...
context.proceed();
}
}

HTTP Request
┌─────────────────────────────────────────────────────────────┐
│ REQUEST FILTERS (Ascending Priority: Smallest → Largest) │
├─────────────────────────────────────────────────────────────┤
│ 1️⃣ [Priority 1] Authentication Filter ← FIRST │
│ 2️⃣ [Priority 50] Encryption Interceptor │
│ 3️⃣ [Priority 100] Logging Filter │
│ 4️⃣ [Priority 1000] CORS Filter (doesn't run on request) │
└─────────────────────────────────────────────────────────────┘
Resource Method (Business Logic)
Resource Method returns response
┌─────────────────────────────────────────────────────────────┐
│ RESPONSE FILTERS (Descending Priority: Largest → Smallest) │
├─────────────────────────────────────────────────────────────┤
│ 1️⃣ [Priority 1000] CORS Filter ← FIRST │
│ 2️⃣ [Priority 100] Logging Filter │
│ 3️⃣ [Priority 50] Encryption Interceptor │
│ 4️⃣ [Priority 1] Authentication Filter ← LAST │
└─────────────────────────────────────────────────────────────┘
HTTP Response
REQUEST SIDE (Priority: Low → High)
───────────────────────────────────────────────────────────
1️⃣ [PRIORITY 1] 🔥 Authentication Filter → Checks API Key
2️⃣ [PRIORITY 50] 🔓 Encryption Interceptor → Decrypts body
3️⃣ [PRIORITY 100] 📝 Logging Filter → Logs request
4️⃣ [PRIORITY 1000] (No Request Filter)
💼 BUSINESS LOGIC (Resource Method executes)
→ Creates user, fetches data, etc.
RESPONSE SIDE (Priority: High → Low)
───────────────────────────────────────────────────────────
5️⃣ [PRIORITY 1000] 🌐 CORS Filter → Adds CORS headers
6️⃣ [PRIORITY 100] 📝 Logging Filter → Logs response
7️⃣ [PRIORITY 50] 🔐 Encryption Interceptor → Encrypts body
8️⃣ [PRIORITY 1] 🔥 Authentication Filter → Auth cleanup
───────────────────────────────────────────────────────────

When you make a request, you’ll see this output:

🟢 REQUEST PHASE:
🔥 [Priority 1] Authentication Filter - REQUEST (First)
🔓 [Priority 50] Encryption Interceptor - REQUEST (After Auth, Before Logging)
📝 [Priority 100] Logging Filter - REQUEST (Middle)
💼 Resource Method: Creating user...
🔴 RESPONSE PHASE:
🌐 [Priority 1000] CORS Filter - RESPONSE (First)
📝 [Priority 100] Logging Filter - RESPONSE (Middle)
🔐 [Priority 50] Encryption Interceptor - RESPONSE (After Logging, Before CORS)
🔥 [Priority 1] Authentication Filter - RESPONSE (Last)

Jakarta REST provides constants for common priorities:

import jakarta.ws.rs.Priorities;
@Provider
@Priority(Priorities.AUTHENTICATION) // = 1000
public class AuthFilter { }
@Provider
@Priority(Priorities.AUTHORIZATION) // = 2000
public class AuthzFilter { }
@Provider
@Priority(Priorities.HEADER_DECORATOR) // = 3000
public class HeaderFilter { }
@Provider
@Priority(Priorities.ENTITY_CODER) // = 4000
public class CompressionInterceptor { }
@Provider
@Priority(Priorities.USER) // = 5000
public class LoggingFilter { }
@Provider
@Priority(Priorities.VALIDATION) // = 6000
public class ValidationFilter { }
ConstantValueTypical Use
Priorities.AUTHENTICATION1000Authentication filters
Priorities.AUTHORIZATION2000Authorization/role checks
Priorities.HEADER_DECORATOR3000Adding/modifying headers
Priorities.ENTITY_CODER4000Encryption/decryption, compression
Priorities.USER5000Custom business logic, logging
Priorities.VALIDATION6000Request/response validation

You can create custom annotations to apply specific priorities to specific endpoints:

package com.example.annotation;
import jakarta.ws.rs.NameBinding;
import java.lang.annotation.*;
@NameBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Secured { }
@NameBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Logged { }
@Provider
@Secured // Only applies to endpoints with @Secured
@Priority(Priorities.AUTHENTICATION)
public class SecurityFilter implements ContainerRequestFilter {
// Auth logic...
}
@Provider
@Logged // Only applies to endpoints with @Logged
@Priority(Priorities.USER)
public class DetailedLoggingFilter implements ContainerRequestFilter {
// Detailed logging...
}
@Path("/api/users")
public class UserResource {
@POST
@Secured // SecurityFilter runs
@Logged // DetailedLoggingFilter runs
public Response createUser(User user) {
// Only authenticated, logged requests
}
@GET
@Logged // Only logging filter runs (no auth needed)
public Response getUsers() {
// Public endpoint, just logging
}
@GET
@Path("/public")
public Response publicInfo() {
// No filters apply (completely public)
}
}

RuleExplanation
Request FiltersExecute in ascending priority (1 → 1000)
Response FiltersExecute in descending priority (1000 → 1)
Interceptors (Reader)Execute in ascending priority (1 → 1000)
Interceptors (Writer)Execute in ascending priority (1 → 1000)
Global vs NamedGlobal (@Provider) runs before named (@NameBinding)
Same PriorityOrder is undefined (avoid having same priority)

  1. Use Standard Constants when possible for readability

    @Priority(Priorities.AUTHENTICATION) // ✅ Good
    @Priority(1000) // ✅ Also fine but less clear
    @Priority(5) // ⚠️ Avoid - too low
  2. Leave Gaps in your priorities for flexibility

    @Priority(100) // Authentication
    @Priority(300) // Authorization (gap for future interceptors)
    @Priority(500) // Logging
  3. Group Related Functionality

    // Security group: 1000-1999
    @Priority(1000) // AuthN
    @Priority(1100) // AuthZ
    // Data processing: 2000-2999
    @Priority(2000) // Decryption
    @Priority(2100) // Validation
    // Logging: 3000-3999
    @Priority(3000) // Request Logging
    @Priority(3100) // Response Logging

Here’s a fully functional example with all priorities:

@ApplicationScoped
@Path("/test")
public class TestResource {
@GET
public String test() {
System.out.println("💼 RESOURCE METHOD: Executing business logic");
return "Hello World!";
}
}
@Provider
@Priority(1)
public class FilterA implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext ctx) {
System.out.println("1️⃣ FilterA - Request (First)");
}
}
@Provider
@Priority(2)
public class FilterB implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext ctx) {
System.out.println("2️⃣ FilterB - Request (Second)");
}
}
// ... etc

Output when calling /test:

1️⃣ FilterA - Request (First)
2️⃣ FilterB - Request (Second)
💼 RESOURCE METHOD: Executing business logic