TOTD #146: Understanding the EJB 3.1 Timer service in Java EE 6 - Programmatic, Deployment Descriptor, @Schedule

EJB 3.1 has greatly simplified the ability to schedule events according to a calendar-based schedule, at a specified time, after a specified elapsed duration, or at a specific recurring intervals.

There are multiple ways events can be scheduled:

  • Programmatically using Timer service
  • Automatic timers based upon the metadata specified using @Schedule
  • Deployment Descriptor

This Tip Of The Day (TOTD) will show code samples on how timer-based events can be created in EJBs.

Lets start with programmatic creation of timers first.

The Timer Service allows for programmatic creation and cancellation of timers. Programmatic timers can be created using createXXX methods on "TimerService". The method to be invoked at the scheduled time can be any of the flavors mentioned below:

  1. EJB implementing "javax.ejb.TimedObject" interface that has a single method "public void ejbTimeout(Timer timer)". For example:
    @Singleton
    @Startup
    public class DummyTimer2 implements TimedObject {
     
     @Resource TimerService timerService;
     
     @PostConstruct
     public void initTimer() {
       if (timerService.getTimers() != null) {
         for (Timer timer : timerService.getTimers()) {
           if (timer.getInfo().equals("dummyTimer2.1") ||
               timer.getInfo().equals("dummyTimer2.2")) 
             timer.cancel();
           }
       }
     
       timerService.createCalendarTimer(
           new ScheduleExpression().
               hour("\*").
               minute("\*").
               second("\*/10"), new TimerConfig("dummyTimer2.1", true));
       timerService.createCalendarTimer(
           new ScheduleExpression().
               hour("\*").
               minute("\*").
               second("\*/45"), new TimerConfig("dummyTimer2.2", true));
     }
    
     @Override
     public void ejbTimeout(Timer timer) {
       System.out.println(getClass().getName() + ": " + new Date());
     }
    }
    
    

    The "initTimer" method is a lifecycle callback method and cleans up any previously created timers and then create new timers that triggers every 10th and 45th second. The "ejbTimeout" method, implemented from "TimedObject" interface is invoked everytime the timeout occurs. The "timer" parameter in the "ejbTimeout" method can be used to cancel the timer, get information on when the next timeout will occur, get information about the timer and other relevant data.

    Notice, there is a @Startup class-level annotation which ensures that bean is eagerly loaded, lifecycle callback methods invoked and thus timers are created before the bean is ready.
  2. At most one method tagged with "@Timeout". Methods annotated with @Timeout must have one of the following signature:
    1. void <METHOD>()
    2. void <METHOD>(Timer timer)

    The second method signature gives you the ability to cancel the timer and obtain metatdata about the timer. These methods may be public, private, protected, or package level access. For example:
    @Singleton
    @Startup
    public class DummyTimer3 {
      @Resource TimerService timerService;
     
      @PostConstruct
      public void initTimer() {
        if (timerService.getTimers() != null) {
          for (Timer timer : timerService.getTimers()) {
            if (timer.getInfo().equals("dummyTimer3.1") ||
                timer.getInfo().equals("dummyTimer3.2")) 
              timer.cancel();
          }
        }
        timerService.createCalendarTimer(
          new ScheduleExpression().
              hour("\*").
              minute("\*").
              second("\*/10"), new TimerConfig("dummyTimer3.1", true));
        timerService.createCalendarTimer(
          new ScheduleExpression().
              hour("\*").
              minute("\*").
              second("\*/45"), new TimerConfig("dummyTimer3.2", true));
      }
      @Timeout
      public void timeout(Timer timer) {
        System.out.println(getClass().getName() + ": " + new Date());
      }
    }
    

    Here again, the "initTimer" method is used for cleaning / initializing timers. The "timeout" method is marked with "@Timeout" annotation is the target method that is invoked when the timer expires.

Read more details about Timer Service in the Java EE Tutorial.

Lets see how this similar functionality can be achieved using deployment descriptor (ejb-jar.xml). Basically a simple method in the POJO class given below:

public class DummyTimer4 {
  public void timeout(Timer timer) {
    System.out.println(getClass().getName() + ": " + new Date());
  }
}

can be converted into a timer method by adding the following "ejb-jar.xml" in WEB-INF directory of WAR file:

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

<ejb-jar xmlns = "http://java.sun.com/xml/ns/javaee"
  version = "3.1"
  xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation = "http://java.sun.com/xml/ns/javaee
     http://java.sun.com/xml/ns/javaee/ejb-jar_3_1.xsd">
  <enterprise-beans>
    <session>
      <ejb-name>DummyTimer4</ejb-name>
      <ejb-class>org.glassfish.samples.DummyTimer4</ejb-class>
      <session-type>Stateless</session-type>
      <timer>
        <schedule>
          <second>\*/10</second>
          <minute>\*</minute>
          <hour>\*</hour>
          <month>\*</month>
          <year>\*</year>
        </schedule>
        <timeout-method>
          <method-name>timeout</method-name>
          <method-params>
            <method-param>javax.ejb.Timer</method-param>
          </method-params>
       </timeout-method>
     </timer>
   </session>
 </enterprise-beans>
</ejb-jar>

Multiple <schedule>s can be added to create multiple timers.

But all this is too much. EJB 3.1 adds @Schedule annotation that automatically creates timers based upon the metadata specified on a method, such as:

@Singleton
public class DummyTimer {

  @Schedules({
    @Schedule(hour="\*", minute="\*", second="\*/10"),
    @Schedule(hour="\*", minute="\*", second="\*/45")
  })
  public void printTime() {
    System.out.println(getClass().getName() + ": " + new Date());
  }
}

EJB 3.1 container reads the @Schedule annotations and automatically create timers. Notice, there is no need for a @Startup annotation here as lifecycle callback methods are not required. Each re-deploy of application will automatically delete and re-create all the schedule-based timers ... really clean and simple! No messing around with deployment descriptors too :-)

Interval timers created using TimerService can be easily created by using "ScheduleExpression.start()" and "end()" methods. The single-action timer can be easily created by specifying fixed values for each field:

@Schedule(year="A", month="B", dayOfMonth="C", hour="D", minute="E", second="F")

Optionally, you can also pass a "Timer" object to the methods annotated with @Schedule such as:

@Schedules({
  @Schedule(hour="\*", minute="\*", second="\*/10", info="every tenth"),
  @Schedule(hour="\*", minute="\*", second="\*/45", info="every 45th")
})
public void printTime(Timer timer) {
  System.out.println(getClass().getName() + ": " +
      new Date() + ":" +
      timer.getInfo());
}

The "timer" object contains information about the timer that just expired. Note that "info" is passed to each @Schedule annotation and "timer.getInfo()" can be used in "printTime" method to find out which of the timers expired.

The complete source code used in this blog can be downloaded here.

Here are some other points to be noted:

  • Timers can be created in stateless session beans, singleton session beans, MDBs but not for stateful session beans. This functionality may be added to a future version of the specification.
  • Timers are persistent by default, need to made non-persistent programmatically (TimerConfig.setPersistent(false)) or automatically (by adding persistent=false on @Schedule)
  • Schedule-based timers may be optionally associated with a timezone.
  • The user code has no control over the timers created using @Schedule and thus cannot be canceled after creation.
  • Timers are not for real time as the container interleaves the calls to a timeout callback method with the calls to the business methods and the lifecycle callback methods of the bean. So the timed out method may not be invoked exactly at the time specified at timer creation.

This blog is derived from the whiteboard discussion with Linda as captured below and numerous emails with Marina for helping me understand the concepts.

Ah, the joys of sitting a few feet away from most of the Java EE 6 spec leads :-)

Here is one tweet that I saw recently on EJB timers ...

Never wrote a Timer faster in #java ...really like it #ejb3.1 #glassfish http://j.mp/d1owSM

I love the simplicity and power provided by @Schedule, how do you create timers in your enterprise applications ?

Technorati: totd javaee6 glassfish ejb timer deploymentdescriptor schedule annotations

Comments:

Hi Arun, ejb timer it's perfect, but there a mistake on time 00 seconds:

INFO: Portable JNDI names for EJB SessionTimer : [java:global/ejbTimerUno/SessionTimer, java:global/ejbTimerUno/SessionTimer!newpackage.SessionTimer]
INFO: Instantiated an instance of org.hibernate.validator.engine.resolver.JPATraversableResolver.
INFO: ejbTimerUno was successfully deployed in 296 milliseconds.
INFO: newpackage.SessionTimer: Thu Dec 02 16:43:10 CET 2010
INFO: newpackage.SessionTimer: Thu Dec 02 16:43:20 CET 2010
INFO: newpackage.SessionTimer: Thu Dec 02 16:43:30 CET 2010
INFO: newpackage.SessionTimer: Thu Dec 02 16:43:40 CET 2010
INFO: newpackage.SessionTimer: Thu Dec 02 16:43:45 CET 2010
INFO: newpackage.SessionTimer: Thu Dec 02 16:43:50 CET 2010
INFO: newpackage.SessionTimer: Thu Dec 02 16:44:00 CET 2010
INFO: newpackage.SessionTimer: Thu Dec 02 16:44:00 CET 2010
INFO: newpackage.SessionTimer: Thu Dec 02 16:44:10 CET 2010
INFO: newpackage.SessionTimer: Thu Dec 02 16:44:20 CET 2010
INFO: newpackage.SessionTimer: Thu Dec 02 16:44:30 CET 2010

