Release History & What’s New

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against Scala 3.10 (3.10.0, released 2026-10-12). Every dated fact comes from the official release notes or the official download pages, checked on 2026-10-15, and the "probe" programs were compiled and run on 2026-10-16 with Scala CLI 1.17.1 (the runner that ships with Scala 3.10), mostly on JDK 26 and, for the oldest compilers, on JDK 17. Older and newer compilers (3.1.3, 3.2.2, 3.3.8, 3.4.3, 3.5.2, 3.6.2, 3.7.4, 3.8.4, 3.9.0 and 2.13.18) were fetched from Maven Central by that same runner. Where a statement comes from a release note only, the page says so.

The page is the timeline of the section. It answers four questions: what shipped in which release, which releases to avoid, how the Scala 2 forms map to Scala 3, and how each outdated form in the three reference books maps to the current language. The concepts themselves are taught on the pages linked from each row; this page does not repeat them. The release lines and how to pick one are introduced in Getting Started & Versions, the compatibility rules are in Versions, Compatibility & JDKs, upgrading a project step by step is in Upgrading Between Scala 3 Versions, and every removal with its error message is in Incompatibilities & Dropped Features.

The timeline at a glance

Timeline of Scala releases from 2.13.0 in 2019 to 3.10.0 in 2026 on three lanes for Scala 2.13, Scala Next and LTS

Three things to read from the figure. First, Scala 3 was published in May 2021 and arrived in minor releases roughly every six to nine months. Second, the 3.3 LTS was the only Long Term Support line from 3.3.0 (May 2023) until 3.9.0 (September 2026), so Scala Next releases 3.4 to 3.8 were all published while 3.3 was the LTS. Third, the JDK floor moved to 17 with 3.8, so the lines before it (3.3 LTS and 3.4 to 3.7) are the only ones that still run on JDK 8 and 11.

The dates are the dates of the official release announcements. Maven Central usually has the artifacts a few days earlier (for example the 3.10.0 artifacts are dated 5 October 2026 and the announcement 12 October 2026), so a date read from a Maven repository can differ from the one on this page.

The latest patch release of each line on the official all-versions page at the check date were:

Line Latest release Remarks

Scala 3.10 (Scala Next)

3.10.0, 12 October 2026

first Scala Next after 3.9 LTS; 3.10.1 and 3.10.2 are planned, 3.11 is expected around March or April 2027

Scala 3.9 (LTS)

3.9.0, 3 September 2026

guaranteed for at least three years; advised for publishing libraries

Scala 3.8

3.8.4, 5 June 2026

3.8.0 and 3.8.1 had regressions, see 3.8.0 (January 2026)

Scala 3.7

3.7.4

last line whose artifacts Scala 2.13 can still read through its TASTy reader

Scala 3.3 (previous LTS)

3.3.8, 10 June 2026

maintained for one year after 3.9.0; the only Scala 3 line that still runs on JDK 8 and 11

Scala 2.13

2.13.18, 24 November 2025

VirtusLab took over its maintenance on 7 October 2026

Scala 2.12

2.12.21, 11 December 2025

maintenance line, mainly for sbt 1.x plugins

The version-baseline table

One row per release that changed the language or the tooling in a way the section teaches. The first column links to the official notes of the release.

Release Announced What changed

2.13.0

11 June 2019

The collections redesign Scala 3 still uses: LazyList, IterableOnce, BuildFrom, with CanBuildFrom and Traversable deprecated; literal types; Using; pipe and tap

3.0.0

14 May 2021

Scala 3: optional braces and quiet control syntax, given/using, extension methods, enums, opaque types, union and intersection types, match types, type lambdas, inline, quotes and splices, TASTy, trait parameters, export, top-level definitions, @main; dropped features

3.1.0

21 October 2021

Experimental saferExceptions; -Wconf and @nowarn; minor versions are backward but not forward binary compatible

3.2.0

5 September 2022

Native code coverage, stricter pattern bindings, non-local returns deprecated, exports in extension clauses

3.3.0 (LTS)

30 May 2023

First LTS; fewer braces stable (SIP-44); boundary and break; -Wunused and -Wvalue-discard; new lazy val implementation

3.4.0

29 February 2024

Deprecation warnings for legacy syntax; case needed for refutable for patterns; SIP-53, SIP-54 and SIP-56 stable (3.3.2 was abandoned)

3.5.0

22 August 2024

scala is Scala CLI (SIP-46); binary literals (SIP-42); best-effort compilation (experimental); named tuples and new given syntax introduced as experimental

3.6.2

10 December 2024

New given and context-bound syntax (SIP-64); clause interleaving (SIP-47); 3.6.0 and 3.6.1 must not be used

3.7.0

7 May 2025

Named tuples stable (SIP-58); @publicInBinary (SIP-52); -preview introduced; given prioritization picks the most general instance; warning for explicit arguments passed to implicit parameters without using

3.8.0

22 January 2026 (use 3.8.2 or later)

JDK 17 required; standard library compiled by Scala 3; REPL a separate artifact with pprint; better fors (SIP-62) and runtimeChecked (SIP-57) stable; into in preview; -opt arrives in 3.8.3

3.9.0 (LTS)

3 September 2026

New LTS; into stable (SIP-71); Scaladoc snippets with expected // error and // warn; Scala CLI drops Ammonite and adds JUnit 5 and sbt 2 export; deprecation of inaccessible companion givens

3.10.0

12 October 2026

Strict-equality pattern matching stable (SIP-67); -preview features; breaking changes (A with B is an error, inaccessible companion givens are not found, Rich* wrappers became extension methods, this is illegal at top level); Java record patterns; \s escape; REPL improvements; -Xmax-fuel; no longer cross-compilable with Scala 2

The sections below go through the rows in order. Each one says what the release is remembered for, shows the changes that could be demonstrated with a compiler on this machine and says what comes only from the release note.

Before Scala 3: 2.13.0 (11 June 2019)

Scala 2.13.0 is the release whose library Scala 3 still uses. Its announcement lists four areas: the collections were overhauled for simplicity, performance and safety (the centerpiece), the standard library gained a faster and more robust Future plus new classes and methods, the language gained literal types, partial unification and by-name implicits, and the compiler became 5 to 10 percent faster with deterministic output.

What this means for a Scala 3 programmer today is that most of the standard library has not changed since 2.13: the collection hierarchy, LazyList, scala.util.Using and scala.util.chaining all come from this release. Three things were deprecated rather than removed in 2.13.0, and the deprecated forms still compile in Scala 3.10. A program that uses them, compiled with -deprecation:

import scala.collection.generic.CanBuildFrom

object Old {
  def main(args: Array[String]): Unit = {
    val t: Traversable[Int] = List(1, 2)
    val s = Stream.from(1).take(2).toList
    println((t, s))
  }
}

On Scala 2.13.18 the compiler answers:

old.scala:5: warning: type Traversable in package scala is deprecated (since 2.13.0): Use Iterable instead of Traversable
    val t: Traversable[Int] = List(1, 2)
           ^
old.scala:6: warning: value Stream in package scala is deprecated (since 2.13.0): Use LazyList instead of Stream
    val s = Stream.from(1).take(2).toList
            ^
2 warnings
(List(1, 2),List(1, 2))

and Scala 3.10.0 says the same, in its own format, and also runs the program:

-- Deprecation Warning: old.scala:5:11
5 |    val t: Traversable[Int] = List(1, 2)
  |           ^^^^^^^^^^^
  |type Traversable in package scala is deprecated since 2.13.0: Use Iterable instead of Traversable
-- Deprecation Warning: old.scala:6:12
6 |    val s = Stream.from(1).take(2).toList
  |            ^^^^^^
  |value Stream in package scala is deprecated since 2.13.0: Use LazyList instead of Stream
2 warnings found
(List(1, 2),List(1, 2))

scala.collection.generic.CanBuildFrom behaves the same way: it is a deprecated type whose message points to scala.collection.BuildFrom ("Use scala.collection.BuildFrom instead"), and the apply method of BuildFrom is deprecated in favour of newBuilder. So the accurate statement for the books is that CanBuildFrom and Traversable are deprecated shims since 2.13.0, not removed, and new code should not use them.

The replacements, all run on 2.13.18 in one program, are LazyList, BuildFrom, Using, pipe, tap and literal types:

import scala.util.Using
import scala.util.chaining._
import scala.collection.BuildFrom

