intermediate~1.5h

Scheduling with @Scheduled

Some work doesn't happen in response to a request — it happens every night at 2am, or every 30 seconds, forever. This module is how Spring Boot runs code on a clock instead of on a request.

Learning objectives

  • Beginner: Enable scheduling and write a method that runs on a fixed interval.
  • Intermediate: Use cron expressions to schedule a task for a specific time of day.
  • Advanced: Explain why the default scheduler runs single-threaded, and configure a thread pool when tasks can overlap or run long.

◆ The problem

Some work is inherently time-based, not request-based: expiring stale sessions, sending a daily digest email, polling a third-party API for updates. None of that has an incoming HTTP request to attach to.

@EnableScheduling on a configuration class turns on Spring's scheduling infrastructure; @Scheduled on a method tells it to run that method automatically.

@Configuration @EnableScheduling public class SchedulingConfig {} @Component public class SessionCleanupTask { @Scheduled(fixedRate = 60_000) // every 60 seconds public void expireStaleSessions() { ... } }

fixedRate/fixedDelay cover "every N seconds," but "every day at 2am" needs a cron expression instead.

@Scheduled(cron = "0 0 2 * * *") // second minute hour day month weekday public void sendDailyDigest() { ... }
FieldExampleMeaning
second0at second 0
minute0at minute 0
hour2at 2 AM
day-of-month*every day
month*every month
day-of-week*every weekday

⚠ Common real-world trap

By default, Spring runs ALL @Scheduled methods on a single shared thread — one slow-running scheduled task silently delays every other scheduled task in the application, since none of them can run until the thread frees up. A team can go months without noticing this until two unrelated scheduled jobs start mysteriously running late together.

The fix is configuring a proper thread pool:

@Bean public TaskScheduler taskScheduler() { ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); scheduler.setPoolSize(5); return scheduler; }

💻 Code example

@Bean public TaskScheduler taskScheduler() { ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); scheduler.setPoolSize(5); return scheduler; }

@Scheduled runs independently on EVERY instance of your application — if you deploy 3 replicas in Kubernetes, that "every night at 2am" job runs 3 times, not once, unless you explicitly coordinate it. For anything that must run exactly once across a cluster (not once per instance), you need a distributed lock (via Redis or a database row) or a dedicated scheduling system like Quartz configured for clustering, not plain @Scheduled.

✓ Quick recap

  • @EnableScheduling + @Scheduled runs code on a timer instead of a request.
  • Use fixedRate/fixedDelay for simple intervals, cron expressions for specific times.
  • The default scheduler is single-threaded — configure a TaskScheduler bean with a pool for anything that can run long or overlap.
  • @Scheduled runs per-instance, not per-cluster — coordinate with a distributed lock if a job must run exactly once across replicas.

Want a visual for this concept?

Generate a diagram tailored to “Scheduling with @Scheduled” — the AI picks whichever visual (flowchart, comparison, sequence, etc.) best fits.

Sign in to generate a visual →

Practice quiz

Next Step

Continue to Async Programming with @Async← Back to all Spring Boot chapters