85
votes

I'm trying to get a cron job working within a legacy Java/Spring/Hibernate project, so I decided to use the spring scheduler.

I want myTask.doStuff to run at 12:00 on the first Sunday of every month.

In my application-context.xml I've configured my task scheduler like:

<task:scheduled-tasks scheduler="MyTaskScheduler">
    <task:scheduled ref="myTask" method="doStuff" cron="0 0 12 ? 1/1 SUN#1 *"/> <!-- Every first Sundy of the month -->
</task:scheduled-tasks>

<task:scheduler id="MyTaskScheduler" pool-size="10"/>

with the problem cron expression itself being the: 0 0 12 ? 1/1 SUN#1 *

and myTask is a bean, which has a method called doStuff that works perfectly when run from unit tests.

When I build and deploy I get a bootime exception from spring:

Caused by: java.lang.IllegalArgumentException: cron expression must consist of 6 fields (found 7 in 0 0 12 ? 1/1 SUN#1 *)
at org.springframework.scheduling.support.CronSequenceGenerator.parse(CronSequenceGenerator.java:233)
at org.springframework.scheduling.support.CronSequenceGenerator.<init>(CronSequenceGenerator.java:81)
at org.springframework.scheduling.support.CronTrigger.<init>(CronTrigger.java:54)
at org.springframework.scheduling.support.CronTrigger.<init>(CronTrigger.java:44)
at org.springframework.scheduling.config.ScheduledTaskRegistrar.afterPropertiesSet(ScheduledTaskRegistrar.java:129)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1477)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1417)

Given that i'm using cron expressions for the first time, my first assumption was that I was doing something wrong, but I double checked using cronmaker and it gave me the same result.

All the documentations says: A cron expression is a string consisting of six or seven subexpressions (fields).1

despite this I tried knocking off the 7th element(year) since it's not in any of the examples, and got a different error message:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.scheduling.config.ScheduledTaskRegistrar#0': Invocation of init method failed; nested exception is java.lang.NumberFormatException: For input string: "0#1"

... does org.springframework.scheduling support a different flavor of cron from everything else? the spring-specific documentation just says 'cron expressions'.

How can I get this cron expression to work as expected in this context? Any help at all would be appreciated.

At the moment my solution would be to simplify this expression to just run every Sunday, and prepend some Java logic to calculate which Sunday of the month it is, and see if that works - but that sort of defeats the purpose of the configuration approach and seems like an antipattern.

2
Have you tried 0 0 12 ? * SUN#1? (without the last *)Tom
Yes, I got a different error message - I mention it where I say "despite this I tried knocking off the 7th element" and list the error message. :)Paul
Spring scheduler does not support too rich cron expressionns(not sure it it's the case here), like Quartz Scheduler does. Try it with quartzEvgeni Dimitrov
@Evgeni So there are different 'flavors' of CRON then? Is SpringScheduler's Cron not expressive enough to do this job? (Don't want to switch dependencies if I can help it)Paul

2 Answers

129
votes

Spring Scheduled tasks are not in the same format as cron expressions.

They don't follow the same format as UNIX cron expressions.

There are only 6 fields:

  • second,
  • minute,
  • hour,
  • day of month,
  • month,
  • day(s) of week.

Asterisk (*) means match any. */X means "every X" (see examples).

Numeric days of the week do not work for me. Besides, "MON-FRI" is much easier to read. Here are some example expressions:

"0 0 18 * * MON-FRI" means every weekday at 6:00 PM. 

"0 0 */1 * * *" means every hour on the hour.

"0 0 */8 * * *" means every 8 hours on the hour.

"0 0 12 1 * *" means 12:00 PM on the first day of every month. 

Here you can find some additional information.

Also you may find the spring documentation useful.

62
votes

Taking some note from: https://www.baeldung.com/cron-expressions

A Spring Scheduled tasks is like this:

1 2 3 4 5 6 Index
- - - - - -
* * * * * * command to be executed
- - - - - -
| | | | | | 
| | | | | ------- Day of week (MON - SUN)
| | | | --------- Month (1 - 12)
| | | ----------- Day of month (1 - 31)
| |-------------- Hour (0 - 23)
| --------------- Minute (0 - 59)
----------------- Seconds (0 - 59)

From: https://www.cyberciti.biz/faq/how-do-i-add-jobs-to-cron-under-linux-or-unix-oses/

A Linux Cron job is like this:

1 2 3 4 5 Index
- - - - -
* * * * * command to be executed
- - - - -
| | | | |
| | | | ----- Day of week (0 - 7) (Sunday=0 or 7)
| | | ------- Month (1 - 12)
| | --------- Day of month (1 - 31)
| ----------- Hour (0 - 23)
------------- Minute (0 - 59)

Side note:

  • Some article said it is possible to have a 7 optional param which is year , I have tried using latest spring and it show error, so I don't think it is working.
  • If your Linux cron job expression is simple enough, seems like it is possible to just put an 0 in front and it will convert to the spring scheduled tasks expression
    • E.g. Every 5 minutes
      • */5 * * * * Linux cron job
      • 0 */5 * * * * Spring schedule tasks

Bonus: Spring Schedule Cron Generator

  1. Click on Show code snippet
  2. Click on Run Code snippet
  3. Have fun!

$('.select2').select2({
  width: '100%'
});

//// Init ////////////
$dropdown = $("#secondsSelect");
for (let i = 1; i < 60; i++) {
  $dropdown.append($("<option />").val(i).text(i));
}
$dropdown = $("#minSelect");
for (let i = 1; i < 60; i++) {
  $dropdown.append($("<option />").val(i).text(i));
}
$dropdown = $("#hoursSelect");
for (let i = 1; i < 24; i++) {
  $dropdown.append($("<option />").val(i).text(i));
}
$dropdown = $("#dayOfMonthSelect");
for (let i = 1; i < 32; i++) {
  $dropdown.append($("<option />").val(i).text(i));
}
//// Init End ////////////


$('.select2').on('select2:select', function(e) {
  let value = e.params.data.id;
  let prevValue = $(this).val().length > 0 ? $(this).val()[0] : null;

  if (value != parseInt(value)) {
    $(this).val(value).change();
  } else if (prevValue != parseInt(prevValue)) {
    $(this).val(value).change();
  }
  calculateSpringCron();
});

let r = function(dropdown) {
  return dropdown.val().join(",");

}

let calculateSpringCron = function() {

  let result = [
    r($("#secondsSelect")),
    r($("#minSelect")),
    r($("#hoursSelect")),
    r($("#dayOfMonthSelect")),
    r($("#monthsSelect")),
    r($("#weekdaySelect")),
  ];

  $("#result").val(result.join(" "));

  $("#result-expand").html(result.join(" &nbsp; &nbsp;"))

}

calculateSpringCron();
.ms-container {
  display: flex;
  flex-direction: column;
  width: 100%;
  padding-left: 3em;
  padding-right: 3em;
  background: none !important;
  padding-bottom: 5em;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/multi-select/0.9.12/css/multi-select.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/multi-select/0.9.12/js/jquery.multi-select.min.js"></script>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">

<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>

<link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.8/css/select2.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.8/js/select2.min.js"></script>

<div class="row">
  <div class="col-12">
    <h1>Spring Schedule Cron Generator</h1>
  </div>
</div>
<div class="row">
  <div class="col-4">
    Seconds:
    <select id="secondsSelect" class="select2" name="states[]" multiple="multiple">
      <option value="*" selected="selected">Every seconds</option>
      <option value="*/2">Every even seconds</option>
      <option value="1-59/2">Every odd seconds</option>
      <option value="*/5">Every 5 seconds</option>
      <option value="*/10">Every 10 seconds</option>
      <option value="*/15">Every 15 seconds</option>
      <option value="*/30">Every 30 seconds</option>
    </select>
  </div>
  <div class="col-4">
    Minutes:
    <select id="minSelect" class="select2" name="states[]" multiple="multiple">
      <option value="*" selected="selected">Every minutes</option>
      <option value="*/2">Every even minutes</option>
      <option value="1-59/2">Every odd minutes</option>
      <option value="*/5">Every 5 minutes</option>
      <option value="*/10">Every 10 minutes</option>
      <option value="*/15">Every 15 minutes</option>
      <option value="*/30">Every 30 minutes</option>
    </select>
  </div>
  <div class="col-4">
    Hours:
    <select id="hoursSelect" class="select2" name="states[]" multiple="multiple">
      <option value="*" selected="selected">Every hour</option>
      <option value="*/2">Every even hour</option>
      <option value="1-11/2">Every odd hour</option>
      <option value="*/3">Every 3 hour</option>
      <option value="*/4">Every 4 hour</option>
      <option value="*/6">Every 6 hour</option>
    </select>
  </div>
</div>

<div class="row">
</div>

<div class="row">
  <div class="col-4">
    Days of month:
    <select id="dayOfMonthSelect" class="select2" name="states[]" multiple="multiple">
      <option value="*" selected="selected">Every day of month</option>
      <option value="*/2">Even day of month</option>
      <option value="1-31/2">Odd day of month</option>
      <option value="*/5">Every 5 days of month (5,10,15...)</option>
      <option value="*/10">Every 10 days of month (10,20,30...)</option>
    </select>
  </div>
  <div class="col-4">
    Months:
    <select id="monthsSelect" class="select2" name="states[]" multiple="multiple">
      <option value="*" selected="selected">Every month</option>
      <option value="*/2">Even months</option>
      <option value="1-11/2">Odd months</option>
      <option value="*/4">Every 4 months</option>
      <option value="*/6">Every 6 months(half year)</option>
      <option value="1">Jan</option>
      <option value="2">Feb</option>
      <option value="3">Mar</option>
      <option value="4">Apr</option>
      <option value="5">May</option>
      <option value="6">Jun</option>
      <option value="7">Jul</option>
      <option value="8">Aug</option>
      <option value="9">Sep</option>
      <option value="10">Oct</option>
      <option value="11">Nov</option>
      <option value="12">Dec</option>
    </select>
  </div>
  <div class="col-4">
    Weekday:
    <select id="weekdaySelect" class="select2" name="states[]" multiple="multiple">
      <option value="*" selected="selected">Every weekday</option>
      <option value="MON-FRI">Weekdays (MON-FRI)</option>
      <option value="SAT,SUN">Weekend</option>
      <option value="SUN">SUN</option>
      <option value="MON">MON</option>
      <option value="TUE">TUE</option>
      <option value="WED">WED</option>
      <option value="THU">THU</option>
      <option value="FRI">FRI</option>
      <option value="SAT">SAT</option>
    </select>
  </div>
</div>
<div class="row">
  <div class="col-12">
    Result:
    <input id="result" class="form-control" /> With a bit of seperation for better viewing:<br/>
    <h1 id="result-expand"></h1>
  </div>
</div>