object S213 {
  val one: 1 = 1                                   // literal type (2.13.0)

  def firstTwo[A, C](xs: C)(implicit ev: C => Iterable[A], bf: BuildFrom[C, A, C]): C =
    bf.fromSpecific(xs)(ev(xs).take(2))             // BuildFrom replaces CanBuildFrom

  def main(args: Array[String]): Unit = {
    val naturals = LazyList.from(1)                 // LazyList replaces Stream
    println(naturals.take(5).toList)
    println(firstTwo(Vector(1, 2, 3)))
    println(5.pipe(_ + 1).tap(x => println(s"tapped $x")))
    val n = Using(new java.io.StringReader("abc"))(_.read()).get
    println(n)
    println(one)
  }
}
List(1, 2, 3, 4, 5)
Vector(1, 2)
tapped 6
6
97
1

The collections themselves are taught in Collections Overview and Collections Architecture & Custom Collections, and LazyList in Views, Iterators & LazyList.

3.0.0 (14 May 2021)

Scala 3 was announced after eight years of work, 28,000 commits and more than a hundred contributors. The announcement sends readers to the Scala 3 documentation, the "New in Scala 3" article, the Book and the migration guide, and says that one thing changed completely compared to Scala 2: macros. It also fixes the release policy: patch releases are forward and backward compatible with each other, minor releases add language features and library APIs, and "libraries built with Scala 3.0.0 will keep working with Scala 3.x.y". At the time of that post Scaladex listed 308 early adopter libraries on Scala 3 against 2597 on Scala 2.13; the 3.9 announcement says that roughly 56 percent of Scala 3 libraries now publish with 3.3 LTS.

What 3.0 contains is most of what the rest of this section teaches. Where each feature is taught:

Feature Page

Optional braces, quiet control syntax (if ... then, while ... do), end markers

Optional Braces & Indentation, Conditionals & Loops

given, using, summon, extension methods

Givens & Using Clauses, Extension Methods

enum, ADTs and GADTs

Enums, Algebraic Data Types & GADTs

Union and intersection types, opaque types, match types, type lambdas, dependent and polymorphic function types

Union & Intersection Types, Opaque Types & Value Classes, Match Types, Type Lambdas & Higher Kinds

Trait parameters, export, top-level definitions, @main

Traits & Mixins, Composition, Export & Self Types, Packages, Imports & Top-Level Definitions, Hello World & @main Methods

inline, quotes and splices, TASTy

Metaprogramming Overview, Inline

Dropped features (procedure syntax, do-while, symbol literals, existential types, early initializers, DelayedInit and others)

Incompatibilities & Dropped Features

Three of the dropped features are easy to demonstrate, because they are syntax errors on every Scala 3 compiler. This file uses the procedure syntax, do-while and a symbol literal; each is compiled on its own so that one error does not hide another. On 3.3.8 and on 3.10.0 the procedure syntax gives the same message:

def proc() { println("x") }

@main def procDemo(): Unit = proc()
-- [E040] Syntax Error: proc.scala:1:11
1 |def proc() { println("x") }
  |           ^
  |           '=' expected, but '{' found
1 error found
Compilation failed

The do-while loop fails with a message that does not name the feature (end of toplevel definition expected but 'do' found, then Illegal start of toplevel definition on the next line), which is why the form is worth recognizing, and a symbol literal such as 'sym fails with a message that tells you what to write instead: "use a string literal "sym" or an application Symbol("sym") instead". All three still need rewriting by hand when you port a Scala 2 file; see the incompatibilities page for the rewrite of each.

3.1.0 (21 October 2021)

The 3.1.0 announcement is the first one that explains the compatibility model of the 3.x series, and it explains it by admitting a limitation. Scala 3.1 is backward binary compatible, since dependencies compiled with 3.0 work in 3.1 projects, but it is not forward binary compatible, since dependencies compiled with 3.1 do not work in 3.0 projects. The advice at the time to library maintainers was to test with 3.1.0 and 3.0.2 but to publish with 3.0.2, so that downstream users on either version could use the library. The same rule, unchanged, is why 3.9 LTS is the recommended publishing target today.

Highlights of the release, all from the announcement:

  • import language.experimental.saferExceptions and the throws clause, a way to declare and check which exceptions a function can throw. It is still experimental today.

  • A -Wconf compiler flag to silence warnings, turn them into errors or otherwise configure them, and the @nowarn annotation back from Scala 2.13.

  • Efficient bytecode (lookupswitch) for match over String literals.

  • Simplified Manifest synthesis, for compatibility with Scala 2.

  • Mirror support for hierarchical sealed types, which type class derivation builds on.

  • Scala.js 1.7.0 or later is required by 3.1.

The release also changed what compiler you can run on a current JDK. A 3.1.3 compiler started on JDK 26 stops with an error about the JDK’s own class files; on JDK 17 the same compiler works. This is the output on JDK 26, for any program:

error while loading ElementType,
class file /modules/java.base/java/lang/annotation/ElementType.class is broken, reading aborted with class java.lang.RuntimeException
bad constant pool index: 0 at pos: 1220
error while loading AccessFlag,
class file /modules/java.base/java/lang/reflect/AccessFlag.class is broken, reading aborted with class java.lang.RuntimeException
bad constant pool index: 0 at pos: 6381
2 errors found
Compilation failed

The remedy is to run the old compiler on an older JDK (17 in the probes of this page). The JDK floor moves in the other direction in 3.8 (see below). Old compilers on new JDKs are covered in Versions, Compatibility & JDKs.

3.2.0 (5 September 2022)

