Skip to content

Move Scala 3 to 3.9.0 LTS - #1623

Merged
julien-truffaut merged 4 commits into
masterfrom
scala-3.9-lts
Sep 5, 2026
Merged

julien-truffaut merged 4 commits into
masterfrom
scala-3.9-lts

Conversation

@julien-truffaut

@julien-truffaut julien-truffaut commented Sep 4, 2026 •

Copy link
Copy Markdown
Member

Moves the Scala 3 build from 3.3.8 to 3.9.0 LTS (released 2026-09-03).

Warning

This raises the JDK baseline for Scala 3 artifacts from 11 to 17. That is forced by the compiler, not a preference — see below.

Why the JDK baseline has to move

3.9.0 will not emit Java 11 bytecode. From ScalaSettingsProperties.scala at tag 3.9.0:

private val minTargetVersion = 17
private val maxTargetVersion = 26
private val minReleaseVersion = 17

Against 3.3.8, where the minimum came from the full classfileVersionMap and went down to Java 8. Keeping -release:11 fails the build outright:

[error] 11 is not a valid choice for -java-output-version

The same limit rules out the temurin@11 CI leg: supported release versions are computed as (minReleaseVersion to min(jdkVersion, maxTargetVersion)), which on JDK 11 is (17 to 11) — an empty range. So the matrix moves to temurin@17 and the workflows are regenerated.

Scala 2.13 is unaffected and keeps -release:8.

The release flag now follows the binary version, on every platform

monocleJvmSettings keyed its flags off a string prefix, and the JS and Native settings added -release:8 unconditionally:

lazy val monocleJvmSettings = ... ++ Seq(scalacOptions ++= {
  if (scalaVersion.value.startsWith("3.3.")) Seq("-Yfuture-lazy-vals", "-release:11")
  else if (scalaBinaryVersion.value == "3")  Nil        // 3.9.0 would land here
  else                                        Seq(defaultReleaseOption)
})
lazy val monocleJsSettings     = ... ++ Seq(scalacOptions += defaultReleaseOption)  // "-release:8"
lazy val monocleNativeSettings = ... ++ Seq(scalacOptions += defaultReleaseOption)  // "-release:8"

Two separate problems there. On the JVM, bumping the version alone would have stopped matching the first branch and silently dropped the release flag — no error, just a changed bytecode target in published artifacts. On JS and Native, -release:8 is rejected outright under 3.9 (8 is not a valid choice for -java-output-version); it only ever worked because Scala 3 was pinned at 3.3.8.

All three now route through one setting:

lazy val releaseOption = Def.setting {
  if (scalaBinaryVersion.value == "3") scala3ReleaseOption else defaultReleaseOption
}

-Yfuture-lazy-vals goes too: the setting exists in 3.3.8 but was removed in 3.9.0, so passing it would fail the build. Its behaviour is the default now.

scalaNextVersion 3.8.4 → 3.10.0-RC1

scalaNextTest depends on core, now built by 3.9.0 and emitting TASTy 28.9. The 3.8.4 compiler reads at most 28.8:

[error] Forward incompatible TASTy file has version 28.9, produced by Scala 3.9.0,
[error]   expected stable TASTy from 28.0 to 28.8.

The project exists to exercise the line ahead of what we publish, so it has to move past 3.9.0 rather than trail it. Note this puts an RC in CI — deliberate, and the reason scalaNextTest exists, but worth flagging.

Verification

Run locally on 3.9.0:

  • rootJVM/test — 2006 tests, 0 failures
  • coreJVM / coreJS / coreNative / macrosJS / macrosNative compile
  • scalaNextTestJVM/test on 3.10.0-RC1 against 3.9.0-built core — 36 tests, 0 failures
  • mimaReportBinaryIssues, scalafmtSbtCheck, githubWorkflowCheck all clean
  • Re-ran the cross-build on 2.13.18 to confirm no regression; resolved flags verified as -release:17 on Scala 3 and -release:8 on 2.13

As a side effect the regenerated workflow clears the Using JDK 11 with Scala Native ... is deprecated and Using sbt-scalajs on JDK 11 is deprecated warnings that were showing up in release logs — both wanted JDK 17 already.

🤖 Generated with Claude Code

https://claude.ai/code/session_01QRbVCYWp3bquNaXPYW12JY

Scala 3.9.0 is the new LTS line, superseding 3.3.x.

The compiler no longer accepts a Java 11 output target: 3.9.0 sets
minReleaseVersion = 17, so `-release:11` is rejected outright with
"11 is not a valid choice for -java-output-version". Scala 3 artifacts
therefore now require JDK 17 at runtime, and the JVM target moves to 17.

For the same reason the temurin@11 CI leg cannot build Scala 3 at all
under 3.9 -- its supported release range computes as (17 to 11), i.e.
empty -- so the matrix moves to temurin@17 and the workflows are
regenerated.

The scalacOptions conditional was keyed off a "3.3." version prefix. Left
as-is it would have stopped matching and silently dropped the release
flag from Scala 3 JVM builds. It is now keyed off the binary version.
`-Yfuture-lazy-vals` is dropped with it: the setting no longer exists in
3.9.0, so passing it would fail the build. Scala 2.13 keeps -release:8.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QRbVCYWp3bquNaXPYW12JY
julien-truffaut and others added 2 commits September 4, 2026 14:54
The JS and Native settings added -release:8 unconditionally, which only
worked because Scala 3 was pinned to 3.3.8. Scala 3 has rejected targets
below 17 since 3.8 (minReleaseVersion = 17), so under 3.9.0 both fail
with "8 is not a valid choice for -java-output-version".

Route all three platforms through a single releaseOption keyed off the
binary version, so Scala 3 gets -release:17 and Scala 2.13 keeps
-release:8 everywhere.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QRbVCYWp3bquNaXPYW12JY
scalaNextTest depends on core, which is now built by 3.9.0 and emits
TASTy 28.9. The 3.8.4 compiler reads at most 28.8, so the Scala Next
jobs failed with "Forward incompatible TASTy file has version 28.9 [...]
expected stable TASTy from 28.0 to 28.8".

The project exists to exercise the compiler line ahead of the one we
publish, so it has to move past 3.9.0 rather than trail it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QRbVCYWp3bquNaXPYW12JY

@cquiroz cquiroz left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

scalaNextTest existed for one reason: NamedTuple support could not be
tested on the version we published against, because scala/NamedTuple
does not exist in 3.3.x at all. It held two files and needed a separate
compiler, a version skew, and a scalacOptions workaround to carry them.

On 3.9.0 NamedTuple is present and no longer experimental, so the two
tests compile and run in the ordinary test project on the same compiler
as everything else. That removes the project, scalaNextVersion (and with
it the 3.10.0-RC1 prerelease from CI), the "-release:8"/"-Ykind-projector"
removal that only papered over the version mismatch, and three CI steps.

The tests move to test/shared/src/test/scala-3, so they stay Scala 3
only and 2.13 does not see them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QRbVCYWp3bquNaXPYW12JY
@julien-truffaut
julien-truffaut merged commit 1844226 into master Sep 5, 2026
23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants