Scala Style Guide

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.0 (released 2026-10-12) with Scala CLI 1.17.1 on JDK 26. Every example whose output is shown was run on that setup on 2026-10-16; the few exceptions are labelled "not run here". Scalafmt 3.11.5 and Scalafix 0.14.9 are third-party tools (they live in the Scala Meta and Scala Center projects, not in the compiler) and were fetched from Maven Central through Scala CLI. The advice about style applies equally to the 3.9 LTS and the 3.3 LTS lines; where a rule depends on a compiler version, the version is named.

The official Scala Style Guide at https://docs.scala-lang.org/style/ is a short document of nine pages. It was written in the Scala 2 era, and its code is almost entirely braced. It says itself that it is "a list of rules to be broken", and in Scala 3 a number of its rules are now enforced by the compiler, changed by the language, or obsolete. This page goes through every page of the guide in order, shows each rule as a before and after pair, and says what Scala 3 changes. The Java counterpart of this kind of document is covered on Java Lexical Structure & Style and the Kotlin one on Kotlin Lexical Structure & Style.

The guide at a glance

Guide page What it asks for Status on Scala 3.10

Indentation

Two spaces, no tabs; wrap long lines by two more spaces; one argument per line for five or more arguments.

Unchanged. Significant indentation (see Optional Braces & Indentation) makes the two-space rule structural, and leading infix operators remove the trailing-operator trap.

Naming Conventions

Camel case; upper camel for types and constants; lower camel for methods, values and annotations; A, B for type parameters.

Unchanged, and the compiler now enforces the zero-parameter-list distinction (parentheses versus none).

Types

Infer locals, annotate public members; value: Type spacing; omit parentheses in Int => String.

Unchanged. x: _* is replaced by x*; structural types need Selectable.

Nested Blocks

Opening brace on the declaration line; parentheses Lisp-style.

Mostly obsolete: with braceless syntax most blocks have no brace, and a brace on its own line is a syntax error.

Declarations

Constructor parameters one per line with trailing commas; member ordering; modifier order; no procedure syntax.

Procedure syntax is now a hard error; extends A, B replaces extends A with B; trailing commas are fine.

Control Structures

Space after the keyword; braces only when needed; for { … } for several generators.

Replaced by the quiet syntax if … then, for … do and for … yield with indented generators.

Method Invocation

No space around the dot; infix only for symbolic and well-known methods; no a.+(b).

Enforced: an alphanumeric method used as an infix operator warns unless it is declared infix.

Files

One class (and its companion) per file named after it; package.scala for package objects.

Top-level definitions replace package objects; the file name decides the synthetic wrapper object.

Scaladoc

Summary sentence first; @param, @tparam, @return; document everything public.

Unchanged; Markdown is accepted next to the wiki syntax (see Scaladoc).

Indentation

The guide asks for two spaces per level and no tabs. This is the one rule everybody follows, and in Scala 3 it is also the rule the significant-indentation syntax is designed around: indentation of any width works, but a body that is indented by a different amount than its siblings is a warning or an error. See Optional Braces & Indentation for the exact rules.

Line wrapping

When one expression is too long, the guide prefers to split it into named intermediate values. If that is impractical, each continuation line is indented two spaces from the first. It warns that Scala "infers a semicolon" at the end of a line that was meant to wrap, so a wrapped line must either end inside an open parenthesis or end with an infix operator.

That warning is a Scala 2 problem. Scala 3 allows a line that starts with an infix operator followed by a space to continue the previous expression (the "leading infix operator" rule), so the three spellings below all mean the same thing. Here is the same code on Scala 3.10 and on Scala 2.13.

//> using scala 3.10.0
@main def wrap2(): Unit =
  val flat = 1 + 2 + 3
  + 4 + 5
  println(flat)
  val ok = 1 + 2 + 3
    + 4 + 5
  println(ok)
  val par = (1 + 2 + 3
    + 4 + 5)
  println(par)
15
15
15

Even the unindented form (flat) is continued. The same construct on Scala 2.13.18 is the trap the guide describes: the second line is a separate statement.

//> using scala 2.13.18
object Wrap3 extends App {
  val bad = 1 + 2 + 3
    + 4 + 5
  println(bad)
}
warning: a pure expression does nothing in statement position; multiline expressions may require enclosing parentheses
    + 4 + 5
        ^
1 warning
6

Prefer the guide’s rule anyway (end the line with the operator, or wrap the expression in parentheses): it reads the same on both language versions, and a formatter will not move the operator for you. Note that App is deprecated since Scala 3.8; the Scala 2 program above only demonstrates the old behaviour. See Hello World & @main Methods.

Methods with numerous arguments

For five or more arguments the guide puts each argument on its own line, indented two spaces, so that renaming the method never forces a re-alignment. If the argument list would have to be indented by more than 50 spaces to line up, the call moves to the next line instead. Scala 3 (and 2.12.2 and later) accepts a trailing comma after the last argument when the closing parenthesis is on its own line, which makes adding an argument a one-line diff. Trailing commas are also what the guide recommends for class declarations (see below).

//> using scala 3.10.0
def route(
  name: String,
  lengthKm: Double,
  elevationGainM: Int,
  difficulty: Int,
  trailhead: String,
): String = s"$name $lengthKm km, +$elevationGainM m, level $difficulty from $trailhead"

@main def args(): Unit =
  val someVeryLongFieldName = "Ridge Loop"
  val andAnotherVeryLongFieldName = 12.5
  val myLongFieldNameWithNoRealPoint =
    route(
      someVeryLongFieldName,
      andAnotherVeryLongFieldName,
      640,
      3,
      "North Gate",
    )
  println(myLongFieldNameWithNoRealPoint)
  println(route(name = "Short", lengthKm = 2.0, elevationGainM = 10, difficulty = 1, trailhead = "Car park"))
Ridge Loop 12.5 km, +640 m, level 3 from North Gate
Short 2.0 km, +10 m, level 1 from Car park

The last line also shows the alternative the guide ends with: avoid methods with many parameters, or give the call named arguments so that the call site documents itself. The names of parameters are part of the public API of a method because callers may use them.

Naming conventions

Scala uses camel case: UpperCamelCase for types, lowerCamelCase for the rest. Acronyms count as ordinary words (xHtml, maxId, not xHTML or maxID). Underscores in names are allowed by the compiler but discouraged, because _ has special meanings in Scala syntax; the exceptions below are the setter name_= and the occasional private backing field _name.

//> using scala 3.10.0
import scala.annotation.StaticAnnotation

class audited extends StaticAnnotation

class HtmlParser(val xhtml: String, val maxId: Int)

object Limits:
  val MaxRetries = 3
  val DefaultName = "trail"

object inc:
  def apply(x: Int): Int = x + 1

class Registry[Key, Value]:
  private val entries = scala.collection.mutable.Map.empty[Key, Value]
  def get(key: Key): Option[Value] = entries.get(key)
  def put(key: Key, value: Value): Unit = entries(key) = value

def add(a: Int, b: Int) = a + b

@audited
def tracked(): Int = add(Limits.MaxRetries, 4)

@main def naming(): Unit =
  val parser = HtmlParser("<p/>", 7)
  println((parser.xhtml, parser.maxId))
  println(inc(41))
  val registry = Registry[String, Int]()
  registry.put("a", 1)
  println(registry.get("a"))
  println(tracked())
  println(math.Pi)
(<p/>,7)
42
Some(1)
7
3.141592653589793

The sections below take the guide’s headings in order.

Classes, traits and objects

Classes and traits are in upper camel case, like Java classes. Objects are upper camel case too, with one stated exception: an object that mimics a package or a function (object inc above, or object ast that holds a family of case classes) may be lower case. Classes whose shape is dictated by an external format (a JSON schema, a protocol) may follow that format’s names, but only for that purpose.

Packages

Packages follow the Java convention, a reversed domain name in lower case. A file may declare package com.novell.coolness in one line, which puts only coolness members in scope, or chain two package clauses, which also makes members of com.novell visible. The example below uses the chained form in a.scala and imports from another package in main.scala.

//> using scala 3.10.0
package com.novell
package coolness

val Greeting = "hello"
def shout(s: String): String = s.toUpperCase

class Inbox(val owner: String)
object Inbox:
  def apply(owner: String): Inbox = new Inbox(owner)
package com.novell.app

import com.novell.coolness.*
import _root_.scala.math.Pi

@main def packages(): Unit =
  println(shout(Greeting))
  println(Inbox("ada").owner)
  println(Pi)
  println(classOf[Inbox].getName)
  println(Class.forName("com.novell.coolness.a$package").getName)
HELLO
ada
3.141592653589793
com.novell.coolness.Inbox
com.novell.coolness.a$package

_root_ is needed only when another package named net (for example) is in scope and shadows the top-level one; the guide asks you not to use it otherwise, because nested package resolution is what keeps imports short. Also note the last line: the top-level Greeting and shout of a.scala live in a synthetic object named a$package. That is the Scala 3 replacement for the guide’s package object coolness, which still compiles but is the old way. See Packages, Imports & Top-Level Definitions and the Scala 3 Reference: Package objects.

Methods, accessors and mutators

Methods use lower camel case. Scala does not use the Java getX/setX prefix pair. The accessor has the name of the property, a boolean accessor may carry an is prefix when there is no mutator, and a mutator is the property name with _= appended. The compiler makes this more than a convention: if both count and count_= are defined on a type, then c.count = 5 is translated to the setter call.

//> using scala 3.10.0
class Counter:
  private var _count = 0
  def count: Int = _count
  def count_=(n: Int): Unit =
    require(n >= 0, "count must not be negative")
    _count = n
  def isZero: Boolean = _count == 0
  def reset(): Unit = _count = 0

@main def acc(): Unit =
  val c = Counter()
  println(c.count)
  c.count = 5
  println(c.count)
  println(c.isZero)
  c.reset()
  println(c.isZero)
0
5
false
true

This is the guide’s "Hungarian" backing field, _count as a private var and count as the public view; the underscore is a prefix so that you cannot mistake name _ for name_. The guide also mentions the _? suffix convention for booleans from the Lift framework, which is not standard. For properties and bindings the guide points to libraries that expose an immutable reference to a property object, not a pair of methods.

Parentheses

A zero-argument method can be declared with an empty parameter list (def reset(): Unit) or with none (def size: Int). By convention the empty list marks a method with a side effect, and its absence marks an accessor that returns the same answer for the same receiver state. The call site must follow the declaration. Scala 2 only warned when the call site disagreed; Scala 3 enforces it. Here is the guide’s rule turned into compiler messages on 3.10.0.

//> using scala 3.10.0
class Counter:
  def reset(): Unit = ()
  def size: Int = 3

@main def paren(): Unit =
  val c = Counter()
  c.reset
  println(c.size())
-- [E100] Syntax Error: paren.scala:8:4
8 |  c.reset
  |  ^^^^^^^
  |  method reset in class Counter must be called with () argument
  |
  | longer explanation available when compiling with `-explain`
-- [E050] Type Error: paren.scala:9:12
9 |  println(c.size())
  |          ^^^^^^
  |          method size in class Counter does not take parameters
  |
  | longer explanation available when compiling with `-explain`
2 errors found
Compilation failed

Methods defined in Java are exempt, since Java has no such distinction, and they can be called with or without parentheses.

//> using scala 3.10.0
@main def javaParens(): Unit =
  val sb = new java.lang.StringBuilder("abc")
  println(sb.length)
  println(sb.length())
3
3

The guide adds that creating an object is not a side effect, which is why iterator has no parentheses while Iterator#next() has them, and that a method with parameters has no convention either way.

Symbolic method names

The guide’s advice is "avoid". Symbolic names are for two uses only: domain-specific languages (actor ! msg) and logically mathematical operations (a + b, c :: d), and then only for pure operations; >>= for monads is fine, << for writing to a stream is not. If a method needs an explanation, give it a name. In Scala 3, an operator-style method may be given a readable alias with @targetName, and an alphanumeric method may be used as an infix operator only if it is declared infix (see the method invocation section below).

Constants, values and variables

A constant is a final, immutable member of an object or package that is never reassigned; its name is in upper camel case, like Pi in scala.math or MaxRetries in the example above. Other values and variables use lower camel case.

Type parameters

Single-letter type parameters start from A (not Java’s T). A parameter with a specific meaning gets a descriptive upper camel case name (Key, Value, never KEY), and a mnemonic like K, V is fine when the scope is small. Higher-kinded parameters are better named with words too, but F[_] and M[_] for a fundamental concept like a functor or a monad are accepted, particularly when a context bound gives the reader the evidence, as in the example below. The Registry[Key, Value] class above is the descriptive style.

//> using scala 3.10.0
trait Monad[M[_]]:
  def pure[A](a: A): M[A]
  def flatMap[A, B](ma: M[A])(f: A => M[B]): M[B]

given Monad[Option] with
  def pure[A](a: A): Option[A] = Some(a)
  def flatMap[A, B](ma: Option[A])(f: A => Option[B]): Option[B] = ma.flatMap(f)

def doSomething[M[_]: Monad](m: M[Int]): M[Int] =
  val monad = summon[Monad[M]]
  monad.flatMap(m)(i => monad.pure(i + 1))

@main def hk(): Unit = println(doSomething(Option(1)))
Some(2)

Annotations

Annotation classes are in lower camel case (class audited, like scala.annotation.tailrec and targetName in the library), unlike the upper camel case of Java annotations. The convention extends to type aliases of annotations.

Brevity

Local names may be very short (def add(a: Int, b: Int) = a + b), because well-written Scala methods are short. The rule applies to the parameters of simple methods and the locals of simple classes; the public interface needs descriptive names, and parameter names are API.

Types

Inference

The guide says to use inference where possible, put clarity first, and favour explicit types in public APIs. Do not annotate a private field or a local variable whose value shows its type (private val name = "Daniel"), but do annotate every public method: an inferred result type depends on the body, so a change to the body can change the API and break binary compatibility without any warning. Explicit types can also reduce compile time.

The program below shows the guide’s argument with real bytecode: the inferred type of justOne is Circle, not the Shape the author probably meant, and lookup leaks None$ instead of Option.

//> using scala 3.10.0
trait Shape
class Circle extends Shape

object Factory:
  def justOne = Circle()
  def justOneExplicit: Shape = Circle()

object Repo:
  def lookup(key: String) = None
  def lookupTyped(key: String): Option[String] = None

@main def leak(): Unit =
  val names = Seq("justOne", "justOneExplicit")
  classOf[Factory.type].getMethods.filter(m => names.contains(m.getName)).sortBy(_.getName)
    .foreach(m => println(s"${m.getName}: ${m.getReturnType.getSimpleName}"))
  classOf[Repo.type].getMethods.filter(_.getName.startsWith("lookup")).sortBy(_.getName)
    .foreach(m => println(s"${m.getName}: ${m.getReturnType.getSimpleName}"))
justOne: Circle
justOneExplicit: Shape
lookup: None$
lookupTyped: Option

Not every inferred type leaks, but you cannot tell from the signature which ones do. See Type Inference.

Function values

When the expected type of a function value is known, its parameter types are inferred and should be left out: ls.map(str => str.toInt) or ls.map(_.toInt). An implicit conversion on function values would defeat the inference and force explicit parameter types.

Annotations

A type annotation is written value: Type, with the colon attached to the value and a space after it. The reason is operators: value ::: is a legal declaration of a value of type ::, and the prefix style value : Type makes such code hard to read.

Ascription

Type ascription uses the same syntax as annotation, and it is an upcast checked at compile time (Nil: List[String]). The case seen most often is passing a sequence to a variable-arity method, which in Scala 2 is values: _*. In Scala 3 the splice is written values*, and the old form warns:

//> using scala 3.10.0
def total(xs: Int*): Int = xs.sum

@main def tpe(): Unit =
  val values = List(1, 2, 3)
  println(total(values*))
  println(total(values: _*))
  val n = Nil: List[String]
  println(n)
-- Warning: tpe.scala:7:24
7 |  println(total(values: _*))
  |                        ^
  |The syntax `x: _*` is no longer supported for vararg splices; use `x*` instead
  |This construct can be rewritten automatically under -rewrite -source 3.4-migration.
1 warning found
6
6
List()

The warning is on by default on Scala 3.10, and the rewrite is described in the section on enforcing the style.

Function types

Function types are written with a space on each side of the arrow, and with no parentheses around a single parameter type: Int => String, not (Int) => String. The guide’s example makes the point for curried function types: Int => String => Boolean => Double is the right form, with (Int) => (String) => (Boolean) => Double the needlessly noisy one. Both compile and are the same type, as the following example shows.

//> using scala 3.10.0
@main def fnTypes(): Unit =
  def f(g: (Int) => (String) => (Boolean) => Double) = g(1)("a")(true)
  def g(h: Int => String => Boolean => Double) = h(1)("a")(true)
  println(f(i => s => b => 1.5))
  println(g(i => s => b => 2.5))
1.5
2.5

Structural types

A structural type with fewer than 50 characters may stay inline; a longer one is split over several lines and given a type alias. The members of an inline one are separated by a semicolon and a space, with a space after { and before }. The guide adds that structural types use reflection at run time and are slower than nominal types, so prefer nominal ones unless a structural type gives a clear benefit. In Scala 3 this has a compile-time consequence: selecting a member of a structural type needs a Selectable instance, which the compiler tells you about.

//> using scala 3.10.0
type Named = { val name: String }
class Dog(val name: String)
@main def st2(): Unit =
  val n: Named = Dog("rex")
  println(n.name)
-- [E007] Type Mismatch Error: st2.scala:6:10
6 |  println(n.name)
  |          ^
  |          Found:    (n : Named)
  |          Required: Selectable | Dynamic
  |
  |          The following import might fix the problem:
  |
  |            import scala.reflect.Selectable.reflectiveSelectable
  |
  |
  | longer explanation available when compiling with `-explain`
1 error found
Compilation failed

With the import, the guide’s long structural type with an alias and an inline one both compile and run:

//> using scala 3.10.0
import scala.reflect.Selectable.reflectiveSelectable

type FooParam = {
  val baz: List[String => String]
  def bar(a: Int, b: Int): String
}

def foo(a: { val bar: String }) = a.bar
def useFoo(p: FooParam): String = p.bar(1, 2) + p.baz.map(_("x")).mkString

@main def st(): Unit =
  println(foo(new { val bar = "inline" }))
  println(useFoo(new { val baz = List((s: String) => s + "!"); def bar(a: Int, b: Int) = (a + b).toString }))
inline
3x!

Nested blocks

The guide wants an opening brace on the same line as the declaration it belongs to. The compiler’s parser can in principle accept the opening brace on the next line (the "GNU style"), but the semicolon rules make it unpredictable. Scala 3 settles it:

//> using scala 3.10.0
def gnu(x: Int)
{
  println(x)
}
@main def nb(): Unit = gnu(1)
-- [E019] Syntax Error: nb.scala:2:15
2 |def gnu(x: Int)
  |               ^
  |               Missing return type
  |
  | longer explanation available when compiling with `-explain`
1 error found
Compilation failed

Better still, most blocks no longer have braces: with the braceless syntax the indented lines after = or : are the block, and the guide’s "curly braces" rule reduces to "use the brace form only where you chose to keep it". See Optional Braces & Indentation.

For parentheses, the guide says that a parenthesised expression that wraps lines is written "Lisp-style": no space after the opening or before the closing parenthesis, and the closing one on the last line of content. Parentheses also disable semicolon inference, so you can start lines with an operator inside them. A closing parenthesis on its own line is acceptable for that variant.

//> using scala 3.10.0
@main def nb2(): Unit =
  val a, b, c = true
  val r1 = (  a
  || b
  || c
  )
  println(r1)
  val r2 =
    a
      || b
      || c
  println(r2)
  val r3 = (1 + 2 +
    3 * 4)
  println(r3)
true
true
15

The r2 form without parentheses works only because of the leading infix operator rule shown above. Use it only when it reads clearly.

Declarations

Classes

Constructor parameters of a class, object or trait are on one line unless the line passes about 100 characters. Otherwise every parameter is on its own line, with a trailing comma, and the closing parenthesis on its own line. The guide’s rule for the supertypes is the same: one line if it fits, otherwise each on its own line, with a blank line before the body so that the extensions are visually separated from the implementation. Scala 3 offers extends A, B, C as an alternative to extends A with B with C, so the supertypes fit better on a line. The old spelling is still accepted.

//> using scala 3.10.0
import java.time.LocalDate

trait Entity
trait Logging
trait Identifiable

class Person(
  name: String,
  age: Int,
  birthdate: LocalDate,
  astrologicalSign: String,
  shoeSize: Int,
  favoriteColor: String,
) extends Entity, Logging, Identifiable:

  def firstMethod: String = s"$name ($age)"

class Legacy(
  name: String,
) extends Entity
  with Logging {

  def hello = name
}

@main def decl(): Unit =
  println(Person("Ada", 36, LocalDate.of(1815, 12, 10), "Sagittarius", 38, "green").firstMethod)
  println(Legacy("old").hello)
Ada (36)
old

Ordering of class elements

Members are separated by blank lines. The only exception is a run of val and var declarations that are simple (one short line, about twenty characters, no Scaladoc), which may be written without the blank lines. Fields come before methods; the exception is a val or lazy val with a block body that does work "like a method", which may be placed further down. The exception does not apply to var, since scattered mutable fields are hard to follow. A lazy field puts the keyword just before val.

class OrderOfFields:
  val bar = 42
  val baz = "Daniel"
  private lazy val expensive = baz.length * 2

  def twice: Int = bar * 2
  def length: Int = expensive

Methods

A method is declared as def foo(bar: Baz): Bin = expr; default arguments have a space on each side of = (def foo(x: Int = 6, y: Int = 7): Int = x + y). Every public member gets a result type, which the guide describes as "documentation checked by the compiler" and which protects binary compatibility. Private and local methods may omit it.

Procedure syntax. The guide says to avoid def printBar(bar: Baz) { ... } because it is deprecated. In Scala 3 it is not deprecated, it is gone:

