🛡️ Jakarta EE Interceptors: Your Code’s Secret Bodyguards
The Story: Meet the Security Team
Imagine you own a fancy restaurant. Every time a customer orders food, you want:
- Someone to check if they’re a VIP (before cooking)
- Someone to add a special garnish (after cooking)
- Someone to log what was ordered (always)
You could tell every chef to do all this… OR you could hire a dedicated team that automatically handles these tasks for EVERY dish. No chef needs to know about it!
That’s exactly what Interceptors do in Jakarta EE! 🎯
They’re like invisible helpers that run code before, after, or around your main business logic—without changing that logic at all.
🎭 What Are Interceptors?
Simple Definition: Interceptors are special classes that “intercept” (catch) method calls and do something extra—like adding seasoning to every dish automatically.
Real-Life Examples:
- 📝 Logging every action a user takes
- ⏱️ Measuring how long operations take
- 🔐 Checking permissions before sensitive operations
- 💾 Starting/ending database transactions
🏗️ Interceptor Classes: Building Your Bodyguards
An Interceptor Class is just a regular Java class with special annotations that tell Jakarta EE: “Hey, I want to intercept stuff!”
The Simplest Interceptor
import jakarta.interceptor.AroundInvoke;
import jakarta.interceptor.Interceptor;
import jakarta.interceptor.InvocationContext;
@Interceptor
public class LoggingInterceptor {
@AroundInvoke
public Object logMethod(InvocationContext ctx)
throws Exception {
System.out.println("BEFORE: " +
ctx.getMethod().getName());
Object result = ctx.proceed();
System.out.println("AFTER: " +
ctx.getMethod().getName());
return result;
}
}
What’s Happening Here?
graph TD A["Method Called"] --> B["Interceptor Catches It"] B --> C["🟡 BEFORE Logic Runs"] C --> D["ctx.proceed - Original Method Runs"] D --> E["🟢 AFTER Logic Runs"] E --> F["Result Returned"] style B fill:#ff6b6b,color:#fff style D fill:#4ecdc4,color:#fff
Key Parts:
| Part | What It Does |
|---|---|
@Interceptor |
Marks this class as an interceptor |
@AroundInvoke |
Says “run this method around business methods” |
InvocationContext |
Gives you info about what was intercepted |
ctx.proceed() |
Calls the original method |
🎯 AroundInvoke: The Main Event
@AroundInvoke is the most common interceptor type. It wraps around regular business methods.
The Recipe
Every @AroundInvoke method must:
- Return
Object - Take
InvocationContextas parameter - Call
ctx.proceed()(or throw exception) - Throw
Exception
Practical Example: Timing Methods
@Interceptor
public class TimingInterceptor {
@AroundInvoke
public Object measureTime(InvocationContext ctx)
throws Exception {
long start = System.currentTimeMillis();
try {
return ctx.proceed();
} finally {
long duration =
System.currentTimeMillis() - start;
System.out.println(
ctx.getMethod().getName() +
" took " + duration + "ms"
);
}
}
}
What Can You Do in AroundInvoke?
| Action | Example |
|---|---|
| Read parameters | ctx.getParameters() |
| Modify parameters | ctx.setParameters(newParams) |
| Skip the method | Don’t call proceed(), return value |
| Throw exception | throw new SecurityException() |
| Modify result | Change what proceed() returns |
🏭 AroundConstruct: Watching Objects Being Born
@AroundConstruct intercepts when objects are created (constructors).
Why Would You Need This?
- ✅ Validate that an object is created correctly
- ✅ Log every object creation
- ✅ Inject dependencies manually
- ✅ Measure construction time
Example: Construction Logger
@Interceptor
public class ConstructionInterceptor {
@AroundConstruct
public void logConstruction(InvocationContext ctx)
throws Exception {
System.out.println("Creating: " +
ctx.getConstructor()
.getDeclaringClass()
.getSimpleName()
);
ctx.proceed();
System.out.println("Created successfully!");
}
}
Key Difference from AroundInvoke
| Feature | AroundInvoke | AroundConstruct |
|---|---|---|
| Returns | Object |
void |
| Intercepts | Methods | Constructors |
| Get target | ctx.getTarget() |
null (not created yet!) |
Remember: In @AroundConstruct, the object doesn’t exist yet before proceed()!
⏰ AroundTimeout: Catching Timer Events
@AroundTimeout intercepts scheduled timer methods (like cron jobs in enterprise apps).
When Timers Fire
@Stateless
public class ReportGenerator {
@Schedule(hour = "9", minute = "0")
public void generateDailyReport() {
// Runs every day at 9 AM
System.out.println("Generating report...");
}
}
Intercepting Timer Callbacks
@Interceptor
public class TimerInterceptor {
@AroundTimeout
public Object handleTimeout(InvocationContext ctx)
throws Exception {
System.out.println("⏰ Timer fired: " +
ctx.getMethod().getName());
try {
return ctx.proceed();
} catch (Exception e) {
System.out.println("⚠️ Timer failed!");
// Maybe retry or notify admin
throw e;
}
}
}
Use Cases for AroundTimeout
| Use Case | What You’d Do |
|---|---|
| Monitoring | Log when timers fire |
| Error handling | Catch and handle timer failures |
| Metrics | Track timer execution duration |
| Circuit breaker | Skip if system is overloaded |
📋 Interceptor Ordering: Who Goes First?
When you have multiple interceptors, the order matters! It’s like a security checkpoint with multiple guards.
The Problem
Interceptor A → Interceptor B → Interceptor C → Your Method
Which runs first? Second? Last?
Solution: @Priority
import jakarta.annotation.Priority;
import jakarta.interceptor.Interceptor;
@Interceptor
@Priority(100)
public class SecurityInterceptor {
// Runs FIRST (lowest number)
}
@Interceptor
@Priority(200)
public class LoggingInterceptor {
// Runs SECOND
}
@Interceptor
@Priority(300)
public class TimingInterceptor {
// Runs THIRD (highest number)
}
How Priority Works
graph TD A["Method Call"] --> B["Priority 100<br/>SecurityInterceptor"] B --> C["Priority 200<br/>LoggingInterceptor"] C --> D["Priority 300<br/>TimingInterceptor"] D --> E["Actual Method"] E --> D D --> C C --> B B --> F["Return to Caller"] style B fill:#ff6b6b,color:#fff style C fill:#feca57,color:#000 style D fill:#48dbfb,color:#000 style E fill:#1dd1a1,color:#fff
Standard Priority Ranges
| Range | Purpose |
|---|---|
| 1000-1999 | Platform interceptors |
| 2000-2999 | Library interceptors |
| 3000-3999 | Application interceptors |
Ordering Without @Priority
If you don’t use @Priority, use @Interceptors annotation to specify order:
@Interceptors({
SecurityInterceptor.class,
LoggingInterceptor.class,
TimingInterceptor.class
})
public class MyService {
// Interceptors run in the order listed
}
🔗 Binding Interceptors to Classes
Method 1: @Interceptors Annotation
import jakarta.interceptor.Interceptors;
@Interceptors(LoggingInterceptor.class)
public class OrderService {
public void createOrder() {
// Logging interceptor runs here
}
}
Method 2: Interceptor Bindings (Cleaner!)
Step 1: Create a binding annotation
import jakarta.interceptor.InterceptorBinding;
import java.lang.annotation.*;
@InterceptorBinding
@Target({TYPE, METHOD})
@Retention(RUNTIME)
public @interface Logged {
}
Step 2: Bind interceptor to annotation
@Interceptor
@Logged
@Priority(2000)
public class LoggingInterceptor {
@AroundInvoke
public Object log(InvocationContext ctx)
throws Exception {
// logging logic
return ctx.proceed();
}
}
Step 3: Use the annotation
@Logged
public class OrderService {
// All methods are now logged!
}
// Or on specific methods:
public class UserService {
@Logged
public void sensitiveOperation() {
// Only this method is logged
}
}
🎓 Complete Example: Building a Security System
Let’s build a real-world interceptor system for an e-commerce app:
// Binding annotation
@InterceptorBinding
@Target({TYPE, METHOD})
@Retention(RUNTIME)
public @interface Secured {
}
// Security interceptor
@Interceptor
@Secured
@Priority(1000)
public class SecurityInterceptor {
@AroundInvoke
public Object checkSecurity(InvocationContext ctx)
throws Exception {
// Check if user is authenticated
if (!isUserAuthenticated()) {
throw new SecurityException(
"Please log in first!"
);
}
return ctx.proceed();
}
private boolean isUserAuthenticated() {
// Check authentication logic
return true;
}
}
// Using it
@Secured
public class PaymentService {
public void processPayment(Order order) {
// This is now protected!
System.out.println("Processing payment...");
}
}
🧠 Quick Memory Tips
| Type | Remember As | When It Runs |
|---|---|---|
@AroundInvoke |
“Around methods” | Before/after regular methods |
@AroundConstruct |
“Around birth” | When objects are created |
@AroundTimeout |
“Around alarms” | When timers fire |
@Priority(n) |
“Queue number” | Lower = runs first |
🚀 Key Takeaways
- Interceptors = Invisible Helpers - They add behavior without changing your business code
- AroundInvoke is King - Used 90% of the time for cross-cutting concerns
- AroundConstruct for Creation - Watch objects being born
- AroundTimeout for Timers - Handle scheduled tasks
- Priority Matters - Lower numbers run first
- Bindings are Cleaner - Use custom annotations instead of
@Interceptors
🎯 The Big Picture
graph TD subgraph "Your Code" A["Business Method"] end subgraph "Interceptor Chain" B["Security Check"] C["Transaction Start"] D["Logging"] E["Timing"] end Client --> B B --> C C --> D D --> E E --> A A --> E E --> D D --> C C --> B B --> Client style A fill:#1dd1a1,color:#fff style B fill:#ff6b6b,color:#fff style C fill:#feca57,color:#000 style D fill:#48dbfb,color:#000 style E fill:#a55eea,color:#fff
You’re now ready to build powerful, clean enterprise applications with interceptors! 🎉
The beauty is: your business code stays clean and focused, while interceptors handle all the repetitive cross-cutting concerns. That’s the Jakarta EE way!
