Skip to content

Modernize the toolchain: Scala 2.13.16, JDK 21, Apache Pekko, and wider PostgreSQL support#1383

Draft
GeekOfWires wants to merge 7 commits into
psforever:masterfrom
GeekOfWires:stack-modernization
Draft

Modernize the toolchain: Scala 2.13.16, JDK 21, Apache Pekko, and wider PostgreSQL support#1383
GeekOfWires wants to merge 7 commits into
psforever:masterfrom
GeekOfWires:stack-modernization

Conversation

@GeekOfWires

Copy link
Copy Markdown

Process Proposal

This PR exists primarily to give the project visibility into the full process of making
the changeover, shown as one continuous path rather than a polished end state. It is
admittedly larger than a comfortable single merge, and would reasonably be broken up
before landing.

It is presented here as a single pull request because the changes are interdependent:
getting from Akka-runnable on the earlier JDK to Pekko-runnable on the latest JDK took
several linked steps — toolchain bump → Akka→Pekko migration → JDK 21 → the runtime and
dependency fixes that only surface once the server actually boots — and no single step is
runnable on its own without the others. Keeping them together documents the real path taken
and lets reviewers see exactly what each hop required, before deciding how to slice it for
merge.

Summary

This branch modernizes the build/runtime stack in small, individually-reviewable phases,
each in its own commit. Nothing in game logic or the network protocol changes — this is
purely infrastructure. It compiles green, runs on JDK 21, and introduces zero test
regressions
(evidence below).

# Commit What it does
0 Phase 0: bump Scala, sbt, and low-risk dependencies Scala 2.13.10 → 2.13.16, sbt 1.8.2 → 1.10.7, plus low-risk dependency bumps
1 Phase 1: migrate Akka 2.6.20 -> Apache Pekko 1.1.x akka.*org.apache.pekko.* (pekko-actor 1.1.3, pekko-http 1.1.0), akka.confpekko.conf, config namespace akka { }pekko { }
2 Phase 2: move build/runtime/CI to JDK 21 Dockerfile builder + eclipse-temurin:21-jre runtime, CI JVM → temurin 21, bcprov-jdk18on 1.78.1
3 Phase 3: widen PostgreSQL version support Flyway 10 (flyway-core + flyway-database-postgresql 10.20.1), postgresql JDBC 42.7.4; CI matrix now proves migrations across 12.8 / 16 / 17 without dropping the current version
Add missing Default import in LocalActionTest Fixes a pre-existing test-compile break on master (see below)
Enable server runtime on JDK 21: Kamon 2.7.7, Kanela 2.1.1, logback 1.5.18 Runtime-only fixes uncovered by actually launching the server on JDK 21 (see below)
Fix Docker image tag; run compose stack on JDK 21 / Postgres 18 The prior JDK-21 sbt image tag isn't published on Docker Hub; also moves docker-compose off the stale JDK 8 image

Why

  • License future-proofing (the main motivation). Akka moved to the BSL 1.1 license at
    2.7.0; the project is pinned to the last Apache-2.0 Akka (2.6.20). Apache Pekko 1.x is the
    community fork of Akka 2.6.x under Apache-2.0 and is a near drop-in (namespace change only).
    Migrating now keeps the actor layer on a permissively-licensed, actively-maintained base and
    unblocks future actor/stream/HTTP upgrades.
  • Supported toolchain. JDK 21 (current LTS) + Scala 2.13.16 + sbt 1.10.7 are all
    actively supported; the previous sbt 1.8.2 / Scala 2.13.10 combination will not even start
    on JDK 21.
  • PostgreSQL headroom. Flyway 10 + a current JDBC driver let operators run newer
    PostgreSQL majors (verified up to 18) while the CI matrix keeps proving the current 12.8
    still migrates — no forced deprecation.

