Documentation

Jobs

Because Play is a web application framework, most of the application logic is done by controllers responding to HTTP requests.

But sometimes you will need to execute some application logic outside of any HTTP request. It can be useful for initialization tasks, maintenance tasks or to run long tasks without blocking the HTTP request execution pool.

Jobs are fully managed by the framework. That means that Play will manage all the database connection stuff, JPA entity manager synchronization and transactions management for you. To create a job you just need to extend the play.jobs.Job super class.

package jobs;
 
import play.jobs.*;
 
public class MyJob extends Job {
    
    public void doJob() {
        // execute some application logic here ...
    }
    
}

Sometimes you need to create jobs that return a result. You then override the doJobWithResult() method.

package jobs;
 
import play.jobs.*;
  
public class MyJob extends Job<String> {
    
    public String doJobWithResult() {
        // execute some application logic here ...
        return result;
    }
    
}

Here the use of String is just for the example, of course a job can return any object type.

Bootstrap jobs

Bootstrap jobs are executed by Play at application start time. To mark your job as a bootstrap job you just need to add the @OnApplicationStart annotation.

import play.jobs.*;
 
@OnApplicationStart
public class Bootstrap extends Job {
    
    public void doJob() {
        if(Page.count() == 0) {
            new Page("root").save();
            Logger.info("The page tree was empty. A root page has been created.");
        }
    }
    
}

You don’t need to return a result. Even if you do it, the result will be lost.

Warning

When you run the application in DEV mode, the application waits for the first HTTP request to start. Moreover when you are in DEV mode, the application will sometimes automatically restart when needed.

When you run in PROD mode, the application will start synchronously with the server start.

Scheduled jobs

Scheduled jobs are run periodically by the framework. You can ask Play to run a job at a specific interval using the @Every annotation.

import play.jobs.*;
 
@Every("1h")
public class Bootstrap extends Job {
    
    public void doJob() {
        List<User> newUsers = User.find("newAccount = true").fetch();
        for(User user : newUsers) {
            Notifier.sayWelcome(user);
        }
    }
    
}

If the @Every annotation is not enough you can use the @On annotation to run your jobs using a CRON expression.

import play.jobs.*;
 
/** Fire at 12pm (noon) every day **/ 
@On("0 0 12 * * ?")
public class Bootstrap extends Job {
    
    public void doJob() {
        Logger.info("Maintenance job ...");
        ...
    }
    
}

Tip

We use the CRON expression parser from the Quartz library.

You don’t need to return a result. Even if you do it, the result will be lost.

Suspendable requests

Play is intended to work with very short requests. It uses a fixed thread pool to process requests queued by the HTTP connector. To get optimum results, the thread pool should be as small as possible. We typically use the optimum value of nb of processors + 1 to set the default pool size.

That means that if a request is very long (for example waiting for a long computation) it will block the thread pool and penalize your application responsiveness. Of course you could add more threads to the pool, but it will result in wasted resources, and anyway the pool will never be infinite.

Think for example about a chat application where browsers send a blocking HTTP request that waits for a new message to display. These requests can be very very long (typically several seconds) and will block the thread pool. If you plan to allow 100 users to connect simultaneously to your chat application you will need to provision at least 100 threads. OK it’s feasible. But what about 1,000 users? Or 10,000?

To resolve these use cases, Play allows you to temporarily suspend a request. The HTTP request will stay connected, but the request execution will be popped out of the thread pool and tried again later. You can either tell Play to try the request later after a fixed delay, or wait for a Job completion.

Tip

You can see a real example in $PLAY_HOME/samples-and-tests/chat/.

For example, this action will launch a very long job and wait for its completion before returning the result to the HTTP response:

public static void generatePDF(Long reportId) {
    if(request.isNew) {
        Report report = Report.findById(reportId);
        Future<InputStream> task = new ReportAsPDFJob(report).now();
        request.args.put("task", task);
        waitFor(task);
    }
    renderBinary(((Future<inputstream>) request.args.get("task")).get());
}

This action will be called twice. On the first call the request.isNew tag is set to true, so the action will create a Job and start it immediately. Then it tells Play to wait for the task completion before trying the action again. After the task is completed, Play will call the action a second time. This time the request.isNew tag is set to false. The action will retrieve the job result and send it as the HTTP response.

Continuing the discussion

Next: Sending e-mail.