Using Jakarta EE Interceptors

We are working on a fresh, updated Jakarta EE Tutorial. This section hasn’t yet been updated.

This chapter discusses how to create interceptor classes and methods that interpose on method invocations or lifecycle events on a target class.

Overview of Interceptors

Interceptors are used in conjunction with Jakarta EE managed classes to allow developers to invoke interceptor methods on an associated target class, in conjunction with method invocations or lifecycle events. Common uses of interceptors are logging, auditing, and profiling.

You can use interceptors with session beans, message-driven beans, and CDI managed beans. In all of these cases, the interceptor target class is the bean class.

An interceptor can be defined within a target class as an interceptor method, or in an associated class called an interceptor class. Interceptor classes contain methods that are invoked in conjunction with the methods or lifecycle events of the target class.

Interceptor classes and methods are defined using metadata annotations, or in the deployment descriptor of the application that contains the interceptors and target classes.

Applications that use the deployment descriptor to define interceptors are not portable across Jakarta EE servers.

Interceptor methods within the target class or in an interceptor class are annotated with one of the metadata annotations defined in Interceptor Metadata Annotations.

Interceptor Metadata Annotations
Interceptor Metadata Annotation Description

jakarta.interceptor.AroundConstruct

Designates the method as an interceptor method that receives a callback after the target class is constructed

jakarta.interceptor.AroundInvoke

Designates the method as an interceptor method

jakarta.interceptor.AroundTimeout

Designates the method as a timeout interceptor for interposing on timeout methods for enterprise bean timers

jakarta.annotation.PostConstruct

Designates the method as an interceptor method for post-construct lifecycle events

jakarta.annotation.PreDestroy

Designates the method as an interceptor method for pre-destroy lifecycle events

Interceptor Classes

Interceptor classes may be designated with the optional jakarta.interceptor.Interceptor annotation, but interceptor classes are not required to be so annotated. An interceptor class must have a public, no-argument constructor.

The target class can have any number of interceptor classes associated with it. The order in which the interceptor classes are invoked is determined by the order in which the interceptor classes are defined in the jakarta.interceptor.Interceptors annotation. However, this order can be overridden in the deployment descriptor.

Interceptor classes may be targets of dependency injection. Dependency injection occurs when the interceptor class instance is created, using the naming context of the associated target class, and before any @PostConstruct callbacks are invoked.

Interceptor Lifecycle

Interceptor classes have the same lifecycle as their associated target class. When a target class instance is created, an interceptor class instance is also created for each declared interceptor class in the target class. That is, if the target class declares multiple interceptor classes, an instance of each class is created when the target class instance is created. The target class instance and all interceptor class instances are fully instantiated before any @PostConstruct callbacks are invoked, and any @PreDestroy callbacks are invoked before the target class and interceptor class instances are destroyed.

Interceptors and CDI

Jakarta Contexts and Dependency Injection (CDI) builds on the basic functionality of Jakarta EE interceptors. For information on CDI interceptors, including a discussion of interceptor binding types, see Using Interceptors in CDI Applications.

Using Interceptors

To define an interceptor, use one of the interceptor metadata annotations listed in Interceptor Metadata Annotations within the target class, or in a separate interceptor class. The following code declares an @AroundTimeout interceptor method within a target class:

@Stateless
public class TimerBean {
    ...
    @Schedule(minute="*/1", hour="*")
    public void automaticTimerMethod() { ... }

    @AroundTimeout
    public void timeoutInterceptorMethod(InvocationContext ctx) { ... }
    ...
}

If you are using interceptor classes, use the jakarta.interceptor.Interceptors annotation to declare one or more interceptors at the class or method level of the target class. The following code declares interceptors at the class level:

@Stateless
@Interceptors({PrimaryInterceptor.class, SecondaryInterceptor.class})
public class OrderBean { ... }

The following code declares a method-level interceptor class:

@Stateless
public class OrderBean {
    ...
    @Interceptors(OrderInterceptor.class)
    public void placeOrder(Order order) { ... }
    ...
}

Intercepting Method Invocations

Use the @AroundInvoke annotation to designate interceptor methods for managed object methods. Only one around-invoke interceptor method per class is allowed. Around-invoke interceptor methods have the following form:

@AroundInvoke
visibility Object method-name(InvocationContext) throws Exception { ... }

For example:

@AroundInvoke
public void interceptOrder(InvocationContext ctx) { ... }

Around-invoke interceptor methods can have public, private, protected, or package-level access, and must not be declared static or final.

An around-invoke interceptor can call any component or resource that is callable by the target method on which it interposes, can have the same security and transaction context as the target method, and can run in the same Java virtual machine call stack as the target method.

Around-invoke interceptors can throw runtime exceptions and any exception allowed by the throws clause of the target method. They may catch and suppress exceptions, and then recover by calling the InvocationContext.proceed method.

Using Multiple Method Interceptors

Use the @Interceptors annotation to declare multiple interceptors for a target method or class:

@Interceptors({PrimaryInterceptor.class, SecondaryInterceptor.class,
        LastInterceptor.class})
public void updateInfo(String info) { ... }

The order of the interceptors in the @Interceptors annotation is the order in which the interceptors are invoked.

You can also define multiple interceptors in the deployment descriptor. The order of the interceptors in the deployment descriptor is the order in which the interceptors will be invoked:

...
<interceptor-binding>
    <target-name>myapp.OrderBean</target-name>
    <interceptor-class>myapp.PrimaryInterceptor.class</interceptor-class>
    <interceptor-class>myapp.SecondaryInterceptor.class</interceptor-class>
    <interceptor-class>myapp.LastInterceptor.class</interceptor-class>
    <method-name>updateInfo</method-name>
</interceptor-binding>
...

To explicitly pass control to the next interceptor in the chain, call the InvocationContext.proceed method.

Data can be shared across interceptors.

  • The same InvocationContext instance is passed as an input parameter to each interceptor method in the interceptor chain for a particular target method. The InvocationContext instance’s contextData property is used to pass data across interceptor methods. The contextData property is a java.util.Map<String, Object> object. Data stored in contextData is accessible to interceptor methods further down the interceptor chain.

  • The data stored in contextData is not sharable across separate target class method invocations. That is, a different InvocationContext object is created for each invocation of the method in the target class.

Accessing Target Method Parameters from an Interceptor Class

You can use the InvocationContext instance passed to each around-invoke method to access and modify the parameters of the target method. The parameters property of InvocationContext is an array of Object instances that corresponds to the parameter order of the target method. For example, for the following target method, the parameters property, in the InvocationContext instance passed to the around-invoke interceptor method in PrimaryInterceptor, is an Object array containing two String objects (firstName and lastName) and a Date object (date):

@Interceptors(PrimaryInterceptor.class)
public void updateInfo(String firstName, String lastName, Date date) { ... }

You can access and modify the parameters by using the InvocationContext.getParameters and InvocationContext.setParameters methods, respectively.

Intercepting Lifecycle Callback Events

Interceptors for lifecycle callback events (around-construct, post-construct, and pre-destroy) may be defined in the target class or in interceptor classes. The jakarta.interceptor.AroundConstruct annotation designates the method as an interceptor method that interposes on the invocation of the target class’s constructor. The jakarta.annotation.PostConstruct annotation is used to designate a method as a post-construct lifecycle event interceptor. The jakarta.annotation.PreDestroy annotation is used to designate a method as a pre-destroy lifecycle event interceptor.

Lifecycle event interceptors defined within the target class have the following form:

void method-name() { ... }

For example:

@PostConstruct
void initialize() { ... }

Lifecycle event interceptors defined in an interceptor class have the following form:

void method-name(InvocationContext) { ... }

For example:

@PreDestroy
void cleanup(InvocationContext ctx) { ... }

Lifecycle interceptor methods can have public, private, protected, or package-level access, and must not be declared static or final. Lifecycle interceptors may throw runtime exceptions but cannot throw checked exceptions.

Lifecycle interceptor methods are called in an unspecified security and transaction context. That is, portable Jakarta EE applications should not assume the lifecycle event interceptor method has access to a security or transaction context. Only one interceptor method for each lifecycle event (post-create and pre-destroy) is allowed per class.

Using AroundConstruct Interceptor Methods

@AroundConstruct methods are interposed on the invocation of the target class’s constructor. Methods decorated with @AroundConstruct may only be defined within interceptor classes or superclasses of interceptor classes. You may not use @AroundConstruct methods within the target class.

The @AroundConstruct method is called after dependency injection has been completed for all interceptors associated with the target class. The target class is created and the target class’s constructor injection is performed after all associated @AroundConstruct methods have called the Invocation.proceed method. At that point, dependency injection for the target class is completed, and then any @PostConstruct callback methods are invoked.

@AroundConstruct methods can access the constructed target instance after calling Invocation.proceed by calling the InvocationContext.getTarget method.

Calling methods on the target instance from an @AroundConstruct method is dangerous because dependency injection may not have completed on the target instance.

@AroundConstruct methods must call Invocation.proceed in order to create the target instance. If an @AroundConstruct method does not call Invocation.proceed, the target instance will not be created.

Using Multiple Lifecycle Callback Interceptors

