Module 3: The Upgrade Trap

In February 2023, SnakeYAML 2.0 shipped the first official fix from the open-source project — but it came with breaking API changes. This module shows what teams faced when security tools insisted they upgrade to snakeyaml version 2.0 or later.

Run all commands from the repository root (where compose.yaml lives).

Why not just use SafeConstructor?

That is the right question — and it is exactly what SnakeYAML’s own guidance recommends for YAML you do not trust: use SafeConstructor instead of Constructor so callers cannot smuggle in arbitrary Java types.

For a simple app that only needs basic YAML structures, that might be a reasonable code change. This demo app is not simple. Open PipelineConstructor.java:

public class PipelineConstructor extends Constructor {

    public PipelineConstructor() {
        super();
        TypeDescription pipelineType = new TypeDescription(PipelineDefinition.class);
        pipelineType.addPropertyParameters("stages", PipelineStage.class);
        this.addTypeDescription(pipelineType);
        this.addTypeDescription(new TypeDescription(PipelineStage.class));
    }
}

The team subclassed Constructor to teach SnakeYAML how to turn pipeline YAML into PipelineDefinition and PipelineStage objects — including nested stages lists. That pattern was normal in 2022.

SafeConstructor does the opposite of what this code needs: it refuses to create arbitrary Java types from YAML markers. Switching to it would block the attack from Module 2 — but it would also break (or severely complicate) the typed mapping the product depends on. You would need to:

  • Redesign PipelineYamlParser around a safer API

  • Re-test every valid pipeline shape your customers already submit

  • Ship and coordinate that application change — while security tools are already flagging your builds

So in late 2022 the realistic options were not "flip one line to SafeConstructor and go home." The three choices at the end of this module spell out what teams actually faced. The hands-on exercise below walks through choice 2: what happened when people tried to upgrade the jar after 2.0 finally shipped.

Attempt the naive upgrade

The obvious fix security tools pointed at was: upgrade to SnakeYAML 2.0 or later. In hindsight that sounds simple. In real time it was not — the bug became public in October 2022, but the first fixed open-source release did not land until February 2023. For more than four months there was no version on Maven Central you could bump to, even if you were willing to refactor.

This exercise fast-forwards to when 2.0 finally existed. Edit demo-app/pom.xml and change the SnakeYAML version:

<snakeyaml.version>2.0</snakeyaml.version>

Or use the property override without editing pom.xml:

podman compose --profile tools run --rm maven clean compile -Dsnakeyaml.version=2.0

Observe the failure

Compilation fails on the custom PipelineConstructor and related SnakeYAML API usage. SnakeYAML 2.0 removed the no-argument Constructor() constructor your subclass calls in super() — one symptom of a wider API overhaul.

This is not a one-line pom.xml fix. It is the same kind of refactoring you would have faced if you had rebuilt the parser around SafeConstructor — except the scanner told you to "just upgrade SnakeYAML," not "schedule a parser rewrite."

Your output should look like this (line numbers may vary slightly):

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.10.1:compile (default-compile) on project pipeline-definition-api: Compilation failure
[ERROR] /app/src/main/java/com/redhat/lightwell/demo/yaml/PipelineConstructor.java:[15,9] no suitable constructor found for Constructor(no arguments)
[ERROR]     constructor org.yaml.snakeyaml.constructor.Constructor.Constructor(org.yaml.snakeyaml.LoaderOptions) is not applicable
[ERROR]       (actual and formal argument lists differ in length)
[ERROR]     constructor org.yaml.snakeyaml.constructor.Constructor.Constructor(java.lang.Class<? extends java.lang.Object>,org.yaml.snakeyaml.LoaderOptions) is not applicable
[ERROR]       (actual and formal argument lists differ in length)
[ERROR]     constructor org.yaml.snakeyaml.constructor.Constructor.Constructor(org.yaml.snakeyaml.TypeDescription,org.yaml.snakeyaml.LoaderOptions) is not applicable
[ERROR]       (actual and formal argument lists differ in length)
[ERROR]     constructor org.yaml.snakeyaml.constructor.Constructor.Constructor(org.yaml.snakeyaml.TypeDescription,java.util.Collection<org.yaml.snakeyaml.TypeDescription>,org.yaml.snakeyaml.LoaderOptions) is not applicable
[ERROR]       (actual and formal argument lists differ in length)
[ERROR]     constructor org.yaml.snakeyaml.constructor.Constructor.Constructor(java.lang.String,org.yaml.snakeyaml.LoaderOptions) is not applicable
[ERROR]       (actual and formal argument lists differ in length)

The failure points at PipelineConstructor.java line 15 — the super() call. In SnakeYAML 2.0, every Constructor now requires a LoaderOptions argument. That is the breaking change you hit before you even get to runtime behavior.

Roll back

Restore 1.33 (or remove the -Dsnakeyaml.version=2.0 override) in pom.xml before continuing to Module 4.

The bigger trap (read-only)

Had your organization pursued a full "modernization" path in late 2022 / early 2023, you would also have faced — for this one service alone:

  • Spring Boot 2.7 → 3.x (requires Java 17, jakarta.* migration)

  • Springfox → springdoc (Springfox is incompatible with Boot 3)

  • WebSecurityConfigurerAdapter removed in Spring Security 6

  • Hibernate 5 → 6

Ballpark effort for a team already maintaining this app (order of magnitude, not a quote):

Work Typical time Notes

Fix only the SnakeYAML parser (2.0 API or SafeConstructor redesign)

~1–2 weeks

One service; includes regression testing on real pipeline YAML

Full Spring Boot 3 + Java 17 migration for this app

~2–4 months

Planning, code changes, QA, staging rollout — often a dedicated project

Portfolio: dozens of Java apps with snakeyaml on the classpath

Months to a year+

Scanners flag every affected dependency; each app that parses YAML like this needs its own assessment and possibly its own parser work. Transitive-only apps still burn time on triage, exceptions, and release coordination.

This demo app is one microservice. A platform team in 2022 might have had many — internal APIs, batch jobs, Jenkins plugins, shared libraries — all showing the same snakeyaml < 2.0 finding. Closing the gap app-by-app through upgrades and refactors does not scale the way a drop-in fixed jar on your current version does.

This application includes those 2022-era patterns deliberately. That path is a fourth kind of pain on top of the three choices below — not a shortcut around them.

The three choices

For an app like this one — parsing pipeline YAML from HTTP — teams in 2022–2023 were mostly stuck picking among three bad options:

  1. Refactor the parser now — Rebuild PipelineYamlParser (and PipelineConstructor) around a safer approach. Real engineering work: redesign, regression tests on every pipeline shape customers submit, a coordinated release. Roughly 1–2 weeks for this service alone; longer if the codebase is larger or understaffed.

  2. Wait for a fixed library, then refactor anyway — Wait more than four months for SnakeYAML 2.0 to appear on Maven Central, bump the version — and hit the compile failure you just saw. The wait did not deliver a drop-in fix. You still owe the same parser rewrite; you only burned calendar time while security tools kept flagging builds.

  3. Do nothing — Stay on SnakeYAML 1.33 and remain exposed. Hard to justify for an API that accepts YAML from the network (Module 2). Some teams argued they were safe because they only parsed their own config files — but their scanners did not care.

There was no fourth open-source option in that window: a fixed 1.33-compatible jar on Maven Central that closed the hole without code changes. That gap is what Module 4 addresses with Lightwell — a path that did not exist in 2022, demonstrated with today’s 1.33.0.rhlw-00001 artifact.