//> using scala 3.10.0
def printBar(bar: String) {
  println(bar)
}
@main def proc(): Unit = printBar("x")
-- [E040] Syntax Error: proc.scala:2:26
2 |def printBar(bar: String) {
  |                          ^
  |                          '=' expected, but '{' found
1 error found
Compilation failed

Write : Unit = instead; Scalafix has a rule for rewriting old code (see below).

Modifiers. The guide’s order is: annotations (each on its own line), override, access modifier (protected, private), implicit, final, then def. With implicit parameters replaced by using clauses in Scala 3, the implicit slot mostly goes away (the modifier on a definition is now given or extension).

//> using scala 3.10.0
class Base:
  protected def foo(): Unit = println("base")

class Derived extends Base:
  @deprecated("use run", "1.0")
  override protected final def foo(): Unit = println("derived")
  def run(): Unit = foo()

@main def mods(): Unit = Derived().run()
there were 2 deprecation warnings; re-run with -deprecation for details
1 warning found
derived

Body. A body that is one expression of up to about 30 characters goes on the def line (def add(a: Int, b: Int): Int = a + b). One of up to about 70 goes on the next line, indented two spaces. Anything longer, or anything with mutable state, uses a block. The guide’s block form uses braces; in Scala 3 the block is the indented lines. A method whose body is one match is written with the match on the declaration line, so that the cases are not indented twice:

//> using scala 3.10.0
def sum(ls: List[Int]): Int = ls match
  case hd :: tail => hd + sum(tail)
  case Nil        => 0

@main def sumDemo(): Unit = println(sum(List(1, 2, 3)))
6

Multiple parameter lists. Use them only for a reason, because they make declaration and call sites longer. The guide names three reasons: a fluent API (your own control structure, like unless), implicit parameters (in Scala 3 using clauses), and type inference. The third needs care: the guide says that foldLeft(z: B, op: (B, A) => B) as a single list "won’t work", and in Scala 2 that is true. Scala 3 infers B from the first argument before checking the lambda in the same list, so a single parameter list works as well:

//> using scala 3.10.0
def foldLeft[A, B](ls: List[A])(init: B)(f: (B, A) => B): B = ls.foldLeft(init)(f)
def foldPair[B, A](ls: List[A], init: B, f: (B, A) => B): B = ls.foldLeft(init)(f)

@main def ml(): Unit =
  println(foldLeft(List(1, 2, 3, 4))(0)(_ + _))
  println(List("").foldLeft(0)(_ + _.length))
  println(foldPair(List(1, 2, 3, 4), 0, _ + _))
  println(foldPair[Int, Int](List(1, 2, 3, 4), 0, (b, a) => b + a))
10
0
10
10

Curried signatures are still the idiom in the standard library because the last list can then be passed as a block, and because a method with a function last is easy to pass an indented argument to. A curried control structure written as the guide’s unless is called with a colon instead of braces in Scala 3.10:

//> using scala 3.10.0
@main def unlessDemo(): Unit =
  def unless(exp: Boolean)(code: => Unit): Unit =
    if !exp then code
  val x = 7
  unless(x < 5):
    println("x was not less than five")
  unless(x < 5) {
    println("braces still work")
  }
x was not less than five
braces still work

When a signature with several lists is too long for one line, the guide shows two layouts: each list’s parameters one per line with trailing commas and parentheses on their own lines, or the lists aligned one per line. Scalafmt chooses between them according to configuration.

Higher-order functions. Put the function parameter last, in its own list: def foldLeft[A, B](ls: List[A])(init: B)(f: (B, A) => B): B, so that the call foldLeft(List(1, 2, 3, 4))(0)(_ + _) needs no extra parentheses around the function. This is the inverse of the ML order (function first), and the one Scala library authors follow.

Fields

Fields follow the method rules (annotations, modifier order), and lazy goes right before val: private lazy val foo = bar().

Function values

The guide compares four equivalent spellings of the same function:

//> using scala 3.10.0
@main def fv(): Unit =
  val f1 = ((a: Int, b: Int) => a + b)
  val f2 = (a: Int, b: Int) => a + b
  val f3 = (_: Int) + (_: Int)
  val f4: (Int, Int) => Int = (_ + _)
  val f5: (Int, Int) => Int = _ + _
  val f6 = (a: Int, b: Int) =>
    val sum = a + b
    sum
  val f7 = { (a: Int, b: Int) =>
    val sum = a + b
    sum
  }
  println(List(f1, f2, f3, f4, f5, f6, f7).map(_(1, 2)))
  val ls = List("1", "22")
  println(ls.map(str => str.toInt))
  println(ls.map(_.toInt))
List(3, 3, 3, 3, 3, 3, 3)
List(1, 22)
List(1, 22)

The guide prefers f1 (explicit parameter types) and f4 (an expected type with a placeholder), and calls f3 obtuse. With f4, the parentheses are the guide’s choice for a single-line function; f5 without them is what formatters and most Scala 3 code write. There should be no space between a parenthesis and its contents, and one space inside braces. A function with several expressions goes on several lines, in the f1 shape (braces, parameters and arrow on the opening line). Scala 3 also allows the f6 shape, where the body is simply the indented lines after the arrow; the parameter lists of a lambda can be dropped altogether when the expected type is known.

Control structures

The guide wants a space after the keyword (if (foo) bar else baz, for (i <- 0 to 10), while (true)). The quiet Scala 3 syntax makes the space natural, because the condition is no longer parenthesised: if foo then …, for i <- 0 to 10 do …, while cond do …. The parenthesised form still compiles:

//> using scala 3.10.0
@main def ctl(): Unit =
  val foo = true
  if(foo) println("old style, no space")
  for(i <- 0 to 1) println(i)
  while(false) println("never")
  val news = if foo then "good" else "bad"
  println(news)
old style, no space
0
1
good

A formatter fixes the spacing; the compiler does not complain about it. See Conditionals & Loops.

Curly braces

The guide’s rules for the braced syntax are these. if with an else and single-line branches needs no braces, if without else takes braces even for one line, while always takes braces (it is not functional), for omits them with yield, and case clauses never take braces. With indentation the rules become: use a block (indented lines) whenever a branch has more than one statement, and nothing else. A condition that spans lines wraps in parentheses or then on its own line.

//> using scala 3.10.0
@main def cond(): Unit =
  val foo = true
  if foo then println("foo was true")
  if foo then
    println("two statements")
    println("need an indented block")
  val res = if foo then "bar" else "baz"
  println(res)
  val x = 3
  val label =
    if x < 2 then "small"
    else if x < 5 then "medium"
    else "large"
  println(label)
  val s = "good"
  s match
    case "good" => println("Good news!")
    case "bad"  => println("Bad news!")
    case _      => println("no news")
foo was true
two statements
need an indented block
bar
medium
Good news!

Comprehensions

For a for with several generators and a yield, the guide prefers the braced form with one generator per line over the semicolon form. Without yield (a loop) it prefers the parenthesised form with semicolons over the confusing } {. It also asks you to prefer for over chains of map, flatMap and filter, as the comprehension is easier to read. In Scala 3 the same layout is written with indentation, yield or do aligned with for; all of them mean the same thing:

//> using scala 3.10.0
@main def ctl2(): Unit =
  val rows = List(1, 2)
  val files = List('a', 'b')
  val pairs1 = for x <- rows; y <- files yield (x, y)
  val pairs2 =
    for
      x <- rows
      y <- files
    yield (x, y)
  val pairs3 = for { x <- rows; y <- files } yield (x, y)
  println(pairs1 == pairs2 && pairs2 == pairs3)
  for x <- rows; y <- files do printf("(%d, %c)", x, y)
  println()
  for
    x <- rows
    y <- files
  do printf("(%d, %c)", x, y)
  println()
true
(1, a)(1, b)(2, a)(2, b)
(1, a)(1, b)(2, a)(2, b)

For a single generator use the one-line form. See For Comprehensions.

Trivial conditionals

Scala has no ternary operator, so a short if … else … goes on one line inside a larger expression: val res = if foo then bar else baz. Never do this with an imperative if that has statements as branches, and never add braces to it.

Method invocation

Calls follow Java: no space around the dot or before the opening parenthesis, a single space after each comma, and name = value with spaces around the equals sign for named arguments (foo(x = 6, y = 7)).

Arity-0

Parentheses on a call of a method with no arguments are omitted only when the method has no side effect: queue.size, never println. Scala 3 enforces it (see the naming section above); the rule on the invocation page is the call-site half of the declaration convention.

//> using scala 3.10.0
@main def arity(): Unit =
  val queue = scala.collection.mutable.Queue(1, 2, 3)
  println(queue.size)
  println(queue.iterator.hasNext)
  println(queue.dequeue())
  val it = queue.iterator
  println(it.next())
  println("daniel" + " " + "spiewak")
  println("daniel".+(" "))
3
true
1
2
daniel spiewak
daniel

Arity-1 (infix notation)

The guide discourages infix notation for ordinary methods (names mkString ",", and above all javaList add item, which has a side effect) and allows it for symbolic operators and for a few well-known short methods like a max b. Scala 3 turns this into a diagnostic: an alphanumeric method that is not declared infix warns when used as an operator.

//> using scala 3.10.0
import scala.annotation.targetName

class Money(val cents: Long):
  def plus(other: Money): Money = Money(cents + other.cents)
  infix def add(other: Money): Money = Money(cents + other.cents)
  def +(other: Money): Money = Money(cents + other.cents)
  @targetName("percent") def %(p: Int): Money = Money(cents * p / 100)

@main def infix(): Unit =
  val a = Money(150)
  val b = Money(250)
  println((a + b).cents)
  println((a plus b).cents)
  println((a add b).cents)
  println((a % 10).cents)
  val names = List("a", "b")
  println(names mkString ",")
  println(names.mkString(","))
  val list = java.util.ArrayList[String]()
  list add "x"
  println(list)
  println(names.map (_.toUpperCase))
  println(names map (_.toUpperCase))
  println(1 max 2)
-- Warning: infix.scala:14:13
14 |  println((a plus b).cents)
   |             ^^^^
   |Alphanumeric method plus is not declared infix; it should not be used as infix operator.
   |Instead, use method syntax .plus(...) or backticked identifier `plus`.
   |The latter can be rewritten automatically under -rewrite -source 3.4-migration.
-- Warning: infix.scala:21:7
21 |  list add "x"
   |       ^^^
   |Alphanumeric method add is not declared infix; it should not be used as infix operator.
   |Instead, use method syntax .add(...) or backticked identifier `add`.
   |The latter can be rewritten automatically under -rewrite -source 3.4-migration.
2 warnings found
400
400
400
15
a,b
a,b
[x]
List(A, B)
List(A, B)
2

The compiler warns for the user-defined plus and for the Java method add, accepts the declared infix def add, and stays silent for the library methods mkString, map and max, which are treated as established infix methods. The guide’s checklist is therefore mostly checked for you; what it still adds is the opinion that even the silent ones, such as names mkString ",", are better with a dot.

Symbolic methods and operators are always called with spaces around the operator (a + b, not a+b and not a.+(b)). Symbolic methods with more than one parameter are discouraged, and the deprecated /: and :\ give way to foldLeft and foldRight.

Higher-order functions are invoked with the dot and no space before the argument list, using parentheses or braces: names.map(_.toUpperCase). The two forms in the guide’s "not recommended" list (names map (_.toUpperCase) and names.map (_.toUpperCase)) both compile, as the output above shows, but chains of them become hard to read.

Files

A class, trait or object cannot be split across files, and a class and its companion must be in the same file. A sealed type can only be extended in its own file, so all its subclasses are in the same file, and the guide recommends one file for such a family (Option.scala for the Option ADT). Names are by convention: a file is named after the class it contains, or the parent class if it contains several; unrelated classes grouped by feature get a descriptive lower camel case name, and a file for quick tests can be called anything, such as demo-bug.scala. The directory structure may follow the packages, "at the discretion of the developer".

The sealed rule is enforced:

// Shapes.scala
sealed trait Shape
//> using scala 3.10.0
// Other.scala
final class Circle extends Shape
@main def other(): Unit = println(Circle())
-- [E112] Syntax Error: sl/Other.scala:2:12
2 |final class Circle extends Shape
  |            ^
  |            Cannot extend sealed trait Shape in a different source file
  |
  | longer explanation available when compiling with `-explain`
1 error found
Compilation failed

For the guide’s package object model in package.scala: Scala 3 allows top-level val, def, type and other definitions in any file, and the compiler wraps them in an object named after the file (a$package for a.scala in the package example above). The guide’s convention for a file of top-level terms is a lower camel case name, which then also names that wrapper. See Packages, Imports & Top-Level Definitions.

Scaladoc

Document every package, class, trait, object, method and other member. The first sentence is a summary that experienced readers can use as a reference; later paragraphs hold the detail and examples for newcomers. The tool does not force a comment layout, and the guide shows three:

//> using scala 3.10.0
package trailhead

/**
 * Javadoc style: returns a greeting.
 *
 * Details of the Javadoc layout.
 */
def javadocStyle: String = "a"

/** Scaladoc style, asterisks in column two.
 *
 *  Details of the layout.
 *
 *  @return the text `b`
 */
def columnTwo: String = "b"

/** Scaladoc style, asterisks in column three.
  *
  * Details of the layout.
  */
def columnThree: String = "c"

/** One-line format */
def simple: Unit = ()

All three were processed by scala doc . -o site -f on 3.10.0, which wrote the site without errors. The Scaladoc-style comments start the text on the first line and align the text on column five, which lines up with a source indented by two spaces; the Javadoc style starts text on the second line.

General style. Say "Returns X" rather than "This method returns X" or "Get the first"; for a class say "Does X" rather than "This class does X"; describe what the code does, not what it should do (the guide’s "returns", not "return"). Use this for the instance and "this object" for an object. Link to library classes with the square bracket syntax, . Do not repeat a one-line description in @return. Prefer the wiki-style markup over HTML (Scala 3 Scaladoc also accepts Markdown). Examples are either full listings or REPL transcripts. @define macros are useful for repeated values.

Packages are documented in a file (the guide says package.scala) with a comment on the package or package object that lists the main classes, then what the package object provides, with a few examples. The page Scaladoc shows how packages are documented on Scala 3.

Classes, objects and traits. Document type parameters with @tparam. A class created through a companion says so in its description and leaves the details to the companion; a class created through its constructor uses @constructor, @param and an example for a complex class. An object that is a factory says so and defers the details to the apply methods; an object that holds implicit conversions shows an example. A trait’s documentation describes the members the implementing classes must provide and lists known implementations.

//> using scala 3.10.0
/** A person who uses the application.
  *
  * @constructor create a new person with a name and age.
  * @param name the person's name
  * @param age the person's age in years
  */
class Person(name: String, age: Int)

object Person:
  /** Creates a person with a given name and age.
    *
    * @param name their name
    * @param age the age of the person to create
    */
  def apply(name: String, age: Int): Person = new Person(name, age)

Methods and other members. Document all of them: a summary sentence, then detail, then every parameter and type parameter (@tparam). For curried functions give an idiomatic usage example, and for implicit parameters (using parameters in Scala 3) say where the values come from and whether the caller needs to do anything for them to be available.

Enforcing the style with tools

Style rules that are not checked by a tool drift. The tooling in Scala is third-party, not part of the compiler: Scalafmt (formatting) and Scalafix (linting and rewriting). The compiler itself offers only a few rewrites. All the commands below were run on 2026-10-16.

Scalafmt

Scalafmt is a code formatter driven by a .scalafmt.conf file at the project root. Scala CLI embeds it as scala fmt. A minimal configuration pins the formatter version, selects the Scala 3 dialect and sets the line length (the guide’s "about 100 characters" is a good value):

version = "3.11.5"
runner.dialect = scala3
maxColumn = 100

Run on a messy braced file:

package trailhead

class   Trail(name:String,lengthKm:Double,val difficulty :Int) {
    def describe=s"$name: $lengthKm km"
    def longer(other:Trail):Boolean = {
        this.lengthKm>other.lengthKm
    }
}

object Report {
    def total(trails:List[Trail])  = trails.map(_.lengthKm).foldLeft(0.0)(_+_)
}

scala fmt --check . prints a diff and exits with a non-zero code (the command that goes into continuous integration), and scala fmt . rewrites the file:

package trailhead

class Trail(name: String, lengthKm: Double, val difficulty: Int) {
  def describe = s"$name: $lengthKm km"
  def longer(other: Trail): Boolean = {
    this.lengthKm > other.lengthKm
  }
}

object Report {
  def total(trails: List[Trail]) = trails.map(_.lengthKm).foldLeft(0.0)(_ + _)
}

This applies the guide’s spacing, two-space indentation and operator rules. Scalafmt can also migrate the syntax. With three extra options:

version = "3.11.5"
runner.dialect = scala3
maxColumn = 100
rewrite.scala3.convertToNewSyntax = true
rewrite.scala3.removeOptionalBraces = yes
rewrite.scala3.insertEndMarkerMinLines = 10

the same messy file becomes braceless, and it prints All files are formatted with scalafmt :) on a second --check:

package trailhead

class Trail(name: String, lengthKm: Double, val difficulty: Int):
  def describe = s"$name: $lengthKm km"
  def longer(other: Trail): Boolean =
    this.lengthKm > other.lengthKm

object Report:
  def total(trails: List[Trail]) = trails.map(_.lengthKm).foldLeft(0.0)(_ + _)

The same conversion also rewrites control structures and vararg splices. On this old-style input:

package trailhead

object Old {
  def run(xs: List[Int]): Int = {
    val values = List(1, 2, 3)
    if (xs.isEmpty) 0 else total(values: _*)
  }
  def total(xs: Int*): Int = {
    for (x <- xs) yield x
    xs.sum
  }
  def pm(x: Option[Int]) = x match {
    case Some(v) => v
    case None => 0
  }
}

scala fmt with convertToNewSyntax and removeOptionalBraces produced:

package trailhead

object Old:
  def run(xs: List[Int]): Int =
    val values = List(1, 2, 3)
    if xs.isEmpty then 0 else total(values*)
  def total(xs: Int*): Int =
    for x <- xs yield x
    xs.sum
  def pm(x: Option[Int]) = x match
    case Some(v) => v
    case None    => 0

Scalafmt has many more options (alignment, newlines, danglingParentheses, trailingCommas, rewrite.rules); they are listed in the Scalafmt configuration reference. The first run downloads Scalafmt itself into the Coursier cache. Sbt and the IDEs use the same .scalafmt.conf (see sbt & Build Tools and IDEs).

Scalafix

Scalafix is a refactoring and linting tool configured by .scalafix.conf. Some rules are syntactic and run on source alone; semantic rules (for example ExplicitResultTypes, which writes the result types the guide asks for on public members, or RemoveUnused) need the compiler’s SemanticDB output. This is the guide’s procedure syntax rule as a rewrite, together with the lint rule DisableSyntax that forbids var:

rules = [
  ProcedureSyntax,
  DisableSyntax
]
DisableSyntax.noVars = true
DisableSyntax.noReturns = true
object Proc {
  def printBar(bar: String) {
    println(bar)
  }
  def twice(x: Int) = x * 2
  var counter = 0
}

Scalafix 0.14.9 run through Scala CLI (scalafix-cli_2.13.18 from Maven Central) rewrote the procedure and reported the lint error, with exit code 64:

Proc.scala:7:3: error: [DisableSyntax.var] mutable state should be avoided
  var counter = 0
  ^^^
object Proc {
  def printBar(bar: String): Unit = {
    println(bar)
  }
  def twice(x: Int) = x * 2
  var counter = 0
}

With --check the same run prints the pending rewrite as a diff and exits with 32 without changing the file. In a Scala 3 Scala CLI project the experimental scala fix --power runs Scalafix with the project’s SemanticDB (and its own built-in rules for migrating directives) with the same .scalafix.conf. On a file containing var hits = 0, it reported the same [DisableSyntax.var] error and exited with 64. In an sbt build the equivalent is the sbt-scalafix plugin (not run here: it needs an sbt project). Procedure syntax is a Scala 2 construct, so that particular rule is for code that is being migrated.