The headline of 3.2.0 is coverage: the compiler can now generate Scoverage data itself with the -coverage-out flag, without a compiler plugin, so it works for Scala 3 code (later releases added // $COVERAGE-OFF$ markers in 3.3.8 and 3.8.3). The release also allowed exports in extension clauses (export richInt.* as a way to add extension methods in bulk), code completion for refined Selectable types, better Mirror synthesis for local and inner classes and tuples up to 22 elements, given bindings in for generators, easier construction of classes whose first parameter list is a using clause, and the -Vprofile flags that report code complexity. The announcement also lists new experimental APIs (MainAnnotation, newMain, TupledFunction) and a list of APIs that were stabilised.

Two changes of 3.2 matter for ordinary code because they are warnings that every project meets:

  • Pattern bindings are type checked more strictly. The reference page says that from 3.2 on, type checking rules are tightened so that warnings are reported at compile time. A val whose pattern is more specific than the right-hand side warns that the match may fail at run time.

  • Non-local returns are deprecated. A return inside a lambda is compiled by throwing and catching an exception, and 3.2.0 started to warn about it.

Both are demonstrated here. This program has a pattern binding that the compiler cannot prove irrefutable:

@main def pbind(): Unit =
  val elems = List(1, 2, 3)
  val first :: rest = elems
  println((first, rest))

On 3.1.3 (JDK 17) it compiles silently and prints (1,List(2, 3)). On 3.3.8 it warns, and the text of the advice changes again in 3.8, where : @unchecked is replaced by .runtimeChecked (SIP-57, see 3.8):

-- Warning: pbind.scala:3:22
3 |  val first :: rest = elems
  |                      ^^^^^
  |pattern's type ::[Int] is more specialized than the right hand side expression's type List[Int]
  |
  |If the narrowing is intentional, this can be communicated by adding `: @unchecked` after the expression,
  |which may result in a MatchError at runtime.
  |This patch can be rewritten automatically under -rewrite -source 3.2-migration.
1 warning found
(1,List(2, 3))

The same program on 3.8.4 and on 3.10.0 prints the identical warning with "adding .runtimeChecked after the expression" and "-rewrite -source 3.8-migration". The non-local return is this program:

def firstNegative(xs: List[Int]): Int =
  xs.foreach { x => if x < 0 then return x }
  0

@main def nlr(): Unit = println(firstNegative(List(3, -2, 5)))

It prints -2 on every version, and compiles without a warning on 3.1.3. On 3.2.2 the warning points to the old API:

-- Warning: nlr.scala:2:34
2 |  xs.foreach { x => if x < 0 then return x }
  |                                  ^^^^^^^^
  |Non local returns are no longer supported; use scala.util.control.NonLocalReturns instead
1 warning found
-2

and from 3.3 on the advice is the one that the next section introduces ("use boundary and boundary.break in scala.util instead"). Both warnings are still warnings, not errors, on 3.10.0. See Exceptions, boundary & Early Exit and Extractors & Pattern Bindings.

3.3.0 LTS (30 May 2023)

3.3.0 is the first Long Term Support release and defines the model the section relies on. Scala 3.3.x is maintained for at least three years; new minor releases (3.4, 3.5 and so on) are called Scala Next, "equivalent to Java’s feature releases"; bug fixes and usability improvements from Scala Next are back-ported as 3.3.x patches, and apart from those forward and backward compatible changes the LTS is feature frozen. The LTS model does not change the compatibility guarantees: projects built with any future release can depend on a library compiled with 3.3.x.

What it added to the language and library:

  • Linting. -Wunused:imports, privates, locals, explicits, implicits, params and all, and -Wvalue-discard, which catches the map where flatMap was meant.

  • Fewer braces. A colon can replace the braces around a method argument (SIP-44, experimental in 3.2.0 and promoted to stable). The probe is a map whose lambda is on the next line:

@main def braces(): Unit =
  val ys = List(1, 2, 3).map: x =>
    x * 2
  println(ys)

On 3.2.2 it does not compile (not a legal formal parameter for a function literal, then Not found: x on the next line); on 3.3.8 it prints List(2, 4, 6).

  • boundary and break, the type-safe replacement for non-local returns. A break can only be called where a matching Label is in scope, so the escape is checked by the compiler.

import scala.util.boundary, boundary.break

@main def bnd(): Unit =
  val r = boundary:
    for n <- List(1, 2, -3, 4) do
      if n < 0 then break(n)
    0
  println(r)

On 3.3.8 it prints -3. On 3.2.2 the import itself fails:

-- [E008] Not Found Error: bmin.scala:1:18
1 |import scala.util.boundary
  |                  ^^^^^^^^
  |                  value boundary is not a member of util
  • A new default lazy val implementation, with better performance and safe under parallel access. It is the implementation whose use of sun.misc.Unsafe later forced the changes of 3.8; the 3.9 notes explain that code compiled with Scala 3.0 to 3.7 still goes through the legacy scala.runtime.LazyVals API, and that Scala CLI’s experimental Sloth support rewrites such dependencies.

  • A known incompatibility: inline parameters are no longer assumed to be stable paths, so inline def method(inline param: Outer): param.Inner is an error and the fix is to drop the inline modifier on that parameter.

3.3 is also the line with the longest tail. The final LTS patch at the check date is 3.3.8 (10 June 2026), which adds JDK 26 support, the // $COVERAGE-OFF$ markers, an @uncheckedOverride annotation and a -Yfuture-lazy-vals option that produces lazy vals compatible with JDK 9 and later. The notes of 3.3.8 say that the next 3.3 release will include the updates introduced up to the last 3.9.x before 3.10. See Optional Braces & Indentation and Compiler Options, Linting & Errors.

3.4.0 (29 February 2024) and the missing 3.3.2

Scala 3.4.0 and 3.3.3 were released the same day, and the announcement is the clearest statement of the split. 3.3.3 "focused solely on bug fixes and usability improvements" and keeps full binary and source compatibility (the team checked that every one of more than a thousand projects that worked with 3.3.1 still worked with 3.3.3); 3.4.0 adds features and deprecates legacy mechanisms. 3.3.2 does not exist on the all-versions page: a bug in the TASTy reader made it emit TASTy files incompatible with 3.3.0 and 3.3.1, so the release was abandoned and users were told to bump to 3.3.3.

The changes of 3.4 are three groups:

  • Stabilised SIPs: SIP-54 (multi-source extension overloads: extension methods with the same name imported from different sources), SIP-53 (a more expressive type-variable syntax in quote patterns, so case '[ (t, t, t) ] matches a three-element tuple) and SIP-56 (a proper specification for match types, which means some previously accepted match types are now illegal).

  • Type inference: foldRight((Nil, Nil))(...) now infers (List[T], List[T]), polymorphic lambdas may omit parameter types, and the compiler no longer synthesizes given definitions that loop. Polymorphic lambdas compile to JVM lambdas.

  • Deprecations. This is the release that made legacy syntax warn: _ as a type wildcard, private[this], var x = _, with as a type operator, xs: _* varargs and a trailing _ to force eta expansion, and refutable patterns in a for generator need an explicit case. The 3.9 migration section gives the rewrite: -rewrite -source:3.4-migration.

The with type operator is the clearest example, because it is one of the few deprecations that was later promoted to an error. This file uses it for an intersection type:

trait Named { def name: String }
trait Aged { def age: Int }

def describe(x: Named with Aged): String = s"${x.name} (${x.age})"

@main def withty(): Unit =
  val p = new Named with Aged { val name = "Ana"; val age = 41 }
  println(describe(p))

On 3.3.8 it compiles without a message and prints Ana (41). On 3.4.3, and unchanged on 3.9.0, it prints a warning and still runs:

-- [E003] Syntax Warning: withty.scala:4:22
4 |def describe(x: Named with Aged): String = s"${x.name} (${x.age})"
  |                      ^^^^
  |with as a type operator has been deprecated; use & instead
  |This construct can be rewritten automatically under -rewrite -source 3.4-migration.
  |
1 warning found
Ana (41)

On 3.10.0 the same text is reported as an error and compilation fails:

-- [E003] Syntax Error: withty.scala:4:22
4 |def describe(x: Named with Aged): String = s"${x.name} (${x.age})"
  |                      ^^^^
  |with as a type operator has been deprecated; use & instead
  |This construct can be rewritten automatically under -rewrite -source 3.4-migration.
  |
1 error found
Compilation failed

Note that only Named with Aged as a type is affected: the new Named with Aged { ... } expression is the ordinary mixin syntax and was not flagged by any of the three compilers. The progression warning in 3.4, still a warning in 3.9, error in 3.10 is the timeline that Incompatibilities & Dropped Features and Upgrading Between Scala 3 Versions repeat for the other features.

Other items of the 3.4 notes, not run here: the improved message for an incompatible TASTy version, which now says which Scala version produced the file and which to use, a similar message for broken class files that points at the JDK compatibility of your Scala version, compilation progress in IDEs, exporting diagnostics (including unused warnings) to SemanticDB, the ported JVM backend parallelization, and the experimental @publicInBinary annotation that SIP-52 stabilised in 3.7.

3.5.0 (22 August 2024)

3.5.0 made Scala CLI part of the Scala distribution. The scala command installed by a package manager became the Scala runner implemented by Scala CLI (SIP-46): it compiles, runs, tests, packages and publishes single-module projects, with //> using directives and toolkits built in. On the Scala 3.10.0 distribution used for this page, the command reports:

$ scala --version
Scala code runner version: 1.17.1
Scala version (default): 3.10.0

This is the biggest shift for the first chapter of any Scala book: the scala program that older books describe runs a class from a classpath (scala -classpath ...), while today it runs source files. See Scala CLI. Coursier 2.1.10 or later is needed to install it.

Other changes in 3.5:

  • Binary integer literals (SIP-42): 0b1000_0010 is 130. The probe is one line, println(0b1000_0010): on 3.4.3 it does not compile (',' or ')' expected, but identifier found, with the caret under b1000_0010) and on 3.5.2 it prints 130.

  • Best-effort compilation (experimental): the compiler writes BETASTy files for code that does not compile, so Metals can keep offering IDE features while you type; enabled in Metals by -Dmetals.enable-best-effort=true.

  • Pipelined builds, enabled in sbt with ThisBuild / usePipelining := true, for faster multi-module compilation.

  • var in type refinements: type A = { var number: Int } is legal.

  • Named tuples (SIP-58) arrived as an experimental feature behind import scala.language.experimental.namedTuples; they became stable in 3.7. This probe is the experimental form:

import scala.language.experimental.namedTuples

@main def ntuple(): Unit =
  val p = (x = 1, y = 2)
  println(p.x + p.y)

On 3.4.3 the import itself is reported (value namedTuples is not a member of object language.experimental) and so are Not found: x and value x is not a member of (Unit, Unit). On 3.5.2 and on 3.7.4 the program prints 3.

  • New givens and context bounds syntax (SIP-64, experimental in 3.5, stable in 3.6.2) and a new modularity proposal (-language:experimental.modularity with -source:future, still experimental; it adds the Self type member and is).

  • An announcement of the given prioritization change, introduced as warnings in 3.6 and in force from 3.7.

3.6.2 (10 December 2024) and the 3.6.0 accident

3.6.0 and 3.6.1 should never be used. While 3.6.0-RC1 was being released an accident published artifacts versioned as 3.6.0 to the immutable Maven repository, so 3.6.1 was released as a hotfix to stop automatic tooling from using the broken version, and 3.6.2 is "effectively 3.6.0 for all intents and purposes". The all-versions page lists 3.6.2 as the first 3.6 release, and a project that must pin 3.6 pins 3.6.2 or later (3.6.4 exists).

The 3.6 line is the one that changed how a type class instance is written:

  • SIP-64, the new syntax for context bounds and givens. given Order[Int]: replaces given Order[Int] with, a conditional given is written given [T: Order] => Order[List[T]]: or given (config: Config) => Factory = ..., context bounds can be named and aggregated (T: {Order as o, Show}), and abstract context bounds on type members use compiletime.deferred. The probe is the simplest form:

trait Show[T]:
  def show(t: T): String

given Show[Int]:
  def show(t: Int): String = s"int:$t"

@main def given36(): Unit = println(summon[Show[Int]].show(7))

On 3.5.2 it fails (an identifier expected, but 'def' found and Declaration of given instance Show not allowed here: only classes can have declared but undefined members); on 3.6.2 and on 3.10.0 it prints int:7. The old given Show[Int] with form still compiles. See Givens & Using Clauses and Context Bounds & Deferred Givens.

  • SIP-47, clause interleaving: type and term parameter clauses can be interleaved (def getOrElse(k: Key)[V >: k.Value](default: V): V), which helps path-dependent APIs. See Currying & Parameter Lists.

  • SIP-56 amendment: match type extractors follow aliases and singleton types.

  • Experimental in 3.6.2: SIP-62 (better fors, aliases at the start of a for) and SIP-57 (runtimeChecked), both stable in 3.8.

  • Context bounds now desugar to using parameters instead of implicit ones, which can change implicit resolution and affects macros and compiler plugins that inspect argument lists by flag; Java-defined annotations with more than one parameter require named arguments.

3.7.0 (7 May 2025)

3.7.0 stabilised two SIPs, introduced the idea of a preview feature and changed given resolution.

SIP-58, named tuples, became stable: they give names to tuple elements for construction, destructuring and pattern matching, they allow matching a subset of case-class fields by name, and Selectable gained a Fields type member that can be a named tuple so that structural types can be computed without macros. The program of 3.5 above, without the import, is the stable form:

@main def ntuple(): Unit =
  val p = (x = 1, y = 2)
  println(p.x + p.y)

It fails on 3.5.2 (Not found: x, Not found: y, value x is not a member of (Unit, Unit)) and prints 3 on 3.7.4 and on 3.10.0. See Tuples & Named Tuples.

SIP-52, binary APIs. @publicInBinary is stable. It lets an inline method access a non-public member without the compiler generating an accessor that is not subject to binary compatibility, and it allows an inline method to call a private constructor. -WunstableInlineAccessors (available since 3.4.0) reports the accessors that are generated.

Preview features. 3.7 introduced a stage between experimental and stable: "fully implemented and SIP-approved, but potentially subject to small refinements before becoming stable". Preview features are guaranteed to be standardised in a future minor release, may change in small and possibly binary incompatible ways, and are discouraged in published libraries; the -preview compiler option enables all of them at once. The first one was SIP-62, better fors. Without the option, 3.7.4 rejects it, and with the option it works:

@main def bf(): Unit =
  val r = for
    a = 1
    b <- Some(2)
  yield a + b
  println(r)
### scala 3.7.4 bf.scala
-- [E040] Syntax Error: bf.scala:3:6
3 |    a = 1
  |      ^
  |      '<-' expected, but '=' found
-- [E007] Type Mismatch Error: bf.scala:3:9
3 |    a = 1
  |         ^
  |Found:    (??? : => Nothing)
  |Required: ?{ flatMap: ? }
  ...
2 errors found
Compilation failed
### scala 3.7.4 bf.scala -preview
Some(3)
### scala 3.8.4 bf.scala
Some(3)

(The ... stands for two lines of the compiler’s note about implicit conversions.) The -preview flag is the one that Scala 3.10 uses for its three preview SIPs, and the page on Language Evolution: SIPs, Preview & Experimental explains the whole experimental, preview, stable path.

Experimental in 3.7: SIP-61 (@unroll) and SIP-68 (referenceable package objects). Both became preview features in 3.10.

The given prioritization change. Announced in 3.5 and warned about in 3.6, the new rule applies from 3.7: given search now picks the most general instance that satisfies the request instead of the most specific one. The probe uses a covariant type class and asks for Pick[Animal] when both Pick[Animal] and Pick[Dog] are available:

trait Pick[+T]:
  def name: String

class Animal
class Dog extends Animal

given forAnimal: Pick[Animal] with
  def name = "Pick[Animal]"
given forDog: Pick[Dog] with
  def name = "Pick[Dog]"

def which[T](using p: Pick[T]): String = p.name

@main def prio(): Unit =
  println(which[Animal])
### scala 3.6.2 prio.scala
-- Warning: prio.scala:15:23
15 |  println(which[Animal])
   |                       ^
   |           Given search preference for Pick[Animal] between alternatives
   |             forDog.type
   |           and
   |             forAnimal.type
   |           will change.
   |           Current choice           : the first alternative
   |           New choice from Scala 3.7: the second alternative
1 warning found
Pick[Dog]
### scala 3.7.4 prio.scala
Pick[Animal]
### scala 3.7.4 prio.scala -source:3.5
Pick[Dog]
### scala 3.7.4 prio.scala -source:3.7-migration
-- [E205] Potential Issue Warning: prio.scala:15:23
15 |  println(which[Animal])
   |          Given search preference for Pick[Animal] between alternatives
   |            forDog.type
   |          and
   |            forAnimal.type
   |          has changed.
   |          Previous choice       : the first alternative
   |          Choice from Scala 3.7 : the second alternative
   |
   |          Suppress this warning by choosing -source 3.5, -source 3.7, or
   |          by using @annotation.nowarn("id=205")
1 warning found
Pick[Animal]

The result changed silently between 3.6.2 and 3.7.4 for this program, which is why -source:3.7-migration exists: it keeps the new rule but prints the warning above wherever the result differs, and -source:3.5 keeps the old rule.

Explicit arguments for implicit parameters. From 3.7 the compiler warns when an argument is passed to an implicit parameter list without using:

def scale(x: Int)(implicit factor: Int): Int = x * factor

@main def explicit2(): Unit =
  println(scale(10)(3))

3.6.2 prints 30; 3.7.4 prints 30 after the warning Implicit parameters should be provided with a `using clause., which `-rewrite -source:3.7-migration fixes.

Other items from the release notes, not run here: the expression compiler behind the Metals and IntelliJ debug consoles moved into the compiler repository, the presentation compiler shows the expected type of a type hole on hover, the experimental subset of the Quotes API gained apply methods for import selectors and summonIgnoring, and the standard library moved to 2.13.16, where .tail and .init on the empty string throw instead of returning it.

Sources: Scala 3.7.0 released!, Scala 3.9 LTS released! (the section "Coming from Scala 3.3 LTS")

3.8.0 (January 2026)

3.8 is the release that modernises the foundations. The announcement is dated 22 January 2026 and carries a warning that is part of the history: a runtime regression was detected after 3.8.0 was published (JVM linkage errors at runtime that could affect Scala 3 users who execute Scala 2.13 libraries and uses of specialized members of the standard library), so "please use Scala 3.8.1 instead"; two further bugs in 3.8.0 and 3.8.1 (incorrect behaviour of some for comprehensions and a compile failure with some Java standard library calls) were announced as fixed in 3.8.2, released on 24 February 2026. That is why this section tells you to use 3.8.2 or later; the last 3.8 release is 3.8.4 (5 June 2026), which includes fixes from the security audit of the Scala codebase (hardened TASTy parsing, a Scaladoc XSS fix).

What 3.8 changed:

  • JDK 17 is required to compile and run. The reason given is JDK 26 and later, which restrict the sun.misc.Unsafe functionality that the old lazy-vals implementation used; the lazy vals now use VarHandle. 3.3 LTS continues to produce JDK 8 bytecode, and the notes recommend 3.3.8 and its -Yfuture-lazy-vals flag for libraries that must support an older JVM.

  • The standard library is compiled with Scala 3. It is the same API as 2.13, and the release is binary compatible, but the context bounds in library classes now desugar to given instead of implicit, which makes explicit arguments need using. The probe is Array.empty(ClassTag.Int):

import scala.reflect.ClassTag

@main def ctag(): Unit =
  val xs = Array.empty(ClassTag.Int)
  println(xs.length)
### scala 3.7.4 ctag.scala
-- Warning: ctag.scala:4:32
4 |  val xs = Array.empty(ClassTag.Int)
  |                       ^^^^^^^^^^^^
  |Implicit parameters should be provided with a `using` clause.
  |This code can be rewritten automatically under -rewrite -source 3.7-migration.
  |To disable the warning, please use the following option:
  |  "-Wconf:msg=Implicit parameters should be provided with a `using` clause:s"
1 warning found
0
### scala 3.8.4 ctag.scala
-- [E172] Type Error: ctag.scala:4:22
4 |  val xs = Array.empty(ClassTag.Int)
  |                      ^
  |                      No ClassTag available for T
  |
  |                      where:    T is a type variable
1 error found
Compilation failed

The fix is Array.empty(using ClassTag.Int) or, better, Array.empty[Int]. The migration is the reason that the notes recommend running 3.7.4 with -source:3.7-migration -rewrite before the bump. This is also the boundary at which Scala 2.13’s -Ytasty-reader stops being able to read Scala 3 libraries (it works up to 3.7), and at which scala.reflect.runtime.universe can fail when initialised against the Scala 3 standard library, the best-known case being Apache Spark; both are covered in Upgrading Between Scala 3 Versions. * App is deprecated. trait App has been deprecated since 3.8.0; on 3.10.0 and on 3.9.0 object Main extends App: with -deprecation prints trait App in package scala is deprecated since 3.8.0: Support for trait App is deprecated in Scala 3 and the program still prints its output. Use @main, see Hello World & @main Methods. * The REPL is a separate artifact (scala3-repl) rendered with fansi and pprint, so long values no longer dump as one unreadable line. * SIP-62, better fors, is stable. Aliases may start a for, and a final map is omitted when it would be the identity (see the probe in 3.7). 3.8.2 added a warning for a for with several val bindings and an overloaded map, because the result type can change: the 3.8.2 notes show a Map iterated with two aliases that yields List43,29), (43,30), (43,31 on 3.7 and Map(43 -> 31) on 3.8. * SIP-57, runtimeChecked, is stable. It replaces the : @unchecked ascription:

@main def rc(): Unit =
  def port(name: String): Option[Int] = if name == "http" then Some(80) else None
  val Some(p) = port("http").runtimeChecked
  println(p)

On 3.7.4 the compiler reports method runtimeChecked is marked @experimental and on 3.8.4 the program prints 80.

  • into in preview (SIP-71, see 3.9). On 3.8.4 the compiler stops with type into is marked @preview and Preview definition may only be used when compiling with the -preview compiler flag unless -preview is passed.

  • Experimental additions: SIP-67 (strict equality pattern matching), SIP-70 (flexible varargs, multiSpreads), SIP-75 (single-line lambdas after :, relaxedLambdaSyntax), match expressions with sub-cases, and, from 3.8.3, a safe mode for capability-safe code. 3.8 also opened a feature freeze for 3.9: "any codebase using Scala 3.8 should not require any additional changes to use Scala 3.9 LTS".

  • 3.8.3 (31 March 2026) added the optional Scala 2 bytecode optimizer (-opt, -opt-inline) and local coverage exclusions with // $COVERAGE-OFF$. 3.8.4 added :help for every compiler setting (for example -Wunused:help).

  • Tooling floors named in the notes: sbt 1.11.5 minimum and 1.12.0 recommended, Scala CLI 1.9.0 minimum and 1.11.0 recommended, Mill 1.0.5 minimum, scalafmt 3.10.1 minimum, Metals 1.6.4 recommended.

3.9.0 LTS (3 September 2026)

3.9.0 opens the new LTS. It "succeeds Scala 3.3 LTS as the recommended baseline for library authors and conservative production users", it is guaranteed to be maintained for at least three years, and 3.3 LTS stays actively maintained for one year after 3.9.0 (3.3.9 is planned within a month of the announcement). Because the feature freeze held, 3.9 has the same feature set as 3.8 apart from what was promoted. Library authors should read one sentence of the announcement as a rule: artifacts built with 3.9 cannot be consumed by Scala 3.3 projects, so moving from 3.3 LTS to 3.9 LTS is a minor-version publishing decision for the libraries you maintain. The notes give the Open Community Build numbers behind the migration: about 1,780 of nearly 2,000 projects build on 3.9 with no or minimal changes.

  • SIP-71, into, is stable. It marks selected parameters or types as accepting implicit conversions without an import scala.language.implicitConversions at the call site. The probe (3.8 and 3.9 notes) is:

import scala.Conversion.into

def concat[A](xs: List[A], ys: into[IterableOnce[A]]): List[A] = xs ++ ys

given Conversion[Array[Int], IterableOnce[Int]] = _.iterator

@main def intoDemo(): Unit =
  println(concat(List(1, 2), Array(3, 4)))

3.7.4: type into is marked @experimental. 3.8.4 without -preview: type into is marked @preview. 3.8.4 with -preview and 3.9.0 without any flag: List(1, 2, 3, 4). See Conversions, into & Implicit Resolution.

  • Documentation snippets can declare the errors they expect. Scaladoc compiles the snippets it finds in documentation, and 3.9 lets a snippet mark the lines that must fail with // error and the warnings with // warn; every diagnostic must be accounted for. See Scaladoc.

  • Scala CLI updates: the REPL can start JShell (scala repl --jshell), tests can use JUnit 5, export can target sbt 2, WebAssembly is supported experimentally, Ammonite support is dropped, and experimental built-in support for Sloth rewrites dependencies compiled with the old lazy val bytecode.

  • Scala.js 1.22.0 is bundled, and its WebAssembly backend is stable (it targets a JavaScript host such as Node.js, Deno, Bun or a browser). The Scala.js version is fixed for the whole 3.9 LTS line because the backend is part of the compiler.

  • Inaccessible companion implicits are deprecated. A given defined in a private companion object is no longer an eligible instance at the call site, because the specification has always required that an eligible implicit be accessible. 3.9 reports every such resolution under -deprecation, and 3.10 stops finding them. The probe:

object api:
  class Config
  private object Config:
    given Config = new Config

  def describe(using Config): String = "configured"

@main def companion(): Unit =
  println(api.describe)
### scala 3.9.0 companion.scala
there was 1 deprecation warning; re-run with -deprecation for details
1 warning found
configured
### scala 3.9.0 companion.scala -deprecation
-- Deprecation Warning: companion.scala:9:22
9 |  println(api.describe)
  |                      ^
  |Usage of implicit given instance given_Config defined in object Config, which is not accessible here. In Scala 3.10, this implicit will no longer be found.
1 warning found
configured
### scala 3.10.0 companion.scala
-- [E172] Type Error: companion.scala:9:22
9 |  println(api.describe)
  |                      ^
  |No given instance of type api.Config was found for parameter x$1 of method describe in object api
1 error found
Compilation failed
  • The 3.3 to 3.9 summary. The announcement summarises each minor between 3.3 and 3.9 (breaking changes, new stable features, notable changes) and a recommended upgrade order: build tool first, then JDK 17 or later on the current Scala version, then Scala.js, then Scala, then libraries. The list of -source:3.x-migration rewrites to apply in order is the core of Upgrading Between Scala 3 Versions.

On 3.9, the older preview and experimental gates matter in a way that is easy to forget when reading 3.10 material: the three features that are preview features in 3.10 were still experimental in 3.9. @unroll on 3.9.0 is rejected with class unroll is marked @experimental: under review as part of SIP-61 and needs -experimental (or an experimental import), after which the same program prints Hello, Ana!; SIP-75 (xs.map: v => v + 1) fails on 3.9.0 with not a legal formal parameter for a function literal, and referenceable package objects fail too, as the next section shows.

3.10.0 (12 October 2026)

3.10.0 is the first release of the Scala Next series that follows 3.9 LTS, and the first release that removes legacy features. Binary compatibility is preserved (libraries compiled with earlier Scala 3 versions keep working, including those that used features now removed), but source that still uses the removed features must be updated. Because cross-compilation between Scala 2 and 3.10 is no longer supported, the notes expect cross-compiled libraries to stay on 3.9 LTS indefinitely; check a 3.10 artifact with MiMa 1.2.1 or later, which adds 3.9 and 3.10 support.

Stable: SIP-67. Under strictEquality, x == y still needs a CanEqual, but match on an enum or case object no longer does. The probe:

import scala.language.strictEquality

enum Foo:
  case Bar
  case Baz(f: Int => Int)

def eval(x: Foo): Int = x match
  case Foo.Bar    => 0
  case Foo.Baz(f) => f(0)

@main def strictDemo(): Unit = println(eval(Foo.Baz(_ + 1)))

On 3.7.4 and on 3.9.0 it fails with Values of types Foo and Foo cannot be compared with == or != (under the case Foo.Bar pattern); on 3.10.0 it prints 1. See Pattern Matching and Multiversal Equality.

Preview features (-preview). SIP-61 (@unroll), SIP-75 (relaxed lambda syntax) and SIP-68 (referenceable package objects) moved from experimental to preview, and the experimental language imports that previously gated them are deprecated. SIP-74 (partial function literals with parentheses) is a fourth preview feature on 3.10.0: the SIP page says it is a preview feature in 3.10.0 and will be on by default in the next minor release, although the SIP list calls it "shipped" and the reference’s preview index does not list it yet. Three probes, each compiled without and with the flag:

@main def relaxed(): Unit =
  val xs = Option(2)
  val ys = xs.map: v => v + 1
  println(ys)
### scala 3.10.0 relaxed.scala
-- Error: relaxed.scala:3:11
3 |  val ys = xs.map: v => v + 1
  |           ^^^^^^^^^
  |           not a legal formal parameter for a function literal
-- [E006] Not Found Error: relaxed.scala:3:24
3 |  val ys = xs.map: v => v + 1
  |                        ^
  |                        Not found: v
2 errors found
Compilation failed
### scala 3.10.0 relaxed.scala -preview
Some(3)

The same probe with package lib and package object json (a val api = lib.json that treats the package object as a value) fails on 3.9.0 with package lib.json is not a value, on 3.10.0 with Preview package object values may only be used when compiling with the -preview compiler flag, and prints 42 on 3.10.0 with -preview. The SIP-74 probe xs.collect(case (a, b) if b > 2 => a) fails on 3.9.0 and on 3.10.0 with expression expected but case found and prints List(3) with -preview. @unroll behaves differently: on 3.10.0 the one-parameter probe compiled and printed Hello, Ana! without the flag, which is the same observation recorded in Versions, Compatibility & JDKs; pass -preview anyway.

Breaking changes. The notes list them; the ones demonstrated here:

  • A with B as a type is an error (probe in 3.4 above); -source:3.4-migration -rewrite rewrites it to A & B, and in typed patterns the intersection must be parenthesised (case x: (A & B) =>).

  • Givens in inaccessible companions are no longer found (probe in 3.9 above).

  • this is illegal in top-level definitions. val self = this at the top of a file prints ok for the rest of the program on 3.9.0 and fails on 3.10.0:

-- Error: toplevelthis.scala:1:11
1 |val self = this
  |           ^^^^
  |           Top-level definitions cannot refer to this
1 error found
  • The primitive Rich* wrappers (runtime.RichInt and the others) are gone: until, to, abs and max are extension methods in the companions of Int, Long and the other primitive types, and a -> b is an inline extension method; Predef.ArrowAssoc is deprecated and no longer an implicit conversion. A DSL that defines -> through an implicit class should change it to an extension method. Macros that inspect the old trees need updating; ZIO 2.1.26 and other macro-heavy libraries published releases for that reason.

  • Java generic signatures use boxed types for primitive type arguments, so MiMa may report IncompatibleSignatureProblem when you republish, and -Xmax-fuel replaces catching StackOverflowError: very deep types now report Recursion limit exceeded (default fuel 550) and may need -Xmax-fuel and -Xss. See Compiler Options, Linting & Errors.

New in the language and library. Java records can be used as extractor patterns, \s is a string escape for a space, and IArray has Factory, IsSeq and BuildFrom instances. The two probes:

### scala 3.9.0 sesc.scala
-- Error: sesc.scala:2:13
2 |  println("a\sb")
  |             ^
  |             invalid escape character
1 error found
### scala 3.10.0 sesc.scala
a b
a\sb

(The second line is println(raw"a\sb"), where raw keeps the two characters.) For the record pattern, a Point.java holding public record Point(int x, int y) {}, compiled with javac and put on the class path, and the program Point(3, 4) match case Point(x, y) => println(s"point $x,$y") fail on 3.9.0 (Point cannot be used as an extractor in a pattern because it lacks an unapply or unapplySeq method with the appropriate signature) and print point 3,4 on 3.10.0. See Using Java from Scala.

Tools in 3.10. The distribution bundles Scala CLI 1.17.1 (Java instance main methods without a class on JDK 25 and later, //> using resource, and the lts version tag resolving to the 3.9 series), the REPL accepts //> using dep, //> using toolkit and //> using jar directives and gained :save and :replay, and Scala is now also published as .deb and .rpm packages. Scala CLI, the REPL and the packages are described in Scala CLI and Installing Scala; the REPL additions come from the release notes only and were not run here.

What is next. The notes say that 3.10.1 has its release branch cut and 3.10.2 will follow, and that development then moves to 3.11, expected around March or April 2027. 3.9 LTS remains the recommended publishing baseline.

Tool and ecosystem milestones

The tools move on their own schedule. Those that the section teaches, with the dates that the official sources give:

Tool Date Milestone

Scala CLI as scala

22 August 2024 (3.5.0)

The default runner of the distribution; 1.17.1 is bundled with 3.10 and 1.18.0 was the latest at the check date

Scala Toolkit

checked 2026-10-15

Version 0.9.2 (//> using toolkit 0.9.2); the official tutorial pages still show older versions such as 0.7.0, sttp 4.0.0-RC1 and uPickle 4.1.0, so copy the version from the Toolkit page, not from a tutorial

sbt 2

29 June 2026

Announced as stable after five years of development; build definitions are written in Scala 3 (3.8.4) and need JDK 17; the announcement names sbt.version = 2.0.1, and the 2.0.x line has had patch releases since; sbt 1.x remains the line for Scala 2 builds

Scala.js 1.22.0

3.9.0 (3 September 2026)

Bundled with the 3.9 LTS line; stable WebAssembly backend; Scala Native 0.5.12 was the latest at the check date

Scala 2 maintenance

7 October 2026

VirtusLab assumed responsibility for maintaining Scala 2.12 and 2.13, with the Scala Center and the Scala Core Team; the next releases planned are 2.12.22 and 2.13.19

Akka and Pekko

Akka 2.7 (2022)

Akka moved to the Business Source License; the Apache-licensed fork is Apache Pekko. Lagom is no longer maintained (security patches ended on 1 July 2024)

Scala 3.8 tool floors

22 January 2026

sbt 1.11.5 (1.12.0 recommended), Scala CLI 1.9.0 (1.11.0 recommended), Mill 1.0.5, scalafmt 3.10.1, Metals 1.6.4

The libraries and frameworks of the ecosystem, with their current versions, are collected in Ecosystem, and building with sbt, Mill and Scala CLI is described in sbt & Build Tools. The Toolkit modules are taught in Testing with MUnit, Files & Processes with OS-Lib, JSON with uPickle and HTTP Client with sttp.

Scala 2 to Scala 3

The table maps what a Scala 2.13 programmer wrote to what Scala 3 offers, with the release in which the Scala 3 form arrived or changed. The old form usually still compiles for migration, unless the row says it is removed.

Area Scala 2.13 form Scala 3 form Since

Block structure

Braces everywhere; if (c) a else b; while (c) {}

Optional braces and indentation; if c then a else b; while c do; end markers; a colon for the last argument

3.0, colon 3.3

Contextual parameters

implicit parameters, implicitly

using clauses and summon

3.0

Type class instances

implicit val, implicit object, implicit def

given instances; given Show[Int]: and given [T: Show] => Show[List[T]]: since SIP-64

3.0, new syntax 3.6

Extending types

implicit class

extension methods

3.0

Conversions

implicit def conversion

given Conversion[A, B]; into to opt in per parameter

3.0, into stable 3.9

View bounds

A <% B

Removed; use a context bound or a using conversion

3.0

Enumerations

sealed trait plus case object`s, `Enumeration

enum, including enums with parameters and ADT-style cases

3.0

Compound and union types

A with B; Either[A, B] for alternatives

The intersection type A & B and the union type written with a vertical bar

3.0, with error 3.10

Wildcards

List[_]

List[?]

3.0, _ warns 3.4

Type lambdas

({ type L[x] = F[x] })#L, kind-projector

[x] =>> F[x]

3.0

Entry point

object Main extends App, def main(args: Array[String])

@main def run(): Unit; App without DelayedInit semantics

3.0

Package objects

package object util

Top-level definitions; package objects still compile and become referenceable values with -preview

3.0, preview 3.10

Value classes

class Meters(val v: Double) extends AnyVal

Opaque types (value classes still work)

3.0

Macros

def macros, quasiquotes, scala.reflect

inline, quotes and splices, quotes.reflect; Scala 2 macros are not expanded by Scala 3

3.0

Exceptional exit

return from a lambda

boundary and break

deprecated 3.2, replacement 3.3

Pattern bindings

val x :: xs = list without warning

Warning unless irrefutable; .runtimeChecked instead of : @unchecked

3.2, runtimeChecked 3.8

for comprehensions

Aliases only after a generator

Aliases may start a for; fewer map calls

preview 3.7, stable 3.8

Tuples

(Int, Int) with _1, _2

Named tuples (x: Int, y: Int)

experimental 3.5, stable 3.7

Binary literals

None

0b1000_0010

3.5

Running code

scala -classpath to run a class; scalac to compile

scala run File.scala with Scala CLI

3.5

Standard library

The 2.13 library, compiled by Scala 2

The same API, compiled by Scala 3 since 3.8; inline and extension methods used inside it since 3.10

3.8, 3.10

Reading each other’s libraries

Scala 2.13 reads Scala 3 libraries with -Ytasty-reader

Scala 3 reads 2.13 libraries; 2.13 can read Scala 3 libraries only up to 3.7

3.8 boundary

Cross-building

Cross-compiling a source tree for 2.13 and Scala 3

Possible up to 3.9 LTS; not supported on 3.10

3.10

Each row is taught on its own page: start from Migrating from Scala 2 and Scala 2 & Scala 3 Compatibility.

Legacy to modern: where the books are outdated

The three books consulted for this section are Programming Scala 3rd edition (PS3), Learn Scala Programming (LSP) and Professional Scala (PRO). Each was written before some of the changes above. The table collects every outdated form with the current state on 3.10, the release where it changed, and the page that teaches the current form. Chapter references are the issue’s own mapping; the table contains no book text, only the forms and their replacements.

Book statement or form Current Scala 3.10 Since Page

implicit val/def instances, implicit parameters, implicitly (LSP ch. 4, 7-10; PRO ch. 6)

given, using and summon. Scala 2 implicits still compile, for migration.

3.0

Contextual Abstractions Overview

implicit class for extension methods (LSP ch. 4, 9, 10; PRO ch. 6, 8)

extension methods

3.0

Extension Methods

implicit def conversions (LSP ch. 4; PRO ch. 6)

given Conversion[A, B] plus import scala.language.implicitConversions. into became stable in 3.9.

3.0, 3.9

Conversions, into & Implicit Resolution

View bounds A <% B (LSP ch. 4; PRO ch. 5-6)

Removed. Use a using conversion or a type class.

3.0

Type Classes

PS3’s given ... with forms and anonymous context bounds (ch. 5, 6, 16, 18)

The 3.6 given syntax (SIP-64): given Ord[Int]:, given [T: Ord] => Ord[List[T]]:, named and aggregate context bounds, deferred givens. The old form still compiles.

3.6

Context Bounds & Deferred Givens

Given resolution examples (PS3 ch. 5; LSP ch. 4)

3.7 prefers the most general given (probe above). 3.10 no longer finds givens in inaccessible companions.

3.7, 3.10

Conversions, into & Implicit Resolution

PS3 ch. 16 view-bound example with (given view: A => B)

Pre-3.0 Dotty syntax, an error in the book. It is (using ...), and a using function is not applied as a conversion.

3.0

Givens & Using Clauses

PS3 predicts legacy syntax warns or fails "in 3.1": _ wildcards, : _*, alphanumeric infix, old implicits under * imports

Deprecation warnings arrived in 3.4 (_ wildcard, xs: _*, with as a type operator, private[this], var x = _, trailing f _). A with B is an error in 3.10. Infix and Matchable checks are still -source:future only.

3.4, 3.10

Upgrading Between Scala 3 Versions

-language:experimental.fewerBraces (PS3 ch. 2)

Standard since 3.3

3.3

Optional Braces & Indentation

-Ysafe-init (PS3 ch. 12)

-Wsafe-init

3.3

Compiler Options, Linting & Errors

scala -classpath as the runner; cs install scala scalac scaladoc (PS3 ch. 1, 22)

Since 3.5 scala is Scala CLI (scala run, scala repl, scala test, //> using directives). Ammonite was dropped from Scala CLI in 3.9.

3.5, 3.9

Scala CLI

JDK 8+ with 11 or 15 recommended (PS3 ch. 1); JDK 8 (PRO); JDK 8-11 (LSP)

Scala 3.8 and later need JDK 17 or later. 3.3 LTS still runs on JDK 8.

3.8

Versions, Compatibility & JDKs

"The Scala 3 standard library is the 2.13 library" (PS3 ch. 1, 14)

Same API, but since 3.8 it is compiled by Scala 3. The REPL is a separate artifact.

3.8

Getting Started & Versions

Polymorphic function types cannot take context bounds or using (PS3 ch. 17)

They can in recent 3.x

3.x

Dependent & Polymorphic Function Types

Compound types A with B (LSP ch. 2; PRO ch. 5)

A & B. with is an error in 3.10.

3.0, 3.10

Union & Intersection Types

Existential types T forSome { ... } (LSP ch. 2; PRO ch. 5)

Dropped. Use ? wildcards or type members.

3.0

Incompatibilities & Dropped Features

List[_], Seq[_] (LSP, PRO)

List[?]

3.0, warns 3.4

Generics & Bounds

Type lambdas ({ type L[x] = F[x] })#L with kind-projector 0.9 (LSP ch. 2, 8, 10)

[x] =>> F[x]. -Xkind-projector is for migration only.

3.0

Type Lambdas & Higher Kinds

Type projections T#A on abstract T (LSP ch. 2; PRO ch. 5)

Allowed only on concrete prefixes

3.0

Incompatibilities & Dropped Features

Procedure syntax def f() { ... }; do { ... } while (c); symbol literals 'sym (PRO ch. 5, 6; PS3 ch. 2)

All dropped. Use def f(): Unit = ..., the loop-and-a-half while, and Symbol("sym").

3.0

Incompatibilities & Dropped Features

object Main extends App (PRO ch. 7)

DelayedInit is dropped; App still works without delayed semantics. Prefer @main.

3.0

Hello World & @main Methods

Traversable, CanBuildFrom, Stream, strict mapValues, JavaConverters (PRO ch. 4, 6; LSP ch. 8)

2.13 hierarchy (IterableOnce/Iterable), BuildFrom/Factory, LazyList, .view.mapValues, scala.jdk.CollectionConverters. The old names are deprecated shims since 2.13.0, not removed.

2.13.0

Collections Architecture & Custom Collections

“Option` extends `Traversable” (PRO ch. 5)

Wrong even in 2.12, where an implicit conversion applied. Option is an IterableOnce since 2.13.

2.13.0

Option & Null Safety

mutable.MultiMap (LSP ch. 1); 2.13 milestone API names (LSP ch. 1)

MultiDict in scala-collection-contrib. Check every name against the final 2.13 API.

2.13.0

Sets & Maps

Scala 2 def macros, quasiquotes, scala.reflect runtime reflection, TypeTag, Shapeless derivation (PRO ch. 8)

inline, scala.compiletime, quotes and splices, quotes.reflect, Mirror-based derives. Scala 2 macros cannot be expanded by Scala 3.

3.0

Metaprogramming Overview

Structural types work out of the box (PRO ch. 5)

Reflective access needs import scala.reflect.Selectable.reflectiveSelectable

3.0

Structural Types, Selectable & Dynamic

Akka as the default actor/streams toolkit (LSP ch. 11-14; PRO ch. 7; PS3 ch. 19)

Akka has been under the Business Source License since 2.7 (2022). The Apache-licensed fork is Apache Pekko. Cats Effect, ZIO and Ox are common alternatives. Ecosystem page only.

2022

Ecosystem

ActorMaterializer, classic remoting, PersistentActor, bindAndHandle, Blaze, Cats Effect 2 (LSP ch. 11-14)

Out of scope; ecosystem page only. Named here so readers of the book know these APIs changed.

n/a

Ecosystem

Lagom and ConductR (LSP ch. 15)

End of life (Lagom 2024-07-01). Not documented.

2024

Ecosystem

sbt 1.1-1.2, ENSIME, Ammonite, Mill under lihaoyi/mill (all three books)

sbt 1.x and sbt 2.0 (announced 2026-06-29); Metals or IntelliJ; Scala CLI; Mill under com-lihaoyi

2026

sbt & Build Tools

ScalaTest 3.0 FunSuite/WordSpec imports (LSP, PRO)

ScalaTest 3.2 funsuite.AnyFunSuite and similar; MUnit is the Toolkit’s test framework

n/a

Testing with MUnit

"Dotty" as the future compiler (PRO, LSP)

Scala 3.0 shipped on 2021-05-14

3.0

3.0.0 (14 May 2021)

SPP (Scala Platform Process) (PRO ch. 8)

Defunct. Use SIPs and the Scala Toolkit.

n/a

Language Evolution: SIPs, Preview & Experimental

The rows show a pattern worth knowing before reading a book: the further a row is toward the top, the more likely the old form still compiles and only the style has changed; the further toward the bottom, the more likely the library or the tool itself has gone. The same statements are collected from the angle of the dropped features in Incompatibilities & Dropped Features.

Experimental, preview and stable at the check date

The table repeats, as a release-history view, the stage of the features that moved during 3.5 to 3.10. The definitions of the three stages and the full lists are in Language Evolution: SIPs, Preview & Experimental.

Feature Introduced Stable On 3.10.0

Named tuples (SIP-58)

3.5 experimental

3.7

stable

Given and context-bound syntax (SIP-64)

3.5 experimental

3.6

stable

Better fors (SIP-62)

3.6.2 experimental, 3.7 preview

3.8

stable

runtimeChecked (SIP-57)

3.6.2 experimental

3.8

stable

into (SIP-71)

3.8 preview

3.9

stable

Strict equality pattern matching (SIP-67)

3.8 experimental

3.10

stable

@unroll (SIP-61)

3.7 experimental

not yet

preview

Referenceable package objects (SIP-68)

3.7 experimental

not yet

preview

Relaxed lambda syntax (SIP-75)

3.8 experimental

not yet

preview

Partial function literals with parentheses (SIP-74)

3.10 preview

expected in the next minor release

preview

Flexible varargs (SIP-70)

3.8 experimental

not yet

experimental

Dedented string literals (SIP-72)

3.10 experimental

not yet

experimental

Which version to use

A short decision list, taken from the release notes and the compatibility page:

  • Publishing a library: 3.9 LTS (3.9.0 or the latest 3.9 patch). Stay on 3.3 LTS only while your users are stuck on it, and never compile with a Scala Next release unless you rely on a feature that exists only there.

  • Writing an application: 3.10 (Scala Next), or 3.9 LTS for the most conservative path. Any Scala 3 minor can be mixed with libraries built by an older Scala 3 minor, but not the other way round.

  • On JDK 8 or 11: 3.3.8 LTS or 3.7.4. From 3.8 on, JDK 17 or later is required.

  • With Scala 2.13 consumers of your Scala 3 library: stay on 3.7 or earlier, or publish a 2.13 artifact, because the TASTy reader of 2.13 cannot read libraries built by 3.8 or later.

  • Never: 3.6.0 and 3.6.1, which should never be used, and 3.8.0 and 3.8.1, which had regressions; 3.3.2 does not exist.

Pitfalls

  • Reading a date from Maven Central instead of the announcement. Artifacts are published days before the news item (3.9.0 on 26 August 2026 against the announcement of 3 September 2026), so a table built from repository timestamps disagrees with the official dates.

  • Running an old compiler on a new JDK. The 3.1.3 compiler on JDK 26 stops with "class file …​ is broken" for the JDK’s own classes; use the JDK that matches the compiler (17 for 3.1 to 3.7 in the probes here), and use JDK 17 or later from 3.8.

  • Treating "deprecated" as "removed". Traversable, Stream and CanBuildFrom still compile on 3.10.0, with warnings; A with B and a given in an inaccessible companion do not. The warning release and the error release are different, and the table of the 3.9.0 notes is the way to see which is which.

  • Treating a preview feature like a stable one. -preview features can change incompatibly before they are stabilised, and are discouraged in published libraries; a library that needs one should say so in its documentation.

  • Taking the experimental import for the preview flag. On 3.10 the old experimental imports for the three promoted features are deprecated and point to -preview; the -experimental flag is a different, much wider switch that also opts everything marked @experimental into your build.

  • Upgrading libraries before the compiler. A newer library may need a newer compiler; the recommended order is build tool, JDK, Scala.js, Scala, then libraries.

  • Believing a tutorial about the Toolkit. The official tutorial pages show Toolkit 0.7.0, sttp 4.0.0-RC1 and uPickle 4.1.0 while 0.9.2 is current. Likewise, a book that starts programs with App predates its deprecation in 3.8.0.

What was run and what was not

Run on 2026-10-16 with Scala CLI 1.17.1 (scala run --server=false -S <version> File.scala, with the compiler fetched from Maven Central): the probes for binary literals (3.4.3 and 3.5.2), named tuples (3.4.3, 3.5.2, 3.7.4 and 3.10.0), better fors (3.6.4, 3.7.4 with and without -preview, 3.8.4), boundary (3.2.2 and 3.3.8), non-local returns (3.1.3, 3.2.2, 3.3.8 and 3.10.0), pattern bindings (3.1.3, 3.3.8, 3.8.4 and 3.10.0), fewer braces (3.2.2 and 3.3.8), A with B (3.3.8, 3.4.3, 3.9.0 and 3.10.0), the SIP-64 given syntax (3.5.2, 3.6.2 and 3.10.0), given prioritization (3.6.2, 3.7.4, 3.10.0 and with -source:3.5 and -source:3.7-migration), explicit arguments to implicit parameters (3.6.2 and 3.7.4), Array.empty(ClassTag.Int) (3.7.4 and 3.8.4), runtimeChecked (3.7.4 and 3.8.4), into (3.7.4, 3.8.4, 3.9.0), inaccessible companion givens (3.9.0 with and without -deprecation, 3.10.0), this at top level (3.9.0 and 3.10.0), the \s escape (3.9.0 and 3.10.0), SIP-67 (3.7.4, 3.9.0 and 3.10.0), @unroll (3.9.0 with and without -experimental, 3.10.0 with and without -preview), SIP-75, SIP-68 and SIP-74 (3.9.0 and 3.10.0 with and without -preview), Java record patterns (3.9.0 and 3.10.0 with the class compiled by javac), the Scala 2.13.18 program and the deprecations of Traversable, Stream and CanBuildFrom (2.13.18 and 3.10.0), the three dropped syntaxes (3.3.8 and 3.10.0), and scala --version. The 3.1.3 and 3.2.2 probes ran on JDK 17; the others on JDK 26.

Not run here, taken from the release notes and the official pages: the JDK 17 floor of 3.8 on a JDK older than 17 (no such JDK is installed on this machine), the REPL features of 3.8 to 3.10 (:dep, //> using in the REPL, :save, :replay), the Scala CLI features (JShell, JUnit 5, sbt 2 export, Sloth, WebAssembly), Scala.js 1.22 and its Wasm backend, native coverage and // $COVERAGE-OFF$, best-effort compilation, pipelined builds, the 3.8.3 optimizer, -Xmax-fuel, the Scala 2.13 TASTy-reader boundary, MiMa, sbt 2, the .deb and .rpm packages, and the statements about Akka, Pekko and Lagom, which are cited from their own pages.