Careeroza
Scheduled Tasks
Running Jobs on a Timer
@SpringBootApplication
@EnableScheduling
public class ShopEaseApplication { ... }
@Component
@Slf4j
@RequiredArgsConstructor
public class ShopEaseJobs {
private final CartRepository carts;
private final ReportService reports;
@Scheduled(fixedRate = 60_000) // every 60 s, measured from the start of each run
public void releaseExpiredReservations() { ... }
@Scheduled(fixedDelay = 30_000, initialDelay = 10_000) // 30 s after the previous run ENDS
public void syncStockFromWarehouse() { ... }
@Scheduled(cron = "0 0 2 * * *", zone = "Asia/Kolkata") // every day at 02:00 IST
public void sendDailySalesReport() {
reports.emailYesterdaySales();
}
@Scheduled(cron = "${shopease.jobs.abandoned-cart-cron:0 0 */3 * * *}")
public void remindAbandonedCarts() {
log.info("Reminded {} users", carts.remindAbandoned(Duration.ofHours(3)));
}
}Cron Format
| Expression | Meaning |
|---|---|
0 0 2 * * * | every day at 02:00:00 |
0 */15 * * * * | every 15 minutes |
0 0 9 * * MON-FRI | 09:00 on weekdays |
0 0 0 1 * * | midnight on the 1st of every month |
Spring cron has six fields: second, minute, hour, day of month, month, day of week (Linux crontab has five, without seconds).
Scheduling in Plain Java
Example
@Scheduled(fixedDelay=...) sits on top of a ScheduledExecutorService. This job runs every 100 ms and the program stops it after three runs:
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
public class Main {
public static void main(String[] args) throws Exception {
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
AtomicInteger runs = new AtomicInteger();
CountDownLatch done = new CountDownLatch(3);
scheduler.scheduleWithFixedDelay(() -> {
int n = runs.incrementAndGet();
if (n <= 3) {
System.out.println("run " + n + ": released " + (n * 2) + " expired cart reservations");
done.countDown();
}
}, 0, 100, TimeUnit.MILLISECONDS);
done.await();
scheduler.shutdownNow();
System.out.println("scheduler stopped");
}
}Output
run 1: released 2 expired cart reservations run 2: released 4 expired cart reservations run 3: released 6 expired cart reservations scheduler stopped
Several Instances and Pool Size
Note: If ShopEase runs on 3 servers, every @Scheduled job runs 3 times. Use ShedLock (a lock row in the database) or a single worker instance so the daily report is sent once.
@Scheduled(cron = "0 0 2 * * *", zone = "Asia/Kolkata")
@SchedulerLock(name = "dailySalesReport", lockAtMostFor = "PT30M")
public void sendDailySalesReport() { ... }Example
Scheduled tasks share a single thread by default. Give them a pool:
application.yml
spring:
task:
scheduling:
pool:
size: 4
thread-name-prefix: shopease-sched-Practice
Exercise: Write a job that runs every Sunday at 23:30 IST and deletes cart items older than 30 days.
Show answer
@Scheduled(cron = "0 30 23 * * SUN", zone = "Asia/Kolkata")
@Transactional
public void purgeOldCartItems() {
int deleted = cartItemRepository.deleteByAddedAtBefore(Instant.now().minus(Duration.ofDays(30)));
log.info("Purged {} old cart items", deleted);
}