Asynchronous 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("A root page has been created.");
}
}
}
You don’t need to return a result. Even if you do it, the result will be lost.
The default is that all jobs annotated with @OnApplicationStart
will be executed in sequence. When all jobs are finished, your application is ready to start processing incoming requests.
If you want your jobs to start when your application starts, but you want to start processing incoming requests immediately, you can annotate your job like this: @OnApplicationStart(async=true)
. Then your job will be started in the background when the application starts. All async jobs will be started at the same time.
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);
}
}
}
The above annotation is for hours, if you want to specify the time duration in minutes or seconds, you can do it in the following way:-
i) For Minutes - @Every(“3mn”),@Every(“30mn”)
ii) For Seconds - @Every(“35s”),@Every(“56s”)
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.
Triggering task jobs
You can also trigger a Job at any time to perform a specific task by simply calling now()
on a Job instance. Then this job will be run immediately in a non blocking way.
public static void encodeVideo(Long videoId) {
new VideoEncoder(videoId).now();
renderText("Encoding started");
}
Calling now()
on a Job returns a Promise
value that you can use to retrieve the task result once finished, or an exception if something went wrong with task execution.
If you want to schedule a Job to run after some delay, in(int seconds)
method is there to help you. Calling in(0)
has the same effect as now()
.
Sometimes when dealing with http requests you want to schedule a Job to run after request is responded. A natural use case would be if you create and save new JPA entities and the Job has to retrieve them from database by some identifier and process them. Triggering a Job with now()
won’t work, because transaction is not yet committed, so a thread executing the task won’t be able to retrieve entities; in(delay)
will work, but it is not flexible enough. For those cases Job
offers afterRequest()
method that will schedule a job to run when request is served.
If afterRequest()
is called not from http invocation context, it behaves like now()
.
All now()
, in()
, afterRequest()
return a Promise
so you have access to this Job execution result.
Stopping the application
Because you sometimes need to perform some action before the application shutdown, Play also provides a @OnApplicationStop
annotation.
import play.jobs.*;
@OnApplicationStop
public class Bootstrap extends Job {
public void doJob() {
Fixture.deleteAll();
}
}
Continuing the discussion
Let’s see how to combine Jobs with more powerful Asynchronous programming with HTTP.