You can define multiple lifecycle interceptors for a target class by specifying the interceptor classes in the @Interceptors annotation:

@Interceptors({PrimaryInterceptor.class, SecondaryInterceptor.class,
        LastInterceptor.class})
@Stateless
public class OrderBean { ... }

Data stored in the contextData property of InvocationContext is not sharable across different lifecycle events.

Intercepting Timeout Events

You can define interceptors for Enterprise Bean timer service timeout methods by using the @AroundTimeout annotation on methods in the target class or in an interceptor class. Only one @AroundTimeout method per class is allowed.

Timeout interceptors have the following form:

Object method-name(InvocationContext) throws Exception { ... }

For example:

@AroundTimeout
protected void timeoutInterceptorMethod(InvocationContext ctx) { ... }

Timeout interceptor methods can have public, private, protected, or package-level access, and must not be declared static or final.

Timeout interceptors can call any component or resource callable by the target timeout method, and are invoked in the same transaction and security context as the target method.

Timeout interceptors may access the timer object associated with the target timeout method through the InvocationContext instance’s getTimer method.

Using Multiple Timeout Interceptors

You can define multiple timeout interceptors for a given target class by specifying the interceptor classes containing @AroundTimeout interceptor methods in an @Interceptors annotation at the class level.

If a target class specifies timeout interceptors in an interceptor class, and also has an @AroundTimeout interceptor method within the target class itself, the timeout interceptors in the interceptor classes are called first, followed by the timeout interceptors defined in the target class. For example, in the following example, assume that both the PrimaryInterceptor and SecondaryInterceptor classes have timeout interceptor methods:

@Interceptors({PrimaryInterceptor.class, SecondaryInterceptor.class})
@Stateful
public class OrderBean {
    ...
    @AroundTimeout
    private void last(InvocationContext ctx) { ... }
    ...
}

The timeout interceptor in PrimaryInterceptor will be called first, followed by the timeout interceptor in SecondaryInterceptor, and finally the last method defined in the target class.

Binding Interceptors to Components

Interceptor binding types are annotations that may be applied to components to associate them with a particular interceptor. Interceptor binding types are typically custom runtime annotation types that specify the interceptor target. Use the jakarta.interceptor.InterceptorBinding annotation on the custom annotation definition and specify the target by using @Target, setting one or more of TYPE (class-level interceptors), METHOD (method-level interceptors), CONSTRUCTOR (around-construct interceptors), or any other valid target:

@InterceptorBinding
@Target({TYPE, METHOD})
@Retention(RUNTIME)
@Inherited
pubic @interface Logged { ... }

Interceptor binding types may also be applied to other interceptor binding types:

@Logged
@InterceptorBinding
@Target({TYPE, METHOD})
@Retention(RUNTIME)
@Inherited
public @interface Secured { ... }

Declaring the Interceptor Bindings on an Interceptor Class

Annotate the interceptor class with the interceptor binding type and @Interceptor to associate the interceptor binding with the interceptor class:

@Logged
@Interceptor
public class LoggingInterceptor {
    @AroundInvoke
    public Object logInvocation(InvocationContext ctx) throws Exception { ... }
    ...
}

An interceptor class may declare multiple interceptor binding types, and more than one interceptor class may declare an interceptor binding type.

If the interceptor class intercepts lifecycle callbacks, it can only declare interceptor binding types with Target(TYPE), or in the case of @AroundConstruct lifecycle callbacks, Target(CONSTRUCTOR).

Binding a Component to an Interceptor

Add the interceptor binding type annotation to the target component’s class, method, or constructor. Interceptor binding types are applied using the same rules as @Interceptor annotations:

@Logged
public class Message {
    ...
    @Secured
    public void getConfidentialMessage() { ... }
    ...
}

If the component has a class-level interceptor binding, it must not be final or have any non-static, non-private final methods. If a non-static, non-private method has an interceptor binding applied to it, it must not be final, and the component class cannot be final.

Ordering Interceptors

The order in which multiple interceptors are invoked is determined by the following rules.

  • Default interceptors are defined in a deployment descriptor, and are invoked first. They may specify the invocation order or override the order specified using annotations. Default interceptors are invoked in the order in which they are defined in the deployment descriptor.

  • The order in which the interceptor classes are listed in the @Interceptors annotation defines the order in which the interceptors are invoked. Any @Priority settings for interceptors listed within an @Interceptors annotation are ignored.

  • If the interceptor class has superclasses, the interceptors defined on the superclasses are invoked first, starting with the most general superclass.

  • Interceptor classes may set the priority of the interceptor methods by setting a value within a jakarta.annotation.Priority annotation.

  • After the interceptors defined within interceptor classes have been invoked, the target class’s constructor, around-invoke, or around-timeout interceptors are invoked in the same order as the interceptors within the @Interceptors annotation.

  • If the target class has superclasses, any interceptors defined on the superclasses are invoked first, starting with the most general superclass.

