S Stubborn

23 October 2026

Stubborn Contract 0.2.0

Release notes for Stubborn Contract 0.2.0 — what changed, and how to pick it up.

Stubborn Contract 0.2.0 is released and on Maven Central. Pin the stubborn-contract-dependencies BOM once and every module lines up.

Highlights

The consumer BOM no longer overrides Spring Boot

stubborn-contract-dependencies now manages only Stubborn Contract’s own artifacts, plus the libraries Spring Boot does not manage — WireMock and REST Assured. Everything else (Netty, Tomcat, Artemis, ZooKeeper, FreeMarker, httpclient5, icu4j, plexus-utils …) is left to whichever platform BOM you import.

Before this release the BOM also carried the version pins Stubborn uses for its own CVE gate. Those pins are correct on the day they are cut and wrong a month later: imported ahead of spring-boot-dependencies they silently replaced Boot’s managed versions, so a consumer on a newer Boot patch could be quietly downgraded by us — with Boot 4.1 that meant a spring-boot-starter-jetty application failing to start. The pins have moved into Stubborn’s own reactor, where they still gate our build and no longer leak into yours.

Fixes stubborn-sh/stubborn#142.

One deliberate exception: Jetty

Correction, added after this post went out. What this section originally said — that there was no version to pin because no ee10 release works against Jetty core 12.1 — is wrong. Jetty 12.1.x does publish the ee10 modules, and wiremock-jetty12:3.13.2 runs on them. 0.2.1 fixes this properly. The rest of this section describes what 0.2.0 shipped and how to work around it if you are staying on 0.2.0.

The failure was never a 12.0-only API in WireMock. It was version skew. spring-boot-dependencies imports jetty-bom and jetty-ee11-bom, so Boot manages Jetty core and the Jakarta EE 11 layer but not org.eclipse.jetty.ee10, which is the servlet layer wiremock-jetty12 is built against. With nothing managing it, ee10 resolved to the 12.0.30 that wiremock-jetty12 declares while Boot pulled the core to 12.1.x — and the Environment.ensure call comes from jetty-ee10-servlet’s own ServletContextHandler static initializer, not from WireMock, so an ee10 on the core’s line resolves it. Pinning everything to 12.0 cured the skew by dragging the core down, which is exactly what broke spring-boot-starter-jetty applications.

Thanks to @mhalbritter, who tried the ee10 import and showed it works (stubborn-sh/stubborn#142).

wiremock-jetty12:3.13.2 is built against Jetty’s ee10 servlet layer, which Boot does not manage. Left unmanaged it resolves to 12.0.30, a minor line below the 12.1.x core Boot manages, and the stub server dies on:

NoSuchMethodError: org.eclipse.jetty.util.component.Environment.ensure(java.lang.String)

WireMock’s shaded standalone distribution was tried as the way out and does not work — it relocates Handlebars, so Stubborn’s own response-template helpers cannot register with it.

So this release’s BOM manages Jetty 12.0.x, and only Jetty. The consequence, stated plainly: if you import the 0.2.0 BOM, a spring-boot-starter-jetty application runs on Jetty 12.0.x rather than Boot’s 12.1.x, and may not start at all — Boot’s own ee11 layer stays at 12.1.x on top of that 12.0.x core.

If you are on 0.2.0, do not exclude the org.eclipse.jetty entries (the original advice here, now withdrawn — that leaves the stub server on a split classpath). Instead let Boot keep its Jetty and bring ee10 up to meet it, by importing this ahead of the Stubborn BOM:

<dependency>
    <groupId>org.eclipse.jetty.ee10</groupId>
    <artifactId>jetty-ee10-bom</artifactId>
    <version>${jetty.version}</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>

${jetty.version} comes from spring-boot-starter-parent, so it follows Boot rather than freezing a version. From 0.2.1 this is unnecessary — the BOM manages ee10 at Boot’s Jetty line itself and leaves the core alone.

Upgrading from 0.1.x

If you relied on the BOM to pin a third-party library, pin it yourself — or let your platform BOM do it. Import order no longer matters for anything except Stubborn’s own modules, WireMock, REST Assured and Jetty.

The case most likely to bite: Spring Cloud Stream. Spring Cloud ships its own release train, so spring-boot-dependencies does not manage spring-cloud-stream and this BOM deliberately no longer does either. If you declare it without a version, import the train yourself:

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-dependencies</artifactId>
  <version>2025.1.2</version>
  <type>pom</type>
  <scope>import</scope>
</dependency>

Dependency updates

A note on the changelog below: #244 added a stubborn-contract-starter-stub-runner-standalone starter and #246 removed it again before this release. It relocated Handlebars, which stopped Stubborn’s own response-template helpers from registering. It is not part of 0.2.0 — don’t go looking for it on Central.

Use it

Pin the BOM once and every Stubborn Contract module lines up:

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>sh.stubborn</groupId>
      <artifactId>stubborn-contract-dependencies</artifactId>
      <version>0.2.0</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

All libraries are on Maven Central.

Standalone stub runner

Prefer running the stub runner as a server? Download stubborn-contract-stub-runner-app-0.2.0-exec.jar below (SHA-256 alongside) and run:

java -jar stubborn-contract-stub-runner-app-0.2.0-exec.jar

This executable jar is published here rather than to Maven Central.

What’s Changed

Full Changelog: https://github.com/stubborn-sh/stubborn-contract/compare/v0.1.3…v0.2.0

← All posts Read the Transition Guide