# How Jenkins Triggers Schedule and Execute Builds: Cron-Based Architecture Explained

> Discover how Jenkins triggers scheduled builds with its cron-based architecture. Learn about timing specifications, next execution instants, and job queuing for efficient automation.

- Repository: [Jenkins/jenkins](https://github.com/jenkinsci/jenkins)
- Tags: internals
- Published: 2026-06-19

---

**Jenkins triggers schedule builds using a cron-based engine that parses timing specifications into bitmasks, calculates the next execution instant via `CronTabList.next()`, and queues jobs through `TimerTrigger.run()` when the scheduler's one-minute loop detects a match.**

The `jenkinsci/jenkins` repository implements a sophisticated scheduling system that translates user-defined cron expressions into precise build execution times. Understanding how Jenkins triggers schedule and execute builds requires examining the interaction between the ANTLR-based parser, the calendar arithmetic engine, and the core trigger lifecycle.

## Architecture of Jenkins Build Scheduling

Jenkins employs a multi-layered architecture to handle timed builds. The system bridges user configuration, temporal calculations, and the build queue through a coordinated sequence of components.

### From Configuration to TimerTrigger

When a user configures a cron schedule (e.g., `H/15 * * * *`) through the UI or Pipeline, Jenkins stores this specification in the job's [`config.xml`](https://github.com/jenkinsci/jenkins/blob/main/config.xml). Upon loading, the system instantiates a **`TimerTrigger`** object from `hudson.triggers.TimerTrigger`, which extends the generic `Trigger<T>` superclass.

The constructor passes the raw spec string to the parent class, which immediately parses it into a **`CronTabList`**—a collection of one or more `CronTab` objects representing individual cron lines. This parsing occurs via `CronTabList.create()`, which handles the translation from textual representation to executable schedule.

### Parsing Cron Specifications with ANTLR

The cron parser relies on ANTLR grammars defined in `core/src/main/antlr4/hudson/scheduler/CrontabLexer.g4` and `CrontabParser.g4`. This parser translates the spec into four bitmasks representing minutes, hours, days, and months, plus a day-of-week mask.

The resulting **`CronTab`** object (from `hudson.scheduler.CronTab`) can answer temporal queries through methods like `check(Calendar)`, which determines if a given instant matches the specification. For load distribution, the parser utilizes the **`Hash`** class (from `hudson.scheduler.Hash`) to convert the `H` symbol into a deterministic offset based on the job name.

### The One-Minute Scheduling Loop

The core scheduler (`hudson.scheduler.Cron`) executes once per minute via a dedicated **`Timer`** thread. During each iteration, the system queries registered triggers for their next firing time using `CronTabList.next()`. 

This method leverages `CronTab.ceil(Calendar)` to compute the nearest future instant matching the cron specification. Conversely, `previous()` uses `floor()` to find past matches. When the current time reaches the computed instant, the scheduler invokes `Trigger.run()`.

## How TimerTrigger Executes Builds

When the scheduling loop determines a trigger should fire, it calls the `run()` method implemented in `TimerTrigger`. This method performs a straightforward enqueue operation:

```java
job.scheduleBuild(0, new TimerTriggerCause());

```

The **`TimerTriggerCause`** (a subclass of `hudson.model.Cause`) serves a dual purpose: it places the build in the queue and provides UI metadata indicating the build was "Triggered by Timer." The build executor then picks up the queued item and proceeds through the standard pipeline execution.

## Calculating Next Run Times with CronTab and CronTabList

The temporal logic resides primarily in `hudson.scheduler.CronTabList` and its constituent `CronTab` instances. These classes handle the complex calendar arithmetic required to resolve cron expressions against real-world time.

**Key methods include:**

- **`check(Calendar)`** – Determines if the provided calendar instant satisfies the cron specification
- **`ceil(Calendar)`** – Finds the next matching instant at or after the given time
- **`floor(Calendar)`** – Finds the previous matching instant at or before the given time

For jobs utilizing the hash symbol (`H`) to distribute load, the `Hash.from(String)` method generates consistent offsets based on project names, ensuring that similarly scheduled jobs do not simultaneously overwhelm the system.

## Handling Edge Cases and Invalid Dates

The scheduler includes robust validation for impossible date specifications. When `CronTabList.create()` or `CronTab.ceil()` encounters a cron expression that cannot be satisfied—such as `0 0 31 2 *` (February 31st)—the system throws **`RareOrImpossibleDateException`** (from `hudson.scheduler.RareOrImpossibleDateException`).

This exception prevents the scheduler from entering an infinite loop searching for non-existent dates and alerts administrators to configuration errors.

## Practical Examples

### Defining a Timer Trigger in a Pipeline Script

The declarative Pipeline syntax abstracts the underlying `TimerTrigger` creation:

```groovy
pipeline {
    triggers {
        // Run every 15 minutes, offset by a hash of the job name
        cron('H/15 * * * *')
    }
    stages {
        stage('Build') {
            steps {
                echo 'Running scheduled build...'
            }
        }
    }
}

```

This creates a `TimerTrigger` with the spec `'H/15 * * * *'`, utilizing the `Hash` class to calculate the offset.

### Manually Creating a TimerTrigger from Java

For programmatic job creation, instantiate the trigger directly:

```java
import hudson.triggers.TimerTrigger;
import hudson.model.FreeStyleProject;
import jenkins.model.Jenkins;

FreeStyleProject job = Jenkins.get().createProject(FreeStyleProject.class, "example");
TimerTrigger trigger = new TimerTrigger("H 0 * * *"); // run once a day at a hashed hour
job.addTrigger(trigger);
job.save(); // persists the trigger in config.xml

```

The constructor stores the spec string; Jenkins later parses it into a `CronTabList` via `CronTabList.create()`.

### Querying the Next Run Time via Groovy Console

Administrators can inspect future execution times using the Script Console:

```groovy
import hudson.scheduler.CronTabList
import hudson.scheduler.Hash
import hudson.model.Item

Item item = Jenkins.instance.getItem("example")
String spec = item.getTriggers().values().first().getSpec() // assume only TimerTrigger
CronTabList ctl = CronTabList.create(spec, Hash.from(item.getFullName()))
Calendar next = ctl.next()
println "Next run will be at ${next.getTime()}"

```

`CronTabList.next()` internally calls `CronTab.ceil` to compute the upcoming timestamp.

### Handling a Rare or Invalid Spec

When validating user input or testing edge cases, catch impossible date exceptions:

```java
try {
    CronTabList ctl = CronTabList.create("0 0 31 2 *", null); // Feb 31 does not exist
    Calendar next = ctl.next(); // throws RareOrImpossibleDateException
} catch (hudson.scheduler.RareOrImpossibleDateException e) {
    println "Cron spec is impossible to satisfy."
}

```

The parser detects impossible dates during the ceiling calculation and throws the specific exception defined in `hudson.scheduler.RareOrImpossibleDateException`.

## Summary

- **TimerTrigger** (from [`hudson/triggers/TimerTrigger.java`](https://github.com/jenkinsci/jenkins/blob/main/hudson/triggers/TimerTrigger.java)) serves as the concrete implementation that queues builds when cron specifications match.
- **CronTabList** and **CronTab** (in `hudson/scheduler/`) perform the core calendar arithmetic using bitmasks and ANTLR-parsed grammar.
- The scheduler runs a one-minute loop checking `next()` times, invoking `run()` only when the current time reaches the calculated instant.
- **RareOrImpossibleDateException** safeguards against unsatisfiable specifications like February 31st.
- The **Hash** class enables load distribution by converting the `H` symbol into job-specific offsets.

## Frequently Asked Questions

### How does Jenkins handle the H (hash) symbol in cron expressions?

Jenkins converts the `H` symbol to a numeric value using `hudson.scheduler.Hash.from(String)`, which hashes the job name to produce a consistent offset within the allowed range. This distributes builds across the time window rather than executing them simultaneously, preventing resource contention when multiple jobs share a schedule like `H/15 * * * *`.

### What happens if a cron spec specifies an impossible date like February 31st?

The system throws `RareOrImpossibleDateException` (from `hudson.scheduler.RareOrImpossibleDateException`) when `CronTab.ceil()` or `CronTabList.next()` detects that the specification cannot be satisfied within a reasonable time window. This prevents the scheduler from hanging while searching for non-existent calendar dates.

### How can I programmatically determine when a Jenkins job will run next?

Access the job's triggers, retrieve the spec string, and call `CronTabList.create(spec, Hash.from(jobName))` to obtain a `CronTabList` instance. Invoking `next()` on this object returns a `Calendar` representing the next execution time, calculated using the `ceil()` method across all configured cron tabs.

### What is the difference between the Trigger class and TimerTrigger in Jenkins?

`hudson.triggers.Trigger` is the abstract base class defining the contract for all trigger types, including SCM polling and remote triggers. `TimerTrigger` is the concrete implementation specifically designed for cron-based scheduling, extending `Trigger` to add cron spec parsing and the `TimerTriggerCause` attribution for build queue entries.