Verification

  • sbt clean test compiles all source roots and runs the full suite on JDK 21.
  • The stack was run end to end via docker-compose: the server boots on JDK 21, Pekko's
    Slf4jLogger starts, Flyway applies all 17 migrations to a fresh PostgreSQL 18.4
    database, and the login server listens. (Flyway 10.20.1 emits only a soft "newer than
    tested" warning on PG 18 and migrates successfully.)

Test status — please read

The suite currently reports 192 pass / 38 fail (scalatest) + 3 specs2 failures. These are
not introduced by this branch — they reproduce identically on today's master. I confirmed
this with a full attribution matrix:

Config Scala Actor lib JDK Result
master (unchanged) 2.13.10 Akka 2.6.20 17 192 / 38 + 3
this branch, Pekko reverted 2.13.16 Akka 2.6.20 17 & 21 192 / 38 + 3
this branch 2.13.16 Pekko 1.1.3 21 192 / 38 + 3

Same count, same suites across every axis → the Pekko/JDK/Scala changes flip none of them.
The failing suites are timing-sensitive objects.* ActorTests (the ActorTest base class
documents this flakiness) tied to the message bundling introduced in #1379; a same-toolchain
Akka baseline reproduces them identically. Happy to share the full per-suite breakdown.

Separately, the Add missing Default import in LocalActionTest commit fixes a spot where
master's test sources don't currently compile (LocalActionTest references Default.GUID0
without importing net.psforever.objects.Default).

Deliberately conservative / follow-ups

  • pekko-stream / pekko-http / cluster modules are kept for parity but have no direct
    imports yet — safe removal candidates in a follow-up.
  • sbt-scalafix is temporarily disabled (semanticdbEnabled := false) because 0.10.4 pins a
    semanticdb with no 2.13.16 build; re-enable alongside a scalafix bump.
  • Kamon's kamon-pekko instrumentation and a jasync/Quill bump are left for a later pass.

Reviewing commit-by-commit is recommended — each phase stands on its own and can be split into
separate PRs if that's preferred.


Prepared with assistance from Claude (Opus 4.8); commits carry Co-Authored-By trailers accordingly.

GeekOfWires and others added 7 commits July 17, 2026 22:15
Toolchain:
- Scala 2.13.10 -> 2.13.16
- sbt 1.8.2 -> 1.10.7 (needed for JDK 21 in a later phase)

Libraries (patch/minor only, no slf4j-2 / cross-major boundary crossings):
- specs2 4.20.9, scalatest 3.2.19, logback 1.2.13, jansi 2.4.1,
  nscala-time 2.34.0, json4s 4.0.7, config 1.4.3, pureconfig 0.17.7,
  enumeratum 1.7.3, commons-io 2.16.1, sentry 6.34.0, circe 0.14.9

Scala 2.13.16 source/tooling fixes (so the bump actually builds):
- Zones.scala: annotate the anonymous-subclass `val zone` with an explicit
  `: Zone` type; 2.13.16 rejects the recursive value (its body references it).
- semanticdb disabled: sbt-scalafix 0.10.4 pins semanticdb 4.6.0, which has no
  2.13.16 build; re-enable with a scalafix bump that supplies a compatible
  semanticdb (linter only; does not affect compilation or tests).
- sbt plugins left at their existing versions (earlier speculative bumps did not
  resolve; plugin currency is out of scope for this branch).

Deferred to later phases: Akka->Pekko, postgres/flyway/quill,
bcprov jdk15on->jdk18on, and the coupled scodec-2 / scala-graph-2 / logback-1.5 bumps.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pekko is the Apache-2.0 licensed fork of Akka 2.6.x; a drop-in namespace/
coordinate migration across ALL source roots (src/, server/, tools/).

- build.sbt: com.typesafe.akka:*:2.6.20 -> org.apache.pekko:*:1.1.3
  (pekko-http 10.2.10 -> 1.1.0); removed the "do not upgrade (BSL)" pin.
- Source: akka.* -> org.apache.pekko.* across root src/, the server/ subproject,
  and tools/ (actor/util/pattern/io/event/dispatch/stream/testkit + all imports),
  including Server.scala's aliased `import akka.{actor => classic}`.
- Two files in the actor package to reach internals moved akka.actor ->
  org.apache.pekko.actor: MDCContextAware (protected[akka] aroundReceive ->
  protected[pekko]) and MDCPropagatingExecutionContext.
- Vendored scodec<->ByteString interop renamed scodec.interop.akka ->
  scodec.interop.pekko (Scala package object + the Java PrivacyHelper reaching
  Pekko's bytecode-public ByteString1C) and its 3 importers.
- Config: akka.conf -> pekko.conf; namespace akka{} -> pekko{}, logger class
  values -> org.apache.pekko.*; application.conf include; test config paths
  akka.* -> pekko.* (root + server ActorTest).
- Two server tests (AutoRepair{,Integration}Test): the cross-package relative
  `import base.FreedContextActorTest` absolutized to
  `import actor.base.FreedContextActorTest` (Scala 2.13.16 no longer resolves
  the relative-sibling form).

Verified: all source roots compile (root + server + tools) on Pekko 1.1.3.

Follow-ups (not blocking): unused pekko-stream/http/cluster-*/protobuf-v3 kept
for parity; Kamon still carries Akka instrumentation (add kamon-pekko later,
kanela no-ops on absent classes).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Dockerfile builder: JDK 11 image -> sbtscala JDK 21 image (exact tag flagged
  in a comment to confirm on Docker Hub; the project's Scala/sbt come from
  build.sbt/build.properties, not the image).
- Dockerfile runtime: openjdk:18-slim -> eclipse-temurin:21-jre (openjdk images
  are deprecated; this also aligns build and runtime on one JDK, fixing the
  prior 11/18 split).
- CI (test.yaml): coursier jvm adopt:11 -> temurin:21.
- bcprov-jdk15on 1.70 -> bcprov-jdk18on 1.78.1 (jdk15on is the legacy artifact
  name; jdk18on is the modern-JDK build; API-compatible).

JDK 21 is supported now that the actor toolkit is Pekko 1.1 (JDK 8/11/17/21);
Akka 2.6 was only supported through JDK 17.

Not built in-session (no Docker/sbt); confirm the builder image tag, then build.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Driver/migration stack (enabled by the JDK 21 move):
- postgresql JDBC 42.5.4 -> 42.7.4 (spans PG12..18; still supports the current
  12.8 CI / 14 compose servers).
- Flyway 9.16.0 -> 10.20.1 + the now-separate flyway-database-postgresql module
  (Flyway 10 is modular). 10.x supports PG16/17 and requires JDK 17+, which Phase 2
  provides. Server.scala already uses the 10-compatible configure()/load() API.
- Config.toJdbc now passes the configured sslmode to the Flyway/JDBC connection so
  it matches the jasync runtime path (they previously negotiated TLS differently).

Prove-and-preserve, not a version swap:
- docker-compose postgres image parameterized: postgres:${POSTGRES_VERSION:-14}.
- CI runs a PostgreSQL matrix [12.8, 16, 17] (fail-fast off) so migrations + tests
  are proven on newer majors while the current 12.8 stays green.

Deliberately NOT changed:
- Existing V001..V017 migrations are untouched -- editing an applied migration breaks
  Flyway checksum validation. The legacy `EXECUTE PROCEDURE` trigger syntax still works
  through PG17; modernizing to `EXECUTE FUNCTION` needs a new migration (follow-up).
- Runtime driver quill-jasync 3.18.0 / jasync 1.2.3 kept as-is; the CI matrix will
  surface whether a jasync 2.x + matching Quill bump is actually needed for newer PG,
  rather than bumping the fragile pairing blind.

Not built in-session (sbt unavailable); run the matrix to validate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
LocalActionTest references Default.GUID0 but never imported
net.psforever.objects.Default (unlike its sibling tests). This is a
pre-existing gap in the base psforever#1379 commit, surfaced when the test sources
were compiled during the modernization verification -- not a modernization
change. Adds the import so the test suite compiles.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
….5.18

The JDK 21 move (Phase 2) was verified for compile and test but not for
`server/run`, which attaches the Kanela instrumentation agent. On JDK 21 the
old agent aborted startup:

  Unable to start Kanela Agent ... Failed to resolve the class file version
  of the current VM: Unknown Java version: 0

kamon-bundle 2.6.1 / sbt-kanela-runner 2.0.14 ship Kanela 1.0.11, whose
ByteBuddy predates JDK 21. Bumping to kamon-bundle/apm-reporter 2.7.7 and
sbt-kanela-runner 2.1.1 pulls Kanela 1.0.18 (JDK 21-aware) with a matching
instrumentation API.

kamon 2.7.7's dependency tree then resolves slf4j-api 2.0.x, which ignores
logback-classic 1.2.13's 1.7-era binding (NOPLoggerFactory -> ClassCastException
at Server.scala when casting to logback's LoggerContext). Bumping logback-classic
to 1.5.18 provides the slf4j 2.0 service provider.

Verified: full test suite unchanged (192 pass / 38 fail scalatest + 3 specs2 --
the pre-existing psforever#1379 baseline, no new regressions) and the server boots on
JDK 21 (Pekko Slf4jLogger started, Kamon module registry active).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The JDK 21 sbt builder tag used by the Dockerfile and docker-compose
(eclipse-temurin-jammy-21.0.5_11_1.10.7_2.13.16) is not published on Docker Hub,
so image pulls failed. No sbtscala/scala-sbt tag bundles sbt 1.10.7 + Scala
2.13.16; the launcher fetches those from build.properties/build.sbt regardless,
so the image only needs to provide JDK 21 + an sbt launcher. Switch both to the
newest available JDK 21 tag (21.0.2_13_1.10.4_2.13.15).

Also update docker-compose:
- loginserver was still on the pre-modernization JDK 8 / Scala 2.13.10 image,
  which cannot build the Pekko/JDK 21 code; move it to the JDK 21 image.
- default the db service to postgres:18 (newest major) via POSTGRES_VERSION.

Verified end to end: the full stack boots, the server auto-migrates a fresh
PostgreSQL 18.4 database (Flyway applies all 17 migrations; Flyway 10.20.1 emits
only a soft "newer than tested" warning), and the login server listens.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant