4 Programming the Annotated EJB Class

This chapter describes how to program the annotated EJB class file.

This chapter includes the following sections:

Overview of Metadata Annotations and EJB Bean Files

The WebLogic Server EJB programming model uses the Jakarta EE metadata annotations feature in which you create an annotated EJB 4.0 bean file, compile the class with the standard Java compiler, and the resulting class can then be packaged into a target module for deployment. At runtime, WebLogic Server parses the annotations and applies the required behavioral aspects to the bean file.

Tip:

To reduce deployment time, you can also use the WebLogic compile tool weblogic.appc (or its Ant equivalent wlappc) to pre-compile a deployable archive file, (WAR, JAR, or EAR). Precompiling with weblogic.appc generates certain helper classes and performs validation checks to ensure your application is compliant.

The annotated 4.0 bean file is the core of your EJB. It contains the Java code that determines how your EJB behaves. The 4.0 bean file is an ordinary Java class file that implements an EJB business interface that outlines the business methods of your EJB. You then annotate the bean file with JDK metadata annotations to specify the shape and characteristics of the EJB, document your EJB, and provide special services such as enhanced business-level security or special business logic during runtime.

See EJB Metadata Annotations Reference for reference information about the annotations, listed in alphabetical order. This topic is part of the iterative development procedure for creating an EJB, described in Iterative Development of EJBs.

See also the annotations described by the following specifications:

Programming the Bean File: Requirements and Changes From EJB 2.x

The requirements for programming the Jakarta Enterprise Beans 4.0 bean class file are essentially the same as the EJB 2.x requirements. This section briefly describes the basic mandatory requirements of the bean class, mostly for overview purposes, as well as changes in requirements between EJB 2.x and Jakarta Enterprise Beans 4.0.

See Developing Jakarta Enterprise Beans Using Deployment Descriptors for detailed information about the mandatory and optional requirements for programming the bean class.

Bean Class Requirements and Changes From EJB 2.x

The following bullets list the EJB requirements for programming a bean class, as well as the EJB 2.x requirements that no longer apply:

  • The bean type must be specified either by exactly one component-defining annotation @jakarta.ejb.Stateless, @jakarta.ejb.Stateful, @jakarta.ejb.Singleton, or @jakarta.ejb.MessageDriven.

    Or, when the bean class has no component-defining annotation, by the corresponding session or message-driven entry in ejb-jar.xml. A deployment descriptor cannot override a component-defining annotation.

    Note:

    WebLogic Server ships the Jakarta Persistence 3.2 API and EclipseLink 5.0.1. EclipseLink is the default persistence provider unless the application selects another supported provider in persistence.xml or the domain configures a different default. A Jakarta Persistence entity is annotated with @jakarta.persistence.Entity.

    Do not confuse a Jakarta Persistence entity with an optional Enterprise Beans 2.x entity bean, which uses the jakarta.ejb.EntityBean component model and deployment descriptors.

    See Configuring the Persistence Provider in Oracle WebLogic Server.

  • If the bean is a session bean, the bean class can implement either:

    • The no-interface local client view type, which simplifies EJB development by providing local session bean access without requiring a separate local business interface.

    • The bean's business interface(s) or the methods of the bean's business interface(s), if any.

  • Session beans no longer need to implement jakarta.ejb.SessionBean, which means the bean no longer needs to implement the ejbXXX() methods, such as ejbCreate(), ejbPassivate(), and so on.

  • Stateful session beans no longer need to implement java.io.Serializable.

  • Message-driven beans no longer need to implement jakarta.ejb.MessageDrivenBean.

The following requirements are the same as in EJB 2.x and are provided only as a brief overview:

  • The class must be defined as public, must not be final, and must not be abstract. The class must be a top level class.

  • The class must have a public constructor that takes no parameters.

  • The class must not define the finalize() method.

  • If the bean is message-driven, the bean class must implement, directly or indirectly, the message listener interface required by the messaging type that it supports or the methods of the message listener interface. In the case of JMS, this is the jakarta.jms.MessageListener interface.

Bean Class Method Requirements

The method requirements have not changed since EJB 2.x and are provided in this section for a brief overview only.

The requirements for programming the session bean class' methods (that implement the business interface methods) are as follows:

  • The method names can be arbitrary.

  • The business method must be declared as public and must not be final or static.

  • The argument and return value types for a method must be legal types for RMI/IIOP if the method corresponds to a business method on the session bean's remote business interface or remote interface.

  • The throws clause may define arbitrary application exceptions.

The requirements for programming the message-driven bean class' methods are as follows:

  • The methods must implement the listener methods of the message listener interface.

  • The methods must be declared as public and must not be final or static.

Programming the Bean File

The sections that follow provide the recommended steps when programming the annotated EJB class file.

Typical Steps When Programming the Bean File

The following procedure describes the typical basic steps when programming the bean file for an EJB. The steps you follow depends, of course, on what your EJB does.

Refer to Simple EJB Examples, for code examples of the topics discussed in the remaining sections.

  1. Import the EJB and other common annotations that will be used in your bean file. The general EJB annotations are in the jakarta.ejb package, the interceptor annotations are in the jakarta.interceptor package, the Jakarta Persistence annotations are in the jakarta.persistence package, and the common annotations are in the jakarta.annotation or jakarta.annotation.security packages. For example:
    import jakarta.ejb.Stateless;
    import jakarta.ejb.TransactionAttribute;
    import jakarta.interceptor.ExcludeDefaultInterceptors;
    
  2. Decide which client views the session bean exposes. A no-interface view uses the bean class and needs no separate Java interface. For each local or remote business view, provide the business interface and either implement it or declare it with @Local or @Remote.
  3. Use the required annotation to specify the type of bean you are programming (session or message-driven).
  4. Optionally, use dependency injection to use external resources, such as another EJB or other Jakarta EE object.
  5. Optionally, inject an EntityManager or EntityManagerFactory and use the Jakarta Persistence API to manage entities.
  6. Optionally, program and configure business method or life cycle callback method interceptor method. You can program the interceptor methods in the bean file itself, or in a separate Java file.
  7. If your business interface specifies that business methods throw application exceptions, you must program the exception class, the same as in EJB 2.x.
  8. Optionally, specify the security roles that are allowed to invoke the EJB methods using the security-related metadata annotations.
  9. Optionally, change the default transaction configuration in which the EJB runs.

Specifying the Business and Other Interfaces

The EJB 3.x local or remote client of a session bean written to the EJB 3.x API accesses a session bean through its business interface. A local client may also access a session bean through a no-interface view that exposes all public methods of the bean class.

Specifying the Business Interface

There are two ways you can specify the business interface for the EJB bean class:

  • By explicitly implementing the business interface, using the implements Java keyword.

  • By using metadata annotations (such as jakarta.ejb.Local and jakarta.ejb.Remote) to specify the business interface. In this case, the bean class does not need to explicitly implement the business interface.

Typically, if an EJB bean class implements an interface, it is assumed to be the business interface of the EJB. Additionally, the business interface is assumed to be the local interface unless you explicitly denote it as the remote interface, either by using the jakarta.ejb.Remote annotation or updating the appropriate EJB deployment descriptor. You can specify the jakarta.ejb.Remote annotation, as well as the jakarta.ejb.Local annotation, in either the business interface itself, or the bean class that implements the interface.

A bean class can have more than one interface. In this case (excluding the interfaces listed below), you must specify the business interface of the EJB by explicitly using the jakarta.ejb.Local or jakarta.ejb.Remote annotations in either the business interface itself, the bean class that implements the business interface, or the appropriate deployment descriptor.

The following interfaces are excluded when determining whether the bean class has more than one interface:

  • java.io.Serializable

  • java.io.Externalizable

  • Any of the interfaces defined by the jakarta.ejb package

The following code snippet shows how to specify the business interface of a bean class by explicitly implementing the interface:

public class ServiceBean
  implements Service

For the full example, see Example of a Simple Business Interface Stateless EJB.

Specifying the No-interface View

Client access to a session bean that exposes a local no-interface view is accomplished through dependency injection or JNDI lookup. Message-driven beans do not expose local, remote, or no-interface client views; clients send messages to the destination or endpoint serviced by the message-driven bean.

  • To obtain a reference to the no-interface view of an enterprise bean through dependency injection, use the jakarta.ejb.EJB annotation and specify the enterprise bean's implementation class:

    @EJB
    ExampleBean exampleBean;
    
  • Within a Jakarta EE component in the same module, obtain a no-interface view through the component naming context as follows:

    import javax.naming.InitialContext;
    import javax.naming.NamingException;
    
    public ExampleBean lookupExampleBean() throws NamingException {
        InitialContext context = new InitialContext();
        try {
            return (ExampleBean) context.lookup("java:module/ExampleBean");
        } finally {
            context.close();
        }
    }
    

Clients do not use the new operator to obtain a new instance of an enterprise bean that uses a no-interface view.

There are code examples of using the No-interface client view bundled in the WebLogic Server distribution kit. See EJB 3.1: Example of Simplified No-interface Programming and Packaging in a WAR File and EJB 3.2: Example of a Message-Driven Bean with No-Methods Listener.

For more detailed information about implementing the no-interface client view, see "Accessing Local Enterprise Beans Using the No-Interface View" in the "Enterprise Beans chapter of the Jakarta EE Tutorial.

Specifying the Bean Type (Stateless, Singleton, Stateful, or Message-Driven)

For an annotation-defined bean class, specify exactly one component-defining annotation:

  • @jakarta.ejb.Stateless—Specifies that you are programming a stateless session bean.

  • @jakarta.ejb.Singleton—Specifies that you are programming a singleton session bean.

  • @jakarta.ejb.Stateful—Specifies that you are programming a stateful session bean.

  • @jakarta.ejb.MessageDriven—Specifies that you are programming a message-driven bean.

    Note:

    WebLogic Server ships the Jakarta Persistence 3.2 API and EclipseLink 5.0.1. EclipseLink is the default persistence provider unless the application selects another supported provider in persistence.xml or the domain configures a different default. A Jakarta Persistence entity is annotated with @jakarta.persistence.Entity.

    Do not confuse a Jakarta Persistence entity with an optional Enterprise Beans 2.x entity bean, which uses the jakarta.ejb.EntityBean component model and deployment descriptors.

    See Configuring the Persistence Provider in Oracle WebLogic Server.

Alternatively, declare the bean type and class in ejb-jar.xml without a component-defining annotation.

Although not required, you can specify attributes of the annotations to further describe the bean type. For example, you can set the following attributes for all bean types:

  • name—Name of the bean class; the default value is the unqualified bean class name.

  • mappedName—Product-specific name of the bean.

  • description—Description of what the bean does.

If you are programming a message-driven bean, then you can specify the following optional attributes:

  • messageListenerInterface—Specifies the message listener interface, if you haven't explicitly implemented it or if the bean implements additional interfaces.

  • activationConfig—Specifies an array of activation configuration name-value pairs that configure the bean in its operational environment.

The following code snippet shows how to specify that a bean is a stateless session bean:

@Stateless
public class ServiceBean
  implements Service

For the full example, see Example of a Simple Business Interface Stateless EJB.

Injecting Resource Dependency into a Variable or Setter Method

Dependency injection is when the EJB container automatically supplies (or injects) a bean's variable or setter method with a reference to a resource or another environment entry in the bean's context. Dependency injection is simply an easier-to-program alternative to using the jakarta.ejb.EJBContext interface or JNDI APIs to look up resources.

You specify dependency injection by annotating a variable or setter method with one of the following annotations, depending on the type of resource you want to inject:

  • @jakarta.ejb.EJB—Specifies a dependency on another EJB.

  • @jakarta.annotation.Resource—Specifies a dependency on an external resource, such as a JDBC datasource or a JMS destination or connection factory.

    Note:

    This annotation is not specific to EJB; rather, it is part of the common set of metadata annotations used by many different types of Jakarta EE components.

Both annotations have an equivalent grouping annotation to specify a dependency on multiple resources (@jakarta.ejb.EJBs and @jakarta.annotation.Resources).

Although not required, you can specify attributes to these dependency annotations to explicitly describe the dependent resource. The amount of information you need to specify depends upon its usage context and how much information the EJB container can infer from that context. See jakarta.ejb.EJB and jakarta.annotation.Resource in jakarta.ejb Package for detailed information on the attributes and when you should specify them.

The following code snippet shows how to use the @jakarta.ejb.EJB annotation to inject a dependency on an EJB into a variable; only the relevant parts of the bean file are shown:

package examples;
import jakarta.ejb.EJB;
...
@Stateful
public class AccountBean
 implements Account
{
  @EJB(beanName="ServiceBean")
  private Service service;
...
  public void sayHelloFromAccountBean() {
    service.sayHelloFromServiceBean();
  }

In the preceding example, the private variable service is annotated with the @jakarta.ejb.EJB annotation, which makes reference to the EJB with a bean name of ServiceBean. The data type of the service variable is Service, which is the business interface implemented by the ServiceBean bean class. As soon as the EJB container creates the AccountBean EJB, the container injects a reference to ServiceBean into the service variable. The variable then exposes the business methods declared by the Service business interface and implemented by ServiceBean, including the sayHelloFromServiceBean method invoked by sayHelloFromAccountBean.

Using Jakarta Persistence Entities from a Session Bean

This section describes how a session bean uses the Jakarta Persistence API to find, create, and update entities.

Note:

Oracle TopLink, a JPA 2.1 persistence provider, is the default JPA provider, replacing Kodo, which was the default provider in previous releases. Any application that does not specify a JPA provider in persistence.xml will now use TopLink by default. For more information, see Configuring the Persistence Provider in Oracle WebLogic Server.

An entity is a persistent object that represents datastore records; typically an instance of an entity represents a single row of a database table. Entities make it easy to query and update information in a persistent store from within another Jakarta EE component, such as a session bean. A Person entity, for example, might include name, address, and age fields, each of which correspond to the columns of a table in a database. Using an jakarta.persistence.EntityManager object to access and manage the entities, you can easily retrieve a Person record, based on either their unique id or by using a Jakarta Persistence query-language (JPQL) query, and then change the information and automatically commit the information to the underlying datastore.

The following sections describe the typical programming tasks you perform in your session bean to interact with entities:

Injecting Persistence Context Using Metadata Annotations

In your session bean, use the following metadata annotations to inject a container-managed EntityManager or an EntityManagerFactory:

  • @jakarta.persistence.PersistenceContext expresses a dependency on a container-managed EntityManager and its associated persistence context. Its optional unitName selects a persistence unit defined in persistence.xml.

  • @jakarta.persistence.PersistenceContexts is the repeatable-container annotation for one or more @PersistenceContext declarations.

  • @jakarta.persistence.PersistenceUnit expresses a dependency on an EntityManagerFactory for a persistence unit.

  • @jakarta.persistence.PersistenceUnits is the repeatable-container annotation for one or more @PersistenceUnit declarations.

Use @PersistenceContext when the container should manage the EntityManager. Use @PersistenceUnit when application code needs an EntityManagerFactory and will create application-managed EntityManager instances. Application-managed instances created with EntityManagerFactory.createEntityManager() must be closed with EntityManager.close() in a finally block or equivalent cleanup path. Application code must not close a container-managed EntityManager.

The following example shows how to inject a persistence context into the variable em of data type EntityManager; relevant code is shown in bold:

package examples;

import jakarta.ejb.Stateless;

import jakarta.persistence.PersistenceContext;
import jakarta.persistence.EntityManager;

@Stateless
public class ServiceBean
  implements Service

{
  @PersistenceContext private EntityManager em;

...
Finding an Entity Using the EntityManager API

Once you have instantiated an EntityManager object, you can use its methods to interact with the entities in the persistence context. This section discusses the methods used to identify and manage the life cycle of an entity; see Configuring the Persistence Provider in Oracle WebLogic Server for additional uses of the EntityManager, such as transaction management, caching, and so on.

Note:

For clarity, this section assumes that the entities are configured such that they represent actual rows in a database table.

Use the EntityManager.find() method to find a row in a table based on its primary key. The find method takes two parameters: the entity class that you are querying, such as Person.class, and the primary key value for the particular row you want to retrieve. Once you retrieve the row, you can use standard getXXX methods to get particular properties of the entity. The following code snippet shows how to retrieve a Person with whose primary key value is 10, and then get their address:

import jakarta.ejb.Stateless;
import jakarta.ejb.TransactionAttribute;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import java.util.List;

import static jakarta.ejb.TransactionAttributeType.REQUIRED;

@Stateless
@TransactionAttribute(REQUIRED)
public class PersonService {
    @PersistenceContext
    private EntityManager em;

    public List<Person> findPeopleNamedPatrick() {
        return em.createQuery(
                "SELECT p FROM Person p WHERE p.name = :name", Person.class)
            .setParameter("name", "Patrick")
            .getResultList();
    }

    public Person createPerson(String name, int age) {
        Address address = new Address();
        Person person = new Person(name, age);
        person.setAddress(address);
        em.persist(address);
        em.persist(person);
        return person;
    }

    public Person changeAge(int id, int newAge) {
        Person person = em.find(Person.class, id);
        if (person == null) {
            throw new IllegalArgumentException("No Person with id " + id);
        }
        person.setAge(newAge);
        return person;
    }

    public Person applyOfflineChanges(Person detachedPerson) {
        return em.merge(detachedPerson);
    }
}

createQuery executes JPQL. Use createNativeQuery only when native SQL is intended. In createPerson, both Address and Person are newly constructed transient entity instances. Because this example does not assume cascade=PERSIST, it explicitly persists both. With cascade=PERSIST on Person.address, persisting Person may cascade to the new Address. persist makes each new instance managed. Changes to the managed Person returned by find are detected automatically. applyOfflineChanges requires a detached Person. merge copies its state into a managed instance and returns that managed instance, so callers should use the returned object. Inserts and updates are synchronized with the database when the persistence context is flushed, normally before transaction commit, not necessarily at the Java method call.

Creating and Updating an Entity Using EntityManager

To create a new entity instance (and thus add a new row to the database), use the EntityManager.persist method, as shown in the following code snippet

  @TransactionAttribute(REQUIRED)
  public Person createNewPerson(String name, int age) {

    Person p = new Person(name, age);
    em.persist(p); // register the new object with the database

    Address a = new Address();
    p.setAddress(a);
    em.persist(a); // depending on how things are configured, this may or 
                   // may not be required
    return p;

  }

Note:

Whenever you create or update an entity, you must be in a transaction, which is why the @TransactionAttribute annotation in the preceding example is set to REQUIRED.

The preceding example shows how to create a new Person, based on parameters passed to the createNewPerson method, and then call the EntityManager.persist method to automatically add the row to the database table.

The preceding example also shows how to update the newly-created Person entity (and thus new table row) with an Address by using the setAddress() entity method. Depending on the cascade configuration of the Person entity, the second persist() call may not be necessary; this is because the call to the setAddress() method might have automatically triggered an update to the database. For more information about cascading operations, see Configuring the Persistence Provider in Oracle WebLogic Server.

If you use the EntityManager.find() method to find an entity instance, and then use a setXXX method to change a property of the entity, the database is automatically updated and you do not need to explicitly call the EntityManager.persist() method, as shown in the following code snippet:

  @TransactionAttribute(REQUIRED)
  public Person changePerson(int id, int newAge) {   
    Person p = em.find(Person.class, id);
    p.setAge(newAge);
    return p;
  }

In the preceding example, the call to the Person.setAge() method automatically triggered an update to the appropriate row in the database table.

Finally, you can use the EntityManager.merge() method to quickly and easily update a row in the database table based on an update to an entity made by a client, as shown in the following example:

  @TransactionAttribute(REQUIRED)
  public Person applyOfflineChanges(Person pDTO) {
    return em.merge(pDTO);
  }

In the example, the applyOfflineChanges() method is a business method of the session bean that takes as a parameter a Person, which has been previously created by the session bean client. When you pass this Person to the EntityManager.merge() method, the EJB container automatically finds the existing row in the database table and automatically updates the row with the new data. The merge() method then returns a copy of this updated row.

Specifying Interceptors for Business Methods or Life Cycle Callback Events

Jakarta Interceptors can interpose on business-method invocations (@AroundInvoke), timeout invocations (@AroundTimeout), constructor invocations (@AroundConstruct), and lifecycle callback events.

Interceptors can be specified for session and message-driven beans.

You can program an interceptor method inside the bean class itself, or in a separate interceptor class which you then associate with the bean class with the @jakarta.interceptor.Interceptors annotation. You can create multiple interceptor methods that execute as a chain in a particular order.

Interceptor instances may hold state. The life cycle of an interceptor instance is the same as that of the bean instance with which it is associated. Interceptors can invoke JNDI, JDBC, JMS, other enterprise beans, and the EntityManager. Interceptor methods share the JNDI name space of the bean for which they are invoked. Programming restrictions that apply to enterprise bean components to apply to interceptors as well.

Interceptors are configured using metadata annotations in the jakarta.interceptor package, as described in later sections.

The following topics discuss how to actually program interceptors for your bean class:

Specifying Business or Life Cycle Interceptors: Typical Steps

The following procedure provides the typical steps to specify and program interceptors for your bean class.

See Example of a Simple Stateful EJB for an example of specifying interceptors and Example of an Interceptor Class for an example of programming an interceptor class.

  1. Decide whether interceptor methods are programmed in bean class or in a separate interceptor class.

  2. If you decide to program the interceptor methods in a separate interceptor class

    1. Program the class, as described in Programming the Interceptor Class.

    2. In your bean class, use the @jakarta.interceptor.Interceptors annotation to associate the interceptor class with the bean class. The method in the interceptor class annotated with the @jakarta.interceptor.AroundInvoke annotation then becomes a business method interceptor method of the bean class. Similarly, the methods annotated with the life cycle callback annotations become the life cycle callback interceptor methods of the bean class.

    You can specify any number of interceptor classes for a given bean class—the order in which they execute is the order in which they are listed in the annotation. If you specify the interceptor class at the class-level, the interceptor methods apply to all appropriate bean class methods. If you specify the interceptor class at the method-level, the interceptor methods apply to only the annotated method.

  3. In the bean class or interceptor class (wherever you are programming the interceptor methods), program business method interceptor methods, as described in Programming Business Method Interceptor Methods.

  4. In the bean class or interceptor class (wherever you are programming the interceptor methods), program life cycle callback interceptor methods, as described in Programming Life Cycle Callback Interceptor Methods .

  5. In the bean class, optionally annotate methods with the @jakarta.interceptor.ExcludeClassInterceptors annotation to exclude any interceptors defined at the class-level.

  6. In the bean class, optionally annotate the class or methods with the @jakarta.interceptor.ExcludeDefaultInterceptors annotation to exclude any default interceptors that you might define later. Default interceptors are configured in the ejb-jar.xml deployment descriptor, and apply to all EJBs in the JAR file, unless you explicitly use the annotation to exclude them.

  7. Optionally specify default interceptors for the entire EJB JAR file, as described in Specifying Default Interceptor Methods.

Programming the Interceptor Class

The interceptor class is a plain Java class that includes the interceptor annotations to specify which methods intercept business methods and which intercept life cycle callback methods.

Interceptor classes support dependency injection, which is performed when the interceptor class instance is created, using the naming context of the associated enterprise bean.

You must include a public no-argument constructor.

You can have any number of methods in the interceptor class, but restrictions apply as to how many methods can be annotated with the interceptor annotations, as described in the following sections.

For an example, see Example of an Interceptor Class.

Programming Business Method Interceptor Methods

You specify business method interceptor methods by annotating them with the @AroundInvoke annotation.

An interceptor class or bean class can have only one method annotated with @AroundInvoke. To specify that multiple interceptor methods execute for a given business method, you must associate multiple interceptor classes with the bean file, in addition to optionally specifying an interceptor method in the bean file itself. The order in which the interceptor methods execute is the order in which the associated interceptor classes are listed in the @jakarta.interceptor.Interceptors annotation. Interceptor methods in the bean class itself execute after those defined in the interceptor classes.

You cannot annotate a business method itself with the @AroundInvoke annotation.

The signature of an @AroundInvoke method must be:

 Object <METHOD>(InvocationContext) throws Exception

An @AroundInvoke method normally calls InvocationContext.proceed() to invoke the next interceptor or target business method and returns that result. It may intentionally omit proceed() to short-circuit the invocation; in that case, no remaining interceptor or target method is invoked, and the interceptor supplies the result or throws an exception. If it calls proceed(), it may wrap the call in try/catch/finally to inspect, transform, suppress, or rethrow a permitted result or exception. See Saving State Across Interceptors With the InvocationContext API for additional information about the InvocationContext API.

Business method interceptor method invocations occur within the same transaction and security context as the business method for which they are invoked. Business method interceptor methods may throw runtime exceptions or application exceptions that are allowed in the throws clause of the business method.

For an example, see Example of an Interceptor Class.

Programming Asynchronous Business Methods

Session beans can implement asynchronous methods, business methods where control is returned to the client by the enterprise bean container before the method is invoked on the session bean instance. Clients may then use the Java SE concurrency API to retrieve the result, cancel the invocation, and check for exceptions. Asynchronous methods are typically used for long-running operations, for processor-intensive tasks, for background tasks, to increase application throughput, or to improve application response time if the method invocation result isn't required immediately.

When a session bean client invokes a typical non-asynchronous business method, control is not returned to the client until the method has completed. Clients calling asynchronous methods, however, immediately have control returned to them by the enterprise bean container. This allows the client to perform other tasks while the method invocation completes. If the method returns a result, the result is an implementation of the java.util.concurrent.Future<V> interface, where “V" is the result value type. The Future<V> interface defines methods the client may use to check if the computation is completed, wait for the invocation to complete, retrieve the final result, and cancel the invocation.

Asynchronous method invocation semantics only apply to the no-interface, local business, and remote business client views. Methods exposed through the EJB 2.x local, EJB 2.x remote, and Web service client views must not be designated as asynchronous.

For detailed instructions on creating an asynchronous business method, see "Asynchronous Method Invocation" in the Enterprise Beans chapter of the Jakarta EE Tutorial.

Programming Life Cycle Callback Interceptor Methods

You specify a method to be a life cycle callback interceptor method so that it can receive notification of life cycle events from the EJB container. Life cycle events include creation, passivation, and destruction of the bean instance.

You can name the life cycle callback interceptor method anything you want; this is different from the EJB 2.x programming model in which you had to name the methods ejbCreate(), ejbPassivate(), and so on.

You use the following life cycle interceptor annotations to specify that a method is a life cycle callback interceptor method:

  • @jakarta.ejb.PrePassivate—Specifies the method that the EJB container notifies when it is about to passivate a stateful session bean.

  • @jakarta.ejb.PostActivate—Specifies the method that the EJB container notifies right after it has reactivated a stateful session bean.

  • @jakarta.annotation.PostConstruct—Specifies the method that the EJB container notifies before it invokes the first business method and after it has done dependency injection. You typically apply this annotation to the method that performs initialization.

    Note:

    This annotation is in the jakarta.annotation package, rather than jakarta.ejb.

  • @jakarta.annotation.PreDestroy—Specifies the method that the EJB container notifies right before it destroys the bean instance. You typically apply this annotation to the method that release resources that the bean class has been holding.

    Note:

    This annotation is in the jakarta.annotation package, rather than jakarta.ejb.

You use the preceding annotations the same way, whether the annotated method is in the bean class or in a separate interceptor class. You can annotate the same method with more than one annotation.

You can also specify any subset or combination of life cycle callback annotations in the bean class or in an associated interceptor class. However, the same callback annotation may not be specified more than once in a given class. If you do specify a callback annotation more than once in a given class, the EJB will not deploy.

To specify that multiple interceptor methods execute for a given life cycle callback event, you must associate multiple interceptor classes with the bean file, in addition to optionally specifying the life cycle callback interceptor method in the bean file itself. The order in which the interceptor methods execute is the order in which the associated classes are listed in the @jakarta.interceptor.Interceptors annotation. Interceptor methods in the bean class itself execute after those defined in the interceptor classes.

The signature of the annotated methods depends on where the method is defined:

  • Life cycle callback methods defined on a bean class have the following signature:

     void <METHOD>()
    
  • Life cycle callback methods defined on an interceptor class have the following signature:

     void <METHOD>(InvocationContext)
    

See Saving State Across Interceptors With the InvocationContext API for additional information about the InvocationContext API.

See jakarta.ejb.PostActivate, jakarta.ejb.PrePassivate, jakarta.annotation.PostConstruct, and jakarta.annotation.PreDestroy in jakarta.ejb Package and jakarta.annotation Package for additional requirements when programming the life cycle interceptor class.

For an example, see Example of an Interceptor Class.

Specifying Default Interceptor Methods

Default interceptor methods apply to all components in a particular EJB JAR file or exploded directory, and thus can only be configured in the ejb-jar.xml deployment descriptor file and not with metadata annotations, which apply to a particular EJB.

The EJB container invokes default interceptor methods, if any, before all other interceptors defined for an EJB (both business and life cycle). If you do not want the EJB container to invoke the default interceptors for a particular EJB, specify the class-level @jakarta.interceptor.ExcludeDefaultInterceptors annotation in the bean file.

In the ejb-jar.xml file, use the <interceptor-binding> child element of <assembly-descriptor> to specify default interceptors. In particular, set the <ejb-name> child element to *, which means the class applies to all EJBs, and then the <interceptor-class> child element to the name of the interceptor class.

The following snippet from an ejb-jar.xml file shows how to specify the default interceptor class org.mycompany.DefaultIC:

<?xml version="1.0" encoding="UTF-8"?>

<ejb-jar version="4.0"
     xmlns="https://jakarta.ee/xml/ns/jakartaee"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/ejb-jar_4_0.xsd">

...

   <assembly-descriptor>

...

      <interceptor-binding>
        <ejb-name>*</ejb-name>
        <interceptor-class>org.mycompany.DefaultIC</interceptor-class>
      </interceptor-binding>

   </assembly-descriptor>

</ejb-jar>
Saving State Across Interceptors With the InvocationContext API

Use the jakarta.interceptor.InvocationContext API to pass state information between the interceptors that execute for a given business method or life cycle callback. The EJB Container passes the same InvocationContext instance to each interceptor method, so you can, for example save information when the first business method interceptor method executes, and then retrieve this information for all subsequent interceptor methods that execute for this business method. The InvocationContext instance is not shared between business method or life cycle callback invocations.

Around-invoke, around-timeout, around-construct, and lifecycle interceptor methods take one InvocationContext parameter. A lifecycle callback method declared directly on the target class instead has no InvocationContext parameter.

Table 4-1 Interceptor Methods

Method Description
Object getTarget() Returns the intercepted target; before proceed() in @AroundConstruct, it returns null.
Object getTimer() Returns the timer for @AroundTimeout, otherwise null.
Method getMethod() Returns the intercepted target method, or null when no corresponding method exists and for @AroundConstruct.
Constructor<?> getConstructor() Returns the intercepted constructor for @AroundConstruct, otherwise null.
Object[] getParameters() / void setParameters(Object[] parameters) Reads or replaces method/constructor parameters; these operations are illegal in lifecycle callbacks other than @AroundConstruct.
Map<String,Object> getContextData() Returns mutable data shared across the invocation chain.
Object proceed() throws Exception Continues to the next interceptor or target operation.
Set<Annotation> getInterceptorBindings() Returns the immutable set of interceptor bindings (Interceptors 2.2).
<T extends Annotation> T getInterceptorBinding(Class<T> type) Returns the single binding of a type, or null (Interceptors 2.2).
<T extends Annotation> Set<T> getInterceptorBindings(Class<T> type) Returns all bindings of a repeatable type (Interceptors 2.2).

For an example of using InvocationContext, see Example of an Interceptor Class.

Programming Application Exceptions

If you specified in the business interface that a method throws an application exception, then you must program the exception as a separate class from the bean class.

Use the @jakarta.ejb.ApplicationException annotation to specify that an exception class is an application exception thrown by a business method of the EJB. The EJB container reports the exception directly to the client in the event of the application error.

Use the rollback Boolean attribute of the @ApplicationException annotation to specify whether the application error causes the current transaction to be rolled back. By default, the current transaction is not rolled back in event of the error.

You can annotate both checked and unchecked exceptions with this annotation.

The following ProcessingException.java file shows how to use the @ApplicationException annotation to specify that an exception class is an application exception thrown by one of the business methods of the EJB:

package examples;

import jakarta.ejb.ApplicationException;

/**
 * Application exception class thrown when there was a processing error
 * with a business method of the EJB.  Annotated with the
 * @ApplicationException annotation.
 */

@ApplicationException()
public class ProcessingException extends Exception {

  /**
   * Catches exceptions without a specified string
   *
   */
  public ProcessingException() {}

  /**
   * Constructs the appropriate exception with the specified string
   *
   * @param message           Exception message
   */
  public ProcessingException(String message) {super(message);}
}

Securing Access to the EJB

By default, any user can invoke the public methods of an EJB. If you want to restrict access to the EJB, you can use the following security-related annotations to specify the roles that are allowed to invoke all, or a subset, of the methods:

  • jakarta.annotation.security.DeclareRoles—Explicitly lists the security roles that will be used to secure the EJB. Role names used in @RolesAllowed are implicitly declared. Use @DeclareRoles, or security-role-ref in ejb-jar.xml, for role names tested with EJBContext.isCallerInRole that are not otherwise declared.

  • jakarta.annotation.security.RolesAllowed—Specifies the security roles that are allowed to invoke all the methods of the EJB (when specified at the class-level) or a particular method (when specified at the method-level.)

  • jakarta.annotation.security.DenyAll—Specifies that the annotated method can not be invoked by any role.

  • jakarta.annotation.security.PermitAll—Specifies that the annotated method can be invoked by all roles.

  • jakarta.annotation.security.RunAs—Specifies the role which runs the EJB. By default, the EJB runs as the user who actually invokes it.

The preceding annotations can be used with many Jakarta EE components that allow metadata annotations, not just EJB.

You create security roles and map users to roles using the WebLogic Remote Console to update your security realm. See Security Policies and Roles in the Oracle WebLogic Remote Console Online Help.

The following example shows a simple stateless session EJB that uses all of the security-related annotations; the code in bold is discussed after the example:

package examples;

import jakarta.ejb.Stateless;

import jakarta.annotation.security.DeclareRoles;
import jakarta.annotation.security.PermitAll;
import jakarta.annotation.security.DenyAll;
import jakarta.annotation.security.RolesAllowed;
import jakarta.annotation.security.RunAs;

/**
 * Bean file that implements the Service business interface.
 */

@Stateless
@DeclareRoles( { "admin", "hr" } )
@RunAs ("admin")

public class ServiceBean
  implements Service
{
  @RolesAllowed ( {"admin", "hr"} )
  public void sayHelloRestricted() {
    System.out.println("Only some roles can invoke this method.");
  }

  @DenyAll
  public void sayHelloSecret() {
    System.out.println("No one can invoke this method.");
  }

  @PermitAll
  public void sayHelloPublic() {
    System.out.println("Everyone can invoke this method.");
  }
}

The main points to note about the preceding example are:

  • Import the security-related metadata annotations:

    import jakarta.annotation.security.DeclareRoles;
    import jakarta.annotation.security.PermitAll;
    import jakarta.annotation.security.DenyAll;
    import jakarta.annotation.security.RolesAllowed;
    import jakarta.annotation.security.RunAs;
    
  • The class-level @DeclareRoles annotation explicitly specifies that the admin and hr security roles will later be used to secure some or all of the methods. This annotation is not required; any security role referenced in, for example, the @RolesReferenced annotation is implicitly declared. However, explicitly declaring the security roles makes your code easier to read and understand.

  • The class-level @RunAs annotation specifies that after the original caller is authorized to invoke a method, outbound calls made by ServiceBean use the principal mapped to the admin run-as role. The annotation does not grant the original caller the admin role and does not change the caller reported by getCallerPrincipal or tested by isCallerInRole.

  • The @RolesAllowed annotation on the sayHelloRestricted method specifies that only users mapped to the admin and hr roles are allowed to invoke the method.

  • The @DenyAll annotation on the sayHelloSecret method specifies that no one is allowed to invoke the method.

  • The @PermitAll annotation on the sayHelloPublic method specifies that all users mapped to any roles are allowed to invoke the method.

Specifying Transaction Management and Attributes

By default, session beans and message-driven beans use container-managed transaction demarcation. For a bean with container-managed transactions, a business method whose transaction attribute is not specified uses TransactionAttributeType.REQUIRED. Use @TransactionManagement(BEAN) only when the bean is to demarcate its own transactions, and use @TransactionAttribute only when an explicit container-managed transaction attribute is needed or improves clarity.

You can change this default behavior by using the following annotations in your bean file:

  • jakarta.ejb.TransactionManagement—Specifies whether the EJB container or the bean file manages the demarcation of transactions. If you specify that the bean file manages it, then you must program transaction management in your bean file, typically using the Java Transaction API (JTA).

  • jakarta.ejb.TransactionAttribute—Specifies whether the EJB container invokes methods within a transaction.

For an example of using the jakarta.ejb.TransactionAttribute annotation, see Example of a Simple Stateful EJB.