The @Priority annotation requires an int value as an element. The lower the number, the higher the priority of the associated interceptor.

The invocation order of interceptors with the same priority value is implementation-specific.

The jakarta.interceptor.Interceptor.Priority class defines the priority constants listed in Interceptor Priority Constants.

Interceptor Priority Constants
Priority Constant Value Description

PLATFORM_BEFORE

0

Interceptors defined by the Jakarta EE Platform and intended to be invoked early in the invocation chain should use the range between PLATFORM_BEFORE and LIBRARY_BEFORE. These interceptors have the highest priority.

LIBRARY_BEFORE

1000

Interceptors defined by extension libraries that should be invoked early in the interceptor chain should use the range between LIBRARY_BEFORE and APPLICATION.

APPLICATION

2000

Interceptors defined by applications should use the range between APPLICATION and LIBRARY_AFTER.

LIBRARY_AFTER

3000

Low priority interceptors defined by extension libraries should use the range between LIBRARY_AFTER and PLATFORM_AFTER.

PLATFORM_AFTER

4000

Low priority interceptors defined by the Jakarta EE Platform should have values higher than PLATFORM_AFTER.

Negative priority values are reserved by the Interceptors specification for future use, and should not be used.

The following code snippet shows how to use the priority constants in an application-defined interceptor:

@Interceptor
@Priority(Interceptor.Priority.APPLICATION+200)
public class MyInterceptor { ... }

The interceptor Example Application

The interceptor example demonstrates how to use an interceptor class, containing an @AroundInvoke interceptor method, with a stateless session bean.

The HelloBean stateless session bean is a simple enterprise bean with two business methods, getName and setName, to retrieve and modify a string. The setName business method has an @Interceptors annotation that specifies an interceptor class, HelloInterceptor, for that method:

@Interceptors(HelloInterceptor.class)
public void setName(String name) {
    this.name = name;
}

The HelloInterceptor class defines an @AroundInvoke interceptor method, modifyGreeting, that converts the string passed to HelloBean.setName to lowercase:

@AroundInvoke
public Object modifyGreeting(InvocationContext ctx) throws Exception {
    Object[] parameters = ctx.getParameters();
    String param = (String) parameters[0];
    param = param.toLowerCase();
    parameters[0] = param;
    ctx.setParameters(parameters);
    try {
        return ctx.proceed();
    } catch (Exception e) {
        logger.warning("Error calling ctx.proceed in modifyGreeting()");
        return null;
    }
}

The parameters to HelloBean.setName are retrieved and stored in an Object array by calling the InvocationContext.getParameters method. Because setName only has one parameter, it is the first and only element in the array. The string is set to lowercase and stored in the parameters array, then passed to InvocationContext.setParameters. To return control to the session bean, InvocationContext.proceed is called.

The user interface of interceptor is a JavaServer Faces web application that consists of two Facelets views: index.xhtml, which contains a form for entering the name, and response.xhtml, which displays the final name.

Running the interceptor Example

You can use either NetBeans IDE or Maven to build, package, deploy, and run the interceptor example.

To Run the interceptor Example Using NetBeans IDE

  1. Make sure that GlassFish Server has been started (see Starting and Stopping GlassFish Server).

  2. From the File menu, choose Open Project.

  3. In the Open Project dialog box, navigate to:

    jakartaee-examples/tutorial/ejb
  4. Select the interceptor folder and click Open Project.

  5. In the Projects tab, right-click the interceptor project and select Run.

    This will compile, deploy, and run the interceptor example, opening a web browser to the following URL:

    http://localhost:8080/interceptor/
  6. Enter a name into the form and click Submit.

    The name will be converted to lowercase by the method interceptor defined in the HelloInterceptor class.

To Run the interceptor Example Using Maven

  1. Make sure that GlassFish Server has been started (see Starting and Stopping GlassFish Server).

  2. Go to the following directory:

    jakartaee-examples/tutorial/ejb/interceptor/
  3. To compile the source files and package the application, use the following command:

    mvn install

    This command builds and packages the application into a WAR file, interceptor.war, located in the target directory. The WAR file is then deployed to GlassFish Server.

  4. Open the following URL in a web browser:

    http://localhost:8080/interceptor/
  5. Enter a name into the form and click Submit.

    The name will be converted to lowercase by the method interceptor defined in the HelloInterceptor class.