Quartz Special Characters: L, W, and # Explained
Deep token-level guidance for Quartz's L, W, and # operators so you can build precise schedules without runtime surprises.
Open Quartz GeneratorWhy Quartz Special Characters Matter
Most cron mistakes start before deployment, when schedule intent and syntax diverge. Standard five-field cron covers minutes, hours, day-of-month, month, and day-of-week. Quartz extends this with a seconds field, a year field, and three special characters—L, W, and #—that unlock scheduling precision standard cron cannot match.
These characters behave differently depending on which field they appear in, and small misunderstandings cascade into schedules that fire on the wrong day or skip runs entirely. This article explains each character with practical examples, validation checks, and clear next actions you can take in Cronwise's Quartz Generator.
The L Character: Last Day and Last Weekday
L stands for "last" and is valid in two fields: day-of-month and day-of-week. Its meaning changes depending on where you place it.
L in Day-of-Month
When used alone, L means the last day of the month. The expression 0 0 18 L * ? fires at 6:00 PM on the last day of every month—whether that is the 28th, 29th, 30th, or 31st. You never need to hardcode month lengths.
You can combine L with a negative offset. L-3 means three days before the last day. In a 31-day month that resolves to the 28th; in February of a non-leap year it resolves to the 25th. This is useful for billing cut-off windows.
L in Day-of-Week
In the day-of-week field, you pair L with a weekday number: 6L means the last Friday of the month. The expression 0 0 9 ? * 6L fires at 9:00 AM on the last Friday of every month—useful for payroll processing or month-end reports that must land on a specific weekday.
The W Character: Nearest Weekday
W is valid only in the day-of-month field. It selects the nearest weekday (Monday through Friday) to the specified date. Write 15W to mean "the weekday closest to the 15th."
How W Resolves
If the 15th falls on a Saturday, the schedule shifts to Friday the 14th. If it falls on a Sunday, it shifts to Monday the 16th. The resolution never crosses month boundaries: if the 1st is a Saturday, 1W resolves to Monday the 3rd, not to the previous Friday.
Combining L and W
Quartz allows the combination LW, meaning "the last weekday of the month." This is ideal for end-of-month financial closes that must run on a business day. If the last day is Saturday, LW resolves to Friday. Note that W cannot be combined with a list or range—expressions like 1W,15W are invalid.
The # Character: Nth Weekday of the Month
# is valid only in the day-of-week field and specifies the Nth occurrence of a weekday within a month. The syntax is day#N, where day is the weekday number (1–7, Sunday through Saturday) and N is the occurrence (1–5).
Practical Examples
0 0 10 ? * 2#1 fires at 10:00 AM on the first Monday of every month. 0 0 14 ? * 6#3 fires at 2:00 PM on the third Friday. These patterns replace fragile workarounds that rely on day-of-month ranges and conditional logic.
When N Exceeds Actual Occurrences
If you specify 2#5 (the fifth Monday), the schedule does not fire in months with only four Mondays. Quartz treats this as a no-match, not an error—a silent skip that makes run-count monitoring essential.
The # character cannot appear in day-of-month and cannot be combined with lists, ranges, or increments. Each day-of-week field accepts at most one # specification.
Edge Behavior and Failure Modes
Subtle mistakes with these characters rarely trigger validation errors but silently deviate from intent.
Common Pitfalls
| Expression | Expected | Actual | Fix |
|---|---|---|---|
0 0 9 L * 6 | Last day if Friday | Conflict: both day fields set | Use ? in one day field |
0 0 9 1W * ? (1st is Saturday) | Previous Friday | Monday the 3rd (no cross-month) | Verify with next-run preview |
0 0 9 ? * 2#5 | Fifth Monday monthly | Only months with five Mondays | Add run-count monitoring |
0 0 9 LW * ? in February | Last weekday | 26th, 27th, or 28th by year | Check timezone-aware preview |
The most dangerous error is specifying values in both day-of-month and day-of-week without using ? as a placeholder. Quartz requires exactly one day field to contain ?. Omitting it produces a parse error or undefined behavior depending on the Quartz version.
Decision Framework: Choosing the Right Character
Each special character solves a different scheduling problem. Selecting the right one depends on whether your schedule is anchored to a calendar date, a weekday, or a relative position within the month.
| Requirement | Character | Field | Example |
|---|---|---|---|
| Run on the last day of every month | L | Day-of-month | 0 0 18 L * ? |
| Run N days before month end | L-N | Day-of-month | 0 0 18 L-3 * ? |
| Run on the last specific weekday | nL | Day-of-week | 0 0 9 ? * 6L |
| Run on the nearest weekday to a date | W | Day-of-month | 0 0 9 15W * ? |
| Run on the last business day of month | LW | Day-of-month | 0 0 17 LW * ? |
| Run on the Nth weekday of the month | # | Day-of-week | 0 0 10 ? * 2#1 |
When your requirement is purely date-based ("the 15th"), you do not need special characters at all. Use W only when business-day alignment is required. Use # only when a recurring weekday occurrence matters more than a fixed date. And use L when month-end semantics must adapt to variable month lengths automatically.
Production Hardening: Pre-Deploy Checks
Before any Quartz schedule reaches production, run through these verification steps to catch edge cases that static validation cannot surface.
Verification Checklist
| Check | Why It Matters | Pass Criteria |
|---|---|---|
| Parse without errors | Confirms syntactic validity | No parser exceptions |
| Inspect next 10 runs | Surfaces month-boundary surprises | All dates match intent |
| Verify timezone alignment | Server vs. business TZ mismatches | Runs match target timezone |
Confirm ? placement | Both day fields active is undefined | One day field has ? |
| Check for silent skips | #5 may fire < 12 times/year | Annual count documented |
Cronwise's next-run preview returns the next 10 executions in your selected timezone, letting you verify these criteria without deploying a test job. If your team operates across time zones, review Cron Timezones Explained for Global Teams for additional guidance.
Putting It All Together
Quartz's L, W, and # characters give you scheduling power that standard cron cannot match. L adapts to variable month lengths, W aligns execution to business days, and # pins runs to specific weekday occurrences. Used correctly, they eliminate brittle workarounds and make schedule intent explicit in the expression itself.
The key decision rules are straightforward. Use L for month-end logic. Use W when you need the nearest weekday. Use # for the Nth weekday of a month. Always place ? in the day field you are not using. And always verify with a next-run preview before deploying.
Ready to build or validate your Quartz schedule? Open the Cronwise Quartz Generator to construct expressions visually, see plain-language explanations, and preview execution times in your target timezone. For more cron scheduling guides and deep dives, browse all cron articles on Cronwise.