HangfireIO/Hangfire · error · CronFormatException

Wrong number of parts in the `{cronExpression}` cron express

Error message

Wrong number of parts in the `{cronExpression}` cron expression, you can only use 5 or 6 (with seconds) part-based expressions.

What it means

Thrown by RecurringJobEntity.ParseCronExpression when a cron expression that doesn't start with '@' does not split into exactly 5 or 6 (with seconds) whitespace-separated parts. Hangfire uses Cronos and requires standard 5-field or 6-field-with-seconds expressions. This is a CronFormatException surfaced before the expression is handed to Cronos.

Source

Thrown at src/Hangfire.Core/RecurringJobEntity.cs:300

            return String.Join(";", _recurringJob.Select(static x => $"{x.Key}:{x.Value}"));
        }

        public static CronExpression ParseCronExpression([NotNull] string cronExpression)
        {
            if (cronExpression == null) throw new ArgumentNullException(nameof(cronExpression));

            var format = CronFormat.Standard;

            if (!cronExpression.StartsWith("@", StringComparison.OrdinalIgnoreCase))
            {
                var parts = cronExpression.Split(SeparatorCharacters, StringSplitOptions.RemoveEmptyEntries);
                if (parts.Length == 6)
                {
                    format |= CronFormat.IncludeSeconds;
                }
                else if (parts.Length != 5)
                {
                    throw new CronFormatException(
                        $"Wrong number of parts in the `{cronExpression}` cron expression, you can only use 5 or 6 (with seconds) part-based expressions.");
                }
            }

            return CronExpression.Parse(cronExpression, format);
        }
    }
}

View on GitHub (pinned to c236dd0f93)

Solutions

  1. Use exactly 5 fields (min hour day month weekday) or 6 fields when seconds are needed.
  2. Validate the cron string with a unit test or a cron validator before calling AddOrUpdate.
  3. If you need named macros, use '@daily', '@hourly', etc., which bypass the part-count check.

Example fix

// before
RecurringJob.AddOrUpdate("j", () => Work(), "0 12 * *"); // 4 fields

// after
RecurringJob.AddOrUpdate("j", () => Work(), "0 12 * * *"); // 5 fields
Defensive patterns

Strategy: validation

Validate before calling

static bool IsValidCronParts(string cron)
{
    if (string.IsNullOrEmpty(cron) || cron.StartsWith("@")) return cron?.StartsWith("@") == true;
    var parts = cron.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
    return parts.Length == 5 || parts.Length == 6;
}

Try / catch

try { RecurringJob.AddOrUpdate(id, () => Work(), cron); }
catch (CronFormatException ex) { /* fix cron string */ }

Prevention

When it happens

Trigger: Passing a cron string with 4 fields (e.g. '* * * *'), 7 fields, empty string, or a malformed schedule. Happens during AddOrUpdate validation and during scheduling when the stored Cron is read.

Common situations: Quartz-style 7-field expressions mistakenly used. Extra/missing spaces or trailing tokens. Copying a cron from a system that uses a different field count. Empty or whitespace-only cron from a misconfigured setting.

Related errors


AI-assisted analysis of HangfireIO/Hangfire@c236dd0f93 (2026-08-13). Data as JSON: /api/errors/0be03723edaf417d. Report an issue: GitHub.