Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2021,2023 Contributors to the Eclipse Foundation
* Copyright (c) 2021,2025 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
Expand Down Expand Up @@ -102,6 +102,21 @@
* findSingleLayoverFlights(RST, DEN).thenApply(Itinerary::sortByPrice);
* </pre>
*
* <h2>Automatic submit on startup</h2>
*
* <p>Asynchronous methods can be submitted automaticaly on application startup
* by observing the application's {@code jakarta.enterprise.Startup} event.
* For example,</p>
*
* <pre>
* {@literal @}Asynchronous(runAt = {@literal @}Schedule(cron = "0 9 * SEP-MAY TUE,THU", zone = "America/Chicago"))
* public void tuesdaysAndThursdaysAt9({@literal @}Observes Startup event) {
* // ...
* }
* </pre>
*
* <h2>Return types</h2>
*
* <p>
* Methods with the following return types can be annotated to be
* asynchronous methods:
Expand All @@ -110,6 +125,9 @@
* <li>{@link java.util.concurrent.CompletionStage CompletionStage}</li>
* <li><code>void</code></li>
* </ul>
*
* <h2>Exceptions</h2>
*
* <p>
* The Jakarta EE Product Provider raises
* {@link java.lang.UnsupportedOperationException UnsupportedOperationException}
Expand Down Expand Up @@ -144,6 +162,9 @@
* exceptionally with {@link java.util.concurrent.CancellationException CancellationException},
* and chains a cause exception if there is any.
* <p>
*
* <h2>Interceptor and Transactional</h2>
*
* The Jakarta EE Product Provider must assign the interceptor for asynchronous methods
* to have priority of <code>Interceptor.Priority.PLATFORM_BEFORE + 5</code>.
* Interceptors with a lower priority, such as <code>Transactional</code>, must run on
Expand Down
81 changes: 77 additions & 4 deletions api/src/main/java/jakarta/enterprise/concurrent/Schedule.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2023,2024 Contributors to the Eclipse Foundation
* Copyright (c) 2023,2025 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
Expand All @@ -25,18 +25,91 @@
import java.time.Month;

/**
* <p>Defines schedules for
* {@link Asynchronous#runAt() scheduled asynchronous methods}.</p>
* <p>Defines a schedule that indicates when to run a method.</p>
*
* <p>For the scheduled asychronous method to aim to run at a given day and time,
* all of the criteria specified by the {@code Schedule} must match
* or be disregarded according to the rules of each field.</p>
*
* <h2>Scheduled methods</h2>
*
* <p>When the {@code Schedule} annotation is applied to a CDI managed bean method,
* the schedule defines the times after which to run the method. The method must
* have a {@code void} return type and no parameters. The bean must not be a
* Jakarta Enterprise Bean.</p>
*
* <p>Upon starting the application, the Jakarta EE Product Provider computes the
* next time from the {@code Schedule} annotation and schedules a task that aims to
* run at the computed time on the default {@link ManagedScheduledExecutorService}.
* After it is time to run the task, the task first checks to ensure that the
* schedule does not require {@linkplain #skipIfLateBy() skipping} the invocation
* of the method. If method invocation is not skipped, the task obtains an instance
* of the bean and invokes the method on the bean instance.</p>
*
* <p>After successful or skipped invocation of the method, the task uses the
* {@code Schedule} annotation to compute the next time.
* The computed next time is after the method completion time or the time of the skip.
* The task schedules another task that aims to run after the computed next time.
* This continues with each
* invocation of the method until an invocation of the method raises an exception
* or error or the application stops.
* </p>
*
* <p>For example,</p>
* <pre>
* {@literal @}Schedule(daysOfWeek = { DayOfWeek.SATURDAY, DayOfWeek.SUNDAY },
* hours = 8,
* minutes = 30,
* zone = "America/Chicago")
* public void weekendsAtEightThirtyAM() {
* System.out.println("Good morning. Today is " + ZonedDateTime.now());
* }
* </pre>
*
* <h2>Asynchronous methods with a Schedule</h2>
*
* <p>When the {@code Schedule} annotation is used as a value of
* {@link Asynchronous#runAt()}, the schedule defines the times after which to
* run the asynchronous method.</p>
*
* <p>{@link Asynchronous} methods with a {@code Schedule} annotation can be
* written to schedule automatically at application startup by observing the
* application's {@code jakarta.enterprise.Startup} event. For example,
* </p>
* <pre>
* {@literal @}Asynchronous(runAt = {@literal @}Schedule(cron = "30 8 * * SAT,SUN",
* zone = "America/Chicago"))
* public void weekendsAtEightThirtyAM({@literal @}Observes Startup event) {
* System.out.println("Good morning. Today is " + ZonedDateTime.now());
* }
* </pre>
*
* <p>Or, asynchronous methods can be written to require invocation in order to
* apply the schedule. For example,
* </p>
* <pre>
* {@literal @}Asynchronous(runAt = {@literal @}Schedule(cron = "30 6 * * MON-FRI",
* zone = "America/Chicago"))
* public void weekdaysAtSixThirtyAM(String message) {
* System.out.println(message + " Today is " + ZonedDateTime.now());
* }
* </pre>
*
* <p>The above method does not run even if a scheduled time is reached until
* the application manually requests to schedule it by invoking the method,
* </p>
*
* <pre>
* if (decidedToScheduleDailyMessage) {
* bean.weekdaysAtSixThirtyAM("Good morning!");
* }
* </pre>
*
* @since 3.1
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD })
public @interface Schedule {
/**
* <p>Cron expression following the rules of {@link CronTrigger}.</p>
Expand Down
103 changes: 102 additions & 1 deletion specification/src/main/asciidoc/jakarta-concurrency.adoc
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
:sectnums:
= Jakarta Concurrency Specification, Version 3.2

Copyright (c) 2013, 2024 Oracle and/or its affiliates. All rights reserved.
Copyright (c) 2013, 2025 Oracle and/or its affiliates. All rights reserved.

Oracle and Java are registered trademarks of Oracle and/or its
affiliates. Other names may be trademarks of their respective owners.
Expand Down Expand Up @@ -2810,3 +2810,104 @@ components to manually control global transaction demarcation
boundaries. Task implementations may optionally begin, commit, and
roll-back a transaction. See EE.4 for details on transaction management
in Jakarta EE.

== Scheduled Methods

The `jakarta.enterprise.concurrent.Schedule` annotation can annotate a
CDI managed bean method to cause the method to run automatically on a
schedule. The method must have a `void` return type and no parameters.
The bean must not be a Jakarta Enterprise Bean.

After the application starts, the Jakarta EE Product Provider arranges for
non-overlapping invocation of the scheduled method to run on the default
`ManagedScheduledExecutorService` according to the schedule.

=== Skipped Invocation

A scheduled time is skipped if the difference between its actual start time
and its target start time would exceed the `skipIfLateBy` field of the
`Schedule` annotation or if the prior invocation ended after its scheduled time.

=== Lifecycle

Once the method runs, or if late, is skipped, the completion time (or time
of the skip) is used to compute the next time according to the schedule.
Invocation of the method continues according to the scheduled times until the
application stops or an invocation of the method raises an error or exception,
after which no further invocation of the method is scheduled.

=== Responsibilities and Requirements

==== Application Component Provider’s Responsibilities

Application Component Providers (application developers) (EE2.12.2)
specify the `jakarta.enterprise.concurrent.Schedule` annotation on one or more
CDI managed bean methods that have a return type of `void` and no parameters.

The Application Component Provider supplies the implementation of the
scheduled method. The application may raise an exception from the method
to prevent subsequent invocations of the method from continuing to be
scheduled.

The application must not invoke the method directly or submit it for
invocation in any other way.

===== Usage Example

In this example, an application component wants to run some business logic
every Monday, Wednesday, and Friday at 10:00 AM Central US time.

[source,java]
----
@ApplicationScoped
public class ScheduleBean {
@Schedule(daysOfWeek = { DayOfWeek.MONDAY, DayOfWeek.WEDNESDAY, DayOfWeek.FRIDAY },
hours = 10,
zone = "America/Chicago")
public void monWedFriAt10() {
System.out.println("Running at " + ZonedDateTime.now());
}
}
----

==== Jakarta EE Product Provider’s Responsibilities

The Jakarta EE Product Provider’s responsibilities are as defined in
EE.5.8.3.

The Jakarta EE Product Provider identifies CDI managed bean methods that are
annotated with the `jakarta.enterprise.concurrent.Schedule` annotation and
computes the next time from the schedule.

The Jakarta EE Product Provider submits tasks to the default
`ManagedScheduledExecutorService` to invoke scheduled methods, compute the
next time, and to reschedule for that time.

The Jakarta EE Product Provider makes the component context available on the
thread that invokes the scheduled method, such that the scheduled method is
able to look up resources in the component namespace.

The Jakarta EE Product Provider ceases to invoke scheduled methods after the
method raises an error or exception or the application stops.

=== Transaction Management

When a scheduled method is also annotated with
`jakarta.transaction.Transactional`, the transactional types which can be
used are:

* `jakarta.transaction.Transactional.TxType.REQUIRES_NEW` -
which causes the method to run in a new transaction
* `jakarta.transaction.Transactional.TxType.NOT_SUPPORTED` -
which causes the method to run with no transaction

When a scheduled method is not annotated as
`jakarta.transaction.Transactional` or the transaction type is set to
`TxType.NOT_SUPPORTED`, the Jakarta EE Product Provider
must support user-managed global transaction demarcation using the
`jakarta.transaction.UserTransaction` interface, which is described in the
Jakarta Transactions specification. User-managed transactions allow
components to manually control global transaction demarcation boundaries.
Scheduled method implementations may optionally begin, commit, and
roll back a transaction. See EE.4 for details on transaction management
in Jakarta EE.