Part 1: 26.04 Strict Parser
Nextflow 26.04 enables the strict parser by default. Existing DSL2 code that relied on broader Groovy compatibility now meets the parser in one of three ways: a hard parser error, a lint warning, or a silent acceptance that you should still migrate. Knowing which is which is the single most useful skill for the rest of the workshop.
Learning goals
- Distinguish what the strict parser errors on, warns on, and silently accepts but should still be migrated.
- Read a real
nextflow lintoutput and act on the first error before everything else. - Internalise the iterative migration loop: lint → fix first error → re-lint to surface warnings → re-lint until clean.
Theory: three severities, not just "removed/deprecated"
The upstream guides describe rule classes (removed, restricted, deprecated). What you actually see in nextflow lint on 26.04.1 is a different and more practical three-tier picture:
| Tier | What lint shows | Example | Effect on this script |
|---|---|---|---|
| Parser error | Error …: Unexpected input |
defaultSampleSuffix(value = '_trimmed') — assignment expression inside a call |
Compilation stops. No process runs. |
| Lint warning | Warn …: … is deprecated |
Channel.of(...), implicit closure parameter it |
Script still compiles and runs today. Will be removed in a future release. |
| Silent (still wrong) | nothing in lint, nothing at runtime | String suffix = ..., List fastqs = ..., Integer count = 0 |
Parses today. Migration guide says to replace with def because typed prefixes will not interact correctly with the upcoming static typing system (Part 6). |
The migration habit that follows from this table is mechanical and worth committing to muscle memory:
nextflow lint -project-dir . main.nf— read the first error. It blocks everything.- Fix that one error. Re-run lint.
- Now any warnings surface (lint hides warnings while there is an unresolved parser error). Fix every warning.
- Re-read the file by hand for the silent tier — typed prefixes, leftover imports, classes — because the tool will not flag them today.
nextflow run main.nf -profile test— runtime confirms the migration is behaviour-preserving, not just parse-clean.
The thing the upstream docs do not stress is point 3: warnings only appear once the script parses. A repository with many parser errors will look "clean of warnings" until you start fixing things — that is misleading and is the most common reason real migrations skip the deprecated-syntax sweep.
1. Demo: old script, new parser
Move to the demo folder:
cd code/01-strict-parser/demo
legacy.nf renames two sample IDs by appending a suffix. It mixes one parser error and three warnings on purpose:
String suffix = defaultSampleSuffix(value = '_trimmed') # parser error + silent typed prefix
Channel.of('sample_a', 'sample_b') # warning: uppercase Channel
.map { it + suffix } # warning: implicit it
.view { "renamed: ${it}" } # warning: implicit it
Try to run it:
nextflow run legacy.nf -profile test
Expected output
Error legacy.nf:9:47: Unexpected input: '='
│ 9 | String suffix = defaultSampleSuffix(value = '_trimmed')
╰ | ^
[ERROR] ERROR ~ Script compilation failed
Only the parser error appears. The three warnings stay hidden until the parse succeeds.
Now run the migrated version:
nextflow run main.nf -profile test
Expected output
renamed: sample_a_trimmed
renamed: sample_b_trimmed
Lint the migrated version to confirm there are no warnings either:
nextflow lint -project-dir . main.nf
Expected output
Linting Nextflow code..
Linting: main.nf
Nextflow linting complete!
✅ 1 file had no errors
To see the warning round in isolation, fix only the assignment expression in legacy.nf (replace value = '_trimmed' with '_trimmed') and re-run nextflow lint. You will see three warnings appear that were hidden a moment ago:
Warn legacy.nf:10:5: The use of `Channel` to access channel factories is deprecated -- use `channel` instead
Warn legacy.nf:11:16: Implicit closure parameter is deprecated, declare an explicit parameter instead
Warn legacy.nf:12:29: Implicit closure parameter is deprecated, declare an explicit parameter instead
That hidden round is the point of this part.
2. Exercise
The exercise builds a tiny FASTQ-renaming pipeline over HG002, HG003, and a control sample. It contains the same three error classes but in different positions: typed declarations now sit on List and String, the assignment-in-call hides on a different helper, the implicit it is inside .filter and .map instead of .map and .view, and Channel.fromList replaces Channel.of.
cd ../exercise
nextflow lint -project-dir . main.nf
nextflow run main.nf -profile test
Fix the script step by step so that, when you re-run lint:
- the parser error on line 10 is gone (the assignment expression
trimmedName(value = …)), - the three deprecation warnings are gone (
Channel.fromList,.filter { it … },.view { … ${it} }), - you also replace
List fastqsandString run_labelwithdefeven though lint does not complain — they are the silent tier from the theory section.
When nextflow run main.nf -profile test succeeds, the expected output is:
[run_trimmed.fastq.gz] HG002_trimmed.fastq.gz
[run_trimmed.fastq.gz] HG003_trimmed.fastq.gz
Compare with the worked solution only after you have tried the migration yourself:
cd ../solution
nextflow run main.nf -profile test
Checkpoint
- I can name the three severity tiers I will meet in 26.04 lint (error, warning, silent) and give one example of each.
- I know that warnings only appear once the file parses, so the first parser error has to come out first.
- I can run
nextflow lint -project-dir . main.nfand act on its output without running the workflow.
Continue to Part 2.