Why are there two output line on time 02 16:44:00 CET 2010?

Posted by M.M. on December 02, 2010 at 12:20 AM PST #

M.M.

Do you have only one @Schedule in your entire WAR ?

I created a new project and it only has one session bean as:

@Stateless
public class HelloBean {

@Schedule(hour="\*", minute="\*", second="\*/10")
public void printDate() {
System.out.println(new Date());
}
}

and it prints the log correctly as:

INFO: Thu Dec 09 08:48:30 BRST 2010
INFO: Thu Dec 09 08:48:40 BRST 2010
INFO: Thu Dec 09 08:48:50 BRST 2010
INFO: Thu Dec 09 08:49:00 BRST 2010
INFO: Thu Dec 09 08:49:10 BRST 2010
INFO: Thu Dec 09 08:49:20 BRST 2010
INFO: Thu Dec 09 08:49:30 BRST 2010
INFO: Thu Dec 09 08:49:40 BRST 2010
INFO: Thu Dec 09 08:49:50 BRST 2010
INFO: Thu Dec 09 08:50:00 BRST 2010
INFO: Thu Dec 09 08:50:10 BRST 2010

Can you check your application ?

Posted by Arun Gupta on December 08, 2010 at 06:50 PM PST #

Hi Arun, maybe you can clarify what should happen in the following situation: if one schedules a recurrent timer using the @Schedule annotation, the timer fires several times(e.g. 3 times) and then the app server is restarted, before the next firing of the timer. What should happen after the server is restarted with the timer? Will the timer fire 3 times like it has never fired before or it shouldn't fire at all since the app server was restarted in between 2 consecutive firings of the timer? Is it any difference in how this is handled in ejb 3.1 and ejb 3.0(in jboss 4.2.1 the recurrent timer would fire again 3 times)? Thanks.

Posted by Cristi on December 09, 2010 at 12:30 AM PST #

Hi Arun,

Just i want check that this timers has the fail-over support and also how to handle the holiays

Thanks

Posted by guest on July 26, 2011 at 10:20 PM PDT #

There is no way to do it now, unless the user code knows its holidays and does not proceed if it is a holiday for their logic. Holidays, as we all know, are different in different countries :)

Posted by Arun Gupta on July 29, 2011 at 04:01 AM PDT #

Hi Arun,

Nice blog.

But @Schedule(hour="\*", minute="\*", second="\*/45") does not contain valid Java Strings. "\" is an escape character. I'm wondering why this compiles and works...

It should be
@Schedule(hour="*", minute="*", second="\*/45")

Michael

Posted by Michael on September 05, 2011 at 03:59 AM PDT #

Hi Arun,

You said "The user code has no control over the timers created using @Schedule and thus cannot be canceled after creation."

I could not find justification from the EJB 3.1 spec (such as chapters 18.2.2, 18.4.2 and 18.4.4).

Could you tell where this is defined/specified/explained.

Best regards

Posted by Tom31 on January 05, 2012 at 12:41 AM PST #

Hi, and thanks for the post, it helped me a lot.

I still would appreciate some more information.

I have achieved a programmatic timer by loading settings from a properties file. Things seemed to work fine until I noticed that my timeout methods where called 2 or 3 times in a row.
Out of despair, I tried your syntax by "back-slashing the star", using "\*" instead of just "*" in my configuration, and it seems to work. Why that? Could you provide an explanation?

Thanks in advance

Posted by guest on March 27, 2012 at 03:16 AM PDT #

I have a situation where I have to tell number of times the timer execution with interval time. (I need to contact SFTP server 5 times with 5mts interval time after I kick the schedule). How can I set this configuration to the ScheduleExpression?

Posted by Ganesh on February 14, 2013 at 07:30 AM PST #

Ganesh,

The expressions allow to specify start and end date. Any other logic would need to be implemented in your application logic.

Posted by Arun Gupta on February 15, 2013 at 09:05 AM PST #

Ganesh,

Can you file an RFE at http://ejb-spec.java.net/ for the requested feature ?

Arun

Posted by Arun Gupta on February 15, 2013 at 09:17 AM PST #

Post a Comment:
Comments are closed for this entry.
About

profile image
Arun Gupta is a technology enthusiast, a passionate runner, author, and a community guy who works for Oracle Corp.


Java EE 7 Samples

Stay Connected

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today