Packages, Imports & Top-Level Definitions
|
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, 2026-10-12) and every example was run with Scala CLI and Scala 3.10.0 on
JDK 26 (checked on 2026-10-15), except where a block says otherwise. Packages group definitions under a name and decide how
they are found; imports decide which of those names are visible in a given piece of code. Both exist in Java and Kotlin
too, but Scala makes them more flexible: a package can be opened in nested blocks, an import can appear in any scope, and
an import can rename or hide what it brings in. Scala 3 also tidies the syntax (* instead of _, as instead of
=>) and removes the need for package objects, because values, methods and type aliases can now sit directly at the
top level of a source file.
Everything here works on the 3.3 LTS and on the 3.9 LTS line, except the referenceable package objects of
Referenceable package objects (SIP-68, preview), which are a preview feature of 3.10 only. Where classes and objects fit into this is on
Classes & Constructors and
Objects & Companions; the braceless package p: syntax used
below is explained on
Optional Braces & Indentation; the details of
given instances are on Givens & Using Clauses.
Packages
A package is a namespace. A source file declares one with a package clause, and everything that follows belongs to it.
Unlike Java, the name of the package does not have to match the directory structure, and one file may declare several
packages. The directory layout is only a convention, which the tools (sbt, Scala CLI, IDEs) do not enforce, but following
Java’s layout (one directory per package segment) keeps the code easy to find and keeps Java developers on a team at ease.
The rest of this page uses a small garden, in the style of the trailhead scenario of this section. The first file declares a package and its contents:
//> using scala 3.10.0
package trailhead
package garden
case class Bed(crop: String, plants: Int)
object Beds:
val all: List[Bed] = List(Bed("basil", 12), Bed("mint", 3))
Two consecutive clauses, package trailhead and package garden, are a chained package clause: the file is in package
trailhead.garden, and it also sees everything declared in trailhead, which is the difference from the single clause
package trailhead.garden. The next file uses the first one and also shows the import forms covered below:
package trailhead.app
import trailhead.garden.*
import trailhead.garden.Beds.{all as allBeds}
import java.util.{List as JList, ArrayList}
import scala.collection.mutable as mut
@main def run(): Unit =
val beds = allBeds
val names: mut.ListBuffer[String] = mut.ListBuffer()
beds.foreach(b => names += b.crop)
val jl: JList[String] = ArrayList()
names.foreach(n => jl.add(n))
println(names.mkString(", "))
println(jl)
println(Beds.all == beds)
basil, mint
[basil, mint]
true
Both files were compiled together with scala run . in one directory. The Garden.scala file carries the
//> using scala directive and the other one inherits it from the same project.
Nested packages and chained clauses
Packages can be written in blocks, or with the braceless package p: form and an optional end marker, and they can nest.
A nested package sees the members of the enclosing one. A file may also contain several unrelated top-level package
clauses, and they are independent of each other. The example below keeps owner in trailhead, uses it from the nested
package garden, and imports a name from the second, unrelated package using an absolute path:
//> using scala 3.10.0
package trailhead:
val owner = "garden-club"
package garden:
class Bed(val crop: String)
// sees `owner` because it is nested inside `package trailhead:`
def label(b: Bed): String = s"$owner/${b.crop}"
end garden
package tools:
def describe(b: garden.Bed): String = garden.label(b)
end tools
end trailhead
package other.place:
import trailhead.garden.*
import _root_.trailhead.tools.describe
def show(): String = describe(Bed("kale"))
@main def vis(): Unit =
println(other.place.show())
garden-club/kale
_root_ always names the outermost package. It is only needed when a nested package has a member with the same name as a
top-level one (for instance, a package called java inside your own code), which otherwise captures the name.
The visibility rule has a catch: a package clause such as package a.b opens only b, and does not make the members
of a visible, while package a followed by package b, or package a: containing package b:, does. This is the same
trap as in Scala 2. A real compiler message shows it:
//> using scala 3.10.0
package a:
val inA = 1
package a.b:
def f = inA
[error] ./Chain.scala:5:11
[error] Not found: inA
[error] def f = inA
[error] ^^^
Replacing the second clause with a nested package b: inside package a: (or with two chained clauses) makes inA
visible, and the program prints 1.
Package-private and qualified modifiers
A package also serves as an access scope. private[trailhead] makes a definition visible inside trailhead and its
nested packages only, and protected[trailhead] does the same for subclasses. This is the closest thing to Java’s default
(package-private) access, which Scala does not have as a keyword: with no modifier a member is public.
Classes & Constructors covers access modifiers in full.
Import statements
An import makes names from a package, object, or any other stable identifier visible without the qualified path. It may appear anywhere a definition may: at the top of a file, inside an object, inside a method, or inside a block, and it is in effect up to the end of that scope. Importing late and locally (for instance, inside the one method that needs it) is idiomatic, because it limits where the names are valid.
| Form | Meaning |
|---|---|
|
one member |
|
several members; the braces are optional for a single one |
|
every member (a wildcard import), except |
|
a member under a different name (renaming) |
|
everything in |
|
a package or object under a shorter name |
|
every member including the given instances |
|
two imports in one statement |
Wildcard imports use *
Scala 3 writes the wildcard as *, as in Java, Kotlin and Python, instead of the underscore of Scala 2 (import a._).
The reason is consistency: _ already means "unnamed placeholder" in too many other positions. A member whose own name is
* is still importable, by quoting it with backticks, as the example below shows. It also shows the renaming selector as.
//> using scala 3.10.0
object A:
def * = "star"
def min = "min"
object B:
import A.`*` // imports just the member called *
val v = *
object C:
import A.* // imports everything in A
val v = (*, min)
println(B.v)
println(C.v)
import A.{min as minimum, `*` as multiply}
println((minimum, multiply))
import Predef.{augmentString as _, *}
import scala.annotation as ann
import java as j
println(j.lang.Math.max(1, 2))
@ann.tailrec def f(n: Int, acc: Int): Int = if n == 0 then acc else f(n - 1, acc + n)
println(f(4, 0))
star
(star,min)
(min,star)
2
10
Source: Scala 3 Reference: Imports
Renaming and hiding with as
import a.{b as c} makes b available as c, and b is no longer available under its own name through that import. A
single renaming does not need braces. When the new name is _, the member is not imported at all, which is how a
wildcard import excludes a name, so that it can be taken from somewhere else or declared locally:
//> using scala 3.10.0
import scala.collection.mutable.{Map as _, *}
val b = scala.collection.mutable.ListBuffer(1)
val m = Map("k" -> 1) // Predef's immutable Map, because mutable.Map is hidden
println(m.getClass.getSimpleName)
Map1
Renaming is the standard way of working with two types of the same name, such as java.util.List and
scala.List, or a Java class and a Scala one: import java.util.{List as JList} keeps both usable. The hiding selector
has to come before the wildcard, as in {Map as _, *}, otherwise the wildcard has already taken the name. Several names
are hidden in one selector list ({Map as _, Set as _, *}).
Ambiguity and precedence
When two imports bring the same name into scope in the same scope, the reference is ambiguous and the compiler refuses to choose. Both the wildcard form and the single-name form behave like this:
//> using scala 3.10.0
import scala.collection.immutable.Set
import scala.collection.mutable.Set
val s = Set(1, 2)
[error] ./d.sc:4:9
[error] Reference to Set is ambiguous.
[error] It is both imported by name by import scala.collection.immutable.Set
[error] and imported by name subsequently by import scala.collection.mutable.Set
[error] val s = Set(1, 2)
[error] ^^^
The same message appears with import scala.collection.immutable.* and import scala.collection.mutable.* (the wording
changes to "imported by import …_"). The way out is a rename, a hide, or a qualified use (mutable.Set(1, 2)).
The rules, from the strongest to the weakest binding, are:
-
definitions and declarations that are local, inherited, or made available by a package clause in the same compilation unit;
-
explicit imports, such as
import a.B(and renamings); -
wildcard imports, such as
import a.*; -
definitions made available by a package clause not in the same compilation unit (for instance, members of the same package that live in other files) and the default imports.
A binding in an inner scope shadows bindings of the same or lower precedence in an outer scope. The practical consequences, all checked on 3.10.0:
-
a local
val List = "local wins"hides everything imported; -
an explicit import in a nested block replaces the default
Listin that block; -
a wildcard import written in the file already beats a default import, which surprises many programmers:
//> using scala 3.10.0
import java.util.* // wildcard in this file, now java.util.List shadows scala.List
@main def ord(): Unit =
val l = List(1, 2)
[error] ./Ord.scala:4:11
[error] object List in package java.util does not take parameters
[error] val l = List(1, 2)
[error] ^^^^
//> using scala 3.10.0
@main def ord2(): Unit =
val List = "local wins" // a local definition shadows everything imported
println(List)
import java.util.List // explicit import in a nested scope
val l: List[Int] = new java.util.ArrayList[Int]()
println(l.getClass.getSimpleName)
local wins
ArrayList
The lesson is to avoid import java.util.* in Scala code, and to import Java collections under another name or
qualified (java.util.List). Scala has no dedicated compiler warning for this kind of shadowing, so a unit test or a
review is what finds it.
Imports from objects, values and anywhere
The prefix of an import is any stable identifier: a package, an object, a val of a stable type, or this. Importing
the members of an object is how a program uses constants or helper methods without writing the object name each time, like
Java’s static imports, which do not exist in Scala because objects hold what Java keeps in static. An import in a method
or a block is limited to it:
//> using scala 3.10.0
object Units:
val centimetres = 100
def toMetres(cm: Int): Double = cm / 100.0
def width(cm: Int): Double =
import Units.* // visible only in this method
toMetres(cm) * 2
@main def local(): Unit = println(width(150))
An import can also bring in the members of a package-level val or of an enumeration, which is common for enum cases
(import Colour.*; see Enums).
Packages imported by default
Every compilation unit begins with three implicit imports, in this order, and the later ones can shadow the earlier ones:
-
java.lang.*:String,Object,Math,Thread,Integer,Runnable,Iterable… -
scala.*:Int,Option,Seq,List,Eitherand the other core types, through the packagescalaand itsscala.packagealiases (that is whereList,Vector,SeqandIterableare defined for the simple names) -
scala.Predef.*:println,assert,require,Map,Set,summon,identity,ArrowAssoc(->), the string conversions and other everyday names
//> using scala 3.10.0
@main def defaults(): Unit =
val s: java.lang.String = "same type" // java.lang.* : String, Math, Integer ...
val i: scala.Int = 1 // scala.* : Int, List, Option, Seq ...
val m = Map("a" -> 1) // scala.Predef.Map = immutable.Map
assert(m == scala.Predef.Map("a" -> 1)) // scala.Predef.* : assert, println, Map, Set ...
println(classOf[String] == classOf[java.lang.String])
println(List(1) == scala.List(1))
println(Math.max(i, 2))
println(m.getClass.getName)
true
true
2
scala.collection.immutable.Map$Map1
A name that is not in these three packages must be imported: ClassTag, Random, Try, Future, ListBuffer and
java.util.List all need one (for instance, the compiler answers Not found: type ClassTag - did you mean Class?).
Importing given instances
The wildcard * imports everything except given instances, which are brought in on their own
with given. This is deliberate: it keeps a type-class instance from sneaking into scope by accident, since the instance
changes the meaning of the code that uses it. The compiler gives a helpful hint when an expected instance exists but was
not imported:
//> using scala 3.10.0
object Rules:
val base = 10
given Int = 42
object A:
import Rules.*
def v = summon[Int]
[error] ./Giv.scala:7:22
[error] No given instance of type Int was found for parameter x of method summon in object Predef
[error]
[error] Note: given instance given_Int in object Rules was not considered because it was not imported with `import given`.
[error] def v = summon[Int]
[error] ^
Writing import Rules.{*, given} (or import Rules.given for the givens alone, or import Rules.given Int for a given of
a type) fixes it, and println(A.v) then prints (10,42) for (base, summon[Int]). Selectors, type patterns, by-type
imports and the interaction with implicit resolution are on
Givens & Using Clauses.
Top-level definitions
In Scala 2 a val, a def or a type could not stand on its own in a package: it had to be wrapped in a package
object (package object util { ... }) in a file of its own. Scala 3 lifts the restriction: any kind of definition may be
written at the top level of a file, next to the classes and objects, and any number of files in the same package may do it.
//> using scala 3.10.0
package trailhead
type Labelled[T] = (String, T)
val count: Labelled[Int] = ("beds", 2)
def double(n: Int): Int = n * 2
private val hidden = "visible in the whole package"
case class Marker()
extension (m: Marker) def twice: (Marker, Marker) = (m, m)
package trailhead
@main def top(): Unit =
println(count)
println(double(21))
println(hidden)
println(Marker().twice)
(beds,2)
42
visible in the whole package
(Marker(),Marker())
(The first listing is Labels.scala, the second Main.scala, compiled together.) The definitions are used by their simple
name from the rest of the package, and from other packages with an import (import trailhead.double, or
import trailhead.*).
How they are compiled
The JVM has no top-level members, so the compiler wraps the definitions of a file in a synthetic object. A file
Labels.scala produces the object Labels$package, whose methods and fields are the definitions, and the wrapping is
transparent for the Scala source. javap -p on the compiled class shows the wrapper of the example above:
Compiled from "Labels.scala"
public final class trailhead.Labels$package$ {
public static final trailhead.Labels$package$ MODULE$;
private static final scala.Tuple2<java.lang.String, java.lang.Integer> count;
private static final java.lang.String hidden;
private trailhead.Labels$package$();
public static {};
private java.lang.Object writeReplace();
public scala.Tuple2<java.lang.String, java.lang.Integer> count();
public int double(int);
public java.lang.String hidden();
public scala.Tuple2<trailhead.Marker, trailhead.Marker> twice(trailhead.Marker);
}
The class directory contains Labels$package.class, whose static forwarders (public static int double(int), and so
on) let Java call Labels$package.double(21), Labels$package$.class, Marker.class and Marker$.class;
Main.scala adds its own Main$package classes and the class top produced by @main. The wrapper is placed last in the file, after any package
clauses, imports, classes and objects. The consequences, taken from the reference:
-
the file name matters for binary compatibility: renaming
Labels.scalarenamesLabels$package, which breaks compiled Java or Scala code that refers to the old name; -
the wrapped categories are pattern, value, method and type definitions, implicit classes and objects, and the companion objects of opaque type aliases; classes, traits and `enum`s are not wrapped;
-
a top-level
def mainis wrapped like any other method, so it is invoked asscala 'Labels$package', not asLabels; put entry points in an explicitly named object or use@main(see Hello World & @main Methods); -
privateon a top-level definition makes it private to the enclosing package (the whole package, across files), not to the file as in Kotlin; Scala 3 has no file-private modifier; -
overloaded variants of a top-level method must be in the same source file.
The last rule has a real error message. Splitting the two overloads of water into two files fails:
[error] ./Two.scala:2:1
[error] water is already defined as method water in .../One.scala
[error]
[error] Note that overloaded methods must all be defined in the same group of toplevel definitions
[error] def water(s: String): String = s"string $s"
[error] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
With Java, a call to a top-level function is a call to a static method of Labels$package, so naming the wrapper explicitly
with an object is kinder to Java callers. Kotlin has the same idea (top-level functions compile to a LabelsKt class)
and it can name the class with @file:JvmName; Scala has no equivalent annotation, the wrapper takes the file name.
Package objects
A package object is the Scala 2 container for package-level definitions:
//> using scala 3.10.0
package trailhead
package object legacy:
val version = 2
def hello = "from a package object"
type Id = Int
package legacy:
class Thing
@main def po(): Unit =
println(legacy.hello)
println(legacy.version)
val t = legacy.Thing()
println(t.getClass.getName)
from a package object
2
trailhead.legacy.Thing
Package objects are dropped in the language design: "they are still available, but will be deprecated and removed at some
point in the future", in the words of the reference. In 3.10.0 the code above compiles without warnings, so existing
code and the libraries cross-built with Scala 2 keep working. New code uses top-level definitions in a plain file; to move
existing code, change package object p { ... } to package p plus the same members at the top level of a file. The
difference to keep in mind is the compiled name (package$ for a package object against <file>$package$ for top-level
definitions), which matters for binary compatibility.
Referenceable package objects (SIP-68, preview)
A package object is not an ordinary value: val z = a.b, where b is a package with a package object, does not compile,
and the workaround is the unfriendly a.b.`package`. SIP-68 removes the limitation. It is a preview feature in Scala
3.10.0, enabled with the compiler option -preview, so it is not part of the 3.9 LTS line. A preview feature is final
in design and meant to become standard in a later minor release, but until then it needs the flag and its details may
still change. Without the flag the compiler says so:
//> using scala 3.10.0
package a
package object b:
val x = 1
package b:
class C
object Main:
val z = a.b
def main(args: Array[String]): Unit = println(z.x)
[error] ./Val.scala:8:11
[error] Preview package object values may only be used when compiling with the `-preview` compiler flag
[error] val z = a.b
[error] ^^^
Adding //> using option -preview (or -preview to scalacOptions in sbt) makes the same program compile and print:
1
a.b.package$@1ef7fe8e
(the second line comes from also printing z; the hash code changes from run to run). With the feature, a reference a.b
that stands alone expands to a.b.package. The reference documents two limits, which are kept for backward compatibility:
the expansion happens only for a standalone reference, not inside a longer select chain (a.b.c), a prefix or infix
expression; and a.b has the type a.b.package.type, so it contains only what is in the package object, not the other
members of the package a.b. It is a small feature for libraries that migrate from package objects, and application
code does not need it.
Scala 2 forms and the migration path
| Scala 2 | Scala 3 | Notes |
|---|---|---|
|
|
|
|
|
single renamings need no braces in Scala 3 |
|
|
hiding |
|
top-level definitions |
still compiles, no warning in 3.10.0 |
|
|
needs |
Scala 3.10.0 still accepts the old syntax by default. The compiler turns it into an error with -source future, and gives
the replacement:
[error] ./Old2.scala:3:19
[error] `_` is no longer supported for a wildcard import; use `*` instead
[error] This construct can be rewritten automatically under -rewrite -source future-migration.
[error] import scala.util._
[error] ^
[error] ./Old2.scala:4:38
[error] The import renaming `a => b` is no longer supported ; use `a as b` instead
[error] This construct can be rewritten automatically under -rewrite -source future-migration.
[error] import scala.collection.mutable.{Map => MMap}
[error] ^
The options -source 3.1-migration -rewrite rewrite the old import syntax to the new one automatically (so says the
reference); -source future-migration -rewrite is the form the message above suggests for 3.10. Library authors
cross-building for Scala 2 and 3 keep _ and => until they drop Scala 2 (Scala 3.0 accepts the old syntax in addition to
the new one for exactly this reason). See
Migrating from Scala 2 for the whole picture.
Differences from Java and Kotlin
-
Java forbids two public top-level classes in a file and ties the file name to the class; Scala has no such rule, and the file name only matters for the wrapper of top-level definitions.
-
Java’s
import staticis replaced by importing the members of anobject(or byimport Foo.*for a Java class: the static members of a Java class are imported like those of an object). -
Java’s
import a.*is the same asimport a.*in Scala 3, but Scala’s wildcard also imports nested packages and can be hidden or renamed. Scala imports can sit inside any scope; Java’s only at the top of the file. -
Kotlin also has
import a.b as cand*, and top-level functions with a generatedFileKtclass, so most of this page is familiar to Kotlin programmers. Scala’sprivatetop-level is package-wide, Kotlin’s is file-wide. -
The
java.lang.*default import exists in both Java and Scala; Scala addsscala.*andscala.Predef.*, and Kotlin importskotlin.*,kotlin.collections.*and a few more.
Where the books are outdated
Books written for Scala 2 (including the editions of the standard references from before 2021) teach import a._,
import a.{b => c}, import a.{b => _, _} and package objects as the way to put a method or a type alias in a package.
They are still right about the semantics (the scoping rules, ambiguity and the default imports have not changed), but
the current style is *, as, and top-level definitions in a plain file. Neither the preview feature SIP-68 nor the
Labels$package naming appears in them. The online Scala 3 Book and the Scala 3 Reference are the up-to-date
sources, and the former uses the new syntax for the Scala 3 tab of every example.