The compiler’s own rewrites

The compiler can rewrite some constructs itself with -rewrite and a migration source. The -new-syntax option turns if (c) into if c then, for (x <- xs) into for x <- xs, and values: _* into values*, while -indent also converts braces to indentation. On the old-style Old object above (as a Scala CLI source), -rewrite -source:3.4-migration -new-syntax gave:

5c5
<     if (xs.isEmpty) 0 else total(values: _*)
---
>     if xs.isEmpty then 0 else total(values*)
8c8
<     for (x <- xs) yield x
---
>     for x <- xs yield x

The same options with -indent instead of -new-syntax convert the braces of the object and its methods to indentation but leave if (…) untouched (if (xs.isEmpty) 0 else total(values*) in the output), and also rewrite the vararg splice. The rewritten file is patched in place, so work on a clean working tree. See Optional Braces & Indentation for the full list of options and Compiler Options, Linting & Errors for the warnings (-Wunused, -Wvalue-discard) that back some style rules.

Practical advice

  • Adopt Scalafmt in the first commit, and run scala fmt --check . in CI so that style never needs to be discussed in review. Decide braces versus indentation once for the whole code base; mixing is legal but noisy.

  • Treat the guide as defaults you can override. Where it conflicts with a rule of Scala 3 (procedure syntax, _*, GNU braces), the compiler wins; where it conflicts with your formatter’s configuration, the formatter wins.

  • Keep public members explicitly typed. If you cannot, use ExplicitResultTypes from Scalafix to write them in, and turn on binary compatibility checks for libraries (see Versions, Compatibility & JDKs).

  • When you migrate a Scala 2 code base, apply the style tooling after the migration steps in Migrating from Scala 2, not before; the dropped features are listed in Incompatibilities & Dropped Features.

  • Across Scala 3 versions the formatting options are stable; only compiler warnings change between releases, see Upgrading Between Scala 3 Versions.

Where the guide and the books are outdated

  • The guide is written for Scala 2: braces everywhere, if (c), for (x <- xs), x: _*, extends A with B, procedure syntax, implicit modifiers and package object. Each has a Scala 3 replacement named above; it has not been revised for the quiet syntax, then, using clauses or top-level definitions.

  • The warning about a trailing operator before line breaks, and the claim that a single parameter list cannot infer a lambda’s types, are not true on Scala 3.

  • The text on parentheses ("Scala 2 merely warns") reflects Scala 3’s current behaviour: it errors.

  • Books and articles that teach Scalastyle or Scalariform predate Scalafmt and Scalafix; for Scala 3 use Scalafmt, which supports the Scala 3 dialect.

  • The guide says nothing about the new features (extension methods, enum, opaque types, given). The same naming and spacing principles are applied to them in the other pages of this section.