Scaladoc
|
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 command below was run with the Scaladoc and
compiler of the 3.10.0 distribution on JDK 26 (checked on 2026-10-15), and the output shown is the output that run
produced, with the long absolute paths shortened. The // error and // warn markers for documentation snippets are
new in 3.9, so they work on the 3.9 LTS and on 3.10 but not on the 3.3 LTS.
Scaladoc is the API documentation generator of the Scala language, the equivalent of Javadoc, KDoc and Groovydoc (see
Javadoc, and for the same job on the other JVM languages
Groovydoc). The Scala 3 version was rewritten from scratch. It reads the
TASTy files that the compiler writes next to the class files, not the source, so it sees the types the compiler
inferred, given instances, extension methods, opaque types and enum cases. It can also build a static site with
hand-written pages and a blog next to the generated API, check that the code in the documentation compiles, and search the
API by type signature.
Producing the documentation
There are three entry points, and they all run the same tool.
| Entry point | Command |
|---|---|
The |
|
Scala CLI |
|
sbt |
|
The sample library for this page has one class, its companion and an extension method, all documented.
package trailhead
/** A hiking route with a name and a length.
*
* Routes are compared by length:
*
* - `shorter` returns the shorter of two routes
* - [[Route.named]] builds a route when the length is not known yet
*
* ## Example
*
* ```scala
* val ridge = Route("Ridge", 12.5)
* val valley = Route("Valley", 8.0)
* ridge.shorter(valley).name // "Valley"
* ```
*
* @constructor creates a route
* @param name the display name
* @param km the length in kilometres, never negative
* @groupname query Queries
* @groupprio query 10
* @groupname build Building
* @groupprio build 20
* @since 1.0
*/
class Route(val name: String, val km: Double):
/** Returns the shorter of this route and `other`.
*
* @param other the route to compare with
* @return `this` when it is not longer than `other`, otherwise `other`
* @group query
*/
def shorter(other: Route): Route = if km <= other.km then this else other
/** Describes the route in one line, for example `Ridge (12.5 km)`.
* @group query
*/
def describe: String = s"$name ($km km)"
/** Factory methods for [[Route]].
*
* @define unit kilometres
*/
object Route:
/** Builds a route of unknown length, in $unit.
*
* @param name the display name
* @group build
*/
def named(name: String): Route = Route(name, 0.0)
/** Parses `"Name:12.5"` into a route.
*
* @throws NumberFormatException if the length is not a number
* @group build
*/
def parse(text: String): Route =
val Array(n, k) = text.split(':')
Route(n, k.toDouble)
/** The length of a route in miles. Extension method, found by type-based search. */
extension (r: Route) def miles: Double = r.km * 0.621371
Compile it first, because the input of Scaladoc is the compiler output, then generate the site:
$ scalac -d classes src/Route.scala
$ scaladoc -d site -project trailhead -project-version 1.0.0 -groups -author classes
$ ls site/trailhead
Route$.html
Route.html
Both commands print nothing when they succeed. site now holds index.html, trailhead.html for the package and one page
per class, object and top-level definition, together with scripts, styles and the search index. Open it from a local web
server rather than from file: URLs, because the search and the snippet features use JavaScript workers.
The Scala CLI route generates the default site from the sources, with the project name taken from the directory:
$ scala doc . -o site -f
Option -classpath was updated
1 warning found
Wrote Scaladoc to /.../site
The warning is harmless: it says that Scala CLI replaced the default class path option. The Scaladoc-only options such as
-project and -groups cannot be passed through scala doc in this version, because they are handed to the compiler as
bad option entries (the compile step of Scala CLI rejects them). For a published site use the scaladoc launcher, as above,
or sbt, where the options go into scalacOptions and are passed to Scaladoc:
Compile / doc / scalacOptions ++= Seq(
"-project", "trailhead",
"-project-version", version.value,
"-groups",
"-source-links:github://example-org/trailhead",
"-snippet-compiler:compile"
)
(The sbt fragment was not run here, because sbt was not used for this page; the options are the ones shown in the table of
settings below, all of which were exercised with the scaladoc launcher.)
Source: Scaladoc settings, New features for Scaladoc.
Doc comments
A Scaladoc comment starts with /** and goes immediately before the member it documents: a class, trait, object, enum,
method, field or type. It cannot document a package; the text for the root package comes from a separate file given with
-doc-root-content. The first sentence becomes the summary shown in lists, so it should stand on its own.
The style guide accepts three indentation styles and does not mandate one: the Javadoc style with the asterisks in column two and the text on the second line, or the two Scaladoc styles that start the text on the first line and align the asterisks on column two or three. The comment markup is sensitive to whitespace, so keep the margin consistent throughout the comment. The column-three style used above lines the text up with code that is indented by two spaces.
/** Provides a service as described.
*
* This is further documentation of what we're documenting.
* Here are more details about how it works and what it does.
*/
def member: Unit = ()
The style guide’s advice on content is short: get to the point, describe what the member does (say "returns the first
element" rather than "return" or "this method returns"), do not repeat a one-line description in @return, and link library
types with the double-bracket syntax. See Style Guide for the rest of the
code conventions.
Tags
Tags are @name markers at the start of a line, with nothing before them. Everything in this table is supported by the Scala 3
Scaladoc.
| Tag | Purpose |
|---|---|
|
documents the primary constructor, which in Scala is part of the class definition |
|
a value parameter of a method, constructor or class; one per parameter |
|
a type parameter of a method, constructor or class |
|
the return value of a method; one per method |
|
an exception that the method or constructor may throw |
|
a link to another entity or an external document |
|
a pre- or post-condition or another restriction |
|
example code or related documentation |
|
provenance; |
|
marks the entity deprecated, with the replacement and the version or date |
|
member groups; shown only with |
|
defines |
|
explicit inheritance of the comment of the overridden member |
|
chooses the syntax of this one comment |
A comment that is missing on an overriding member is inherited from the member it overrides, and so are the @param,
@tparam and @return entries that are missing. @inheritdoc is for when you want the inherited text plus your own.
@define gives a comment-level macro. In the sample, @define unit kilometres in the comment of object Route makes
$unit in the comment of named appear as "kilometres" in the generated page; the macro is scoped to its source file.
The generated page for describe and shorter shows the @group members under their group name:
Grouped members
Queries
def describe: String
def shorter(other: Route): Route
Value members
Concrete methods
def miles: Double Extension method from trailhead
Concrete fields
val km: Double
val name: String
(That is the structure of the generated HTML for Route, read back from the generated file and shortened.) The class comment
produced "Value parameters", "Constructor" and "Since" sections from @param, @constructor and @since.
Member groups
@group, @groupname, @groupdesc and @groupprio organise a large type into sections. They are off unless the compiler
option -groups is passed to Scaladoc, which is why they appear in the sample as comments and in the command above as a
flag. Put @groupname and @groupprio in the comment of the type that owns the members, and @group on each member. A group
name is a single word; @groupprio takes a number between 0 and 999, low values first. Members with no @group are listed
under "Ungrouped" with priority 1000.
Markdown and wiki syntax
Comments are parsed as Markdown (CommonMark) by default. This is the main change from the Scala 2 Scaladoc, whose language
was the "wikidoc" syntax: ''italic'', '''bold''', __underline__, ^superscript^, ,,subscript,,, headings written
=Heading=, and {{{ code }}} blocks. The new tool still reads wiki syntax. Switch the whole project back with the setting
-comment-syntax wiki, or one comment with @syntax wiki. The Markdown dialect has two extensions: wiki-style definition
links and {{{ }}} code blocks are accepted too, so an old comment’s example block keeps working.
The sample above is Markdown: a bullet list, a ## heading and a fenced scala block. A fenced block is rendered with
highlighting, and it is also the hook for the snippet compiler (see below).
New projects should use Markdown. Keep wiki syntax only where an old code base has too many comments to convert at once.
Linking
A definition link is the wiki-style double-bracket form, kept on purpose because it is shorter for the common case where
the link has no label: is a link to the class. The reason the Markdown link syntax is not
used for it is the collisions: [](#field) clashes with URL fragments, and [](meth(Int)) with overload resolution.
The rules, from the Scaladoc manual:
-
A link is a sequence of identifiers separated by
.;#is accepted too, for compatibility with the Scala 2 syntax. -
An identifier refers to the first entity with that name in source order. A trailing
$forces a value (anobject, aval, agiven) and a trailing!forces a type (a class, an alias, a type member). -
Links are resolved relative to the entity the comment is on: when documenting a class, relative to its enclosing package, class or object.
-
packagestands for the enclosing package andthisfor the enclosing class or object, so a link survives a rename. -
Scala backtick quoting works inside a link, so a member with an unusual name can be linked without the Scala 2 backslash escape.
-
Spaces are allowed inside a link, which makes overload selection such as
[[meth(Int, Float)]]possible. -
A label is separated from the target by a space:
[[trailhead.Route$ the companion]].
An unresolved link is a warning, not an error. A line @see , naming a member that does not exist,
was added to the comment of Route and the library was compiled again:
$ scaladoc -d site -project trailhead classes
-- Warning: src/Route.scala:28:6 ----------------------------------------------
28 |class Route(val name: String, val km: Double):
| ^
|Couldn't resolve a member for the given link query: trailhead.Route.longest
1 warning found
The position points at the documented member, not at the line of the link inside the comment. The site is still written, and
passing -Werror to Scaladoc printed the same warning and also still produced the site, so a CI job that must fail on a broken
link has to look at the tool’s output itself. -no-link-warnings silences
these warnings for ambiguous or incorrect member links (it does not affect warnings about assets), but it hides the very
mistake that rots a documentation set, so prefer fixing the links.
To link to a type in another library, give Scaladoc a mapping from a class path entry to that library’s documentation with
-external-mappings, for example
.*scala.*::scaladoc3::https://scala-lang.org/api/3.x/,.*java.*::javadoc::https://docs.oracle.com/javase/8/docs/api/. A mapping is
<regex>::[scaladoc3|scaladoc|javadoc]::<url>. Source links, which add a link from each member to its source file in the
repository, use -source-links:github://example-org/trailhead (or gitlab://..., or a template with ${FILE_PATH} and
${FILE_LINE}), and -revision pins the links to a tag.
Source: Scaladoc: Linking documentation.
Static documentation sites
Scaladoc can build a Jekyll-style site next to the API. The directory given with -siteroot (default ./docs) holds the
pages in _docs; the Markdown or HTML files in it are templates, expanded with the Liquid engine, and everything else is
static. A site for the sample library:
docs3/
└── _docs/
├── index.md
└── errors.md
The home page uses two things that only Scaladoc understands, a Liquid variable and a definition link:
# Trailhead
Version {{ projectVersion }} of {{ projectTitle }}. See [[trailhead.Route]] for the API.
projectTitle and projectVersion come from -project and -project-version. The command that builds it is the same as
before with the site root added: scaladoc -d site -project trailhead -siteroot docs3 classes. A page may start with YAML front
matter. The properties Scaladoc knows are title, extraCss, extraJs, hasFrame (set to false to drop the navigation
frame), layout, and the redirect properties redirectFrom and redirectTo. The title of the home page is the project
name: a title: property in index.md is ignored with the message "Property title will be overridden by project name".
By default the tree under _docs becomes the sidebar. A sidebar.yml file in the site root replaces it with an explicit
tree: index for the section index, subsection for nesting, page for a page, and hidden: true to keep a page out of
the navigation. Layouts live in _layouts (a layout is a template with {{ content }} where the page goes) and images and
other assets in _assets, which are linked relative to that directory. Redirect properties keep an old URL alive after a page
is moved.
Blogs
A directory named _blog in the site root enables the blog. Posts are Markdown files in _blog/_posts whose names start
with YYYY-MM-DD-, with front matter for title, author, date, subTitle and authorImg. A blog.yml can change the
input and output directories or set hidden: true. The directory for the sample:
docs2/
├── _docs/index.md
└── _blog/_posts/2026-10-15-trailhead-1-0.md
Building the site wrote the post under the documentation tree, by date:
site/docs/blog/index.html
site/docs/blog/2026/10/15/trailhead-1-0.html
The post is ordinary Markdown with the same definition links as the API comments (it contained
See . and the link resolved to ../trailhead/Route.html). The layout: front matter key of the
documentation’s example refers to layouts that have to exist: asking for layout: static-site-main in a site without that
layout stopped the build with No layouts named static-site-main in Map(), so leave the key out unless you supply the layout.
Versioned documentation
-versions-dictionary-url names a JSON document that maps version labels to the index.html of each published version; every
build points to the same file, so publishing a new version means uploading the site and adding one entry, without rebuilding
the old versions. The sites then show a version selector.
Snippet compiling
Documentation examples rot: the API changes and the example in the Markdown no longer compiles. Scaladoc can compile the
scala code blocks it finds, in static pages and in Markdown doc comments, and fail the build. It is off by default and is
turned on with -snippet-compiler. The value is a list of [path=]flag entries, where the path is a prefix of the source file
(a path with no prefix is the default) and the longest matching prefix wins.
| Flag | Effect |
|---|---|
|
check that the snippet compiles |
|
do not check |
|
check that the snippet does not compile |
|
additionally check |
The first example, from the Scaladoc manual, is -snippet-compiler:docs=compile,library/src=compile,library/src/scala/quoted=nocompile.
A single snippet overrides the setting with a flag in its info string, after the scala language tag: sc:nocompile or sc:fail.
Context, hiding and reuse
A snippet in a doc comment is compiled inside the documented entity, so it can use the members of the class without
qualifying them. A snippet that needs an earlier definition names it with sc-name: and the later one pulls it in with
sc-compile-with:; the code of the first is compiled with the second, and may only come from above:
```scala sc-name:model
case class Pin(id: Int)
```
```scala sc-compile-with:model
val p = Pin(1)
```
A snippet marked sc-hidden together with sc-name is compiled and available for inclusion but not shown. A hidden preamble
inside a visible snippet can be written between //{ and //} lines, which the page shows collapsed.
The snippets of the sample library compile; the check is the same command as before with the flag, and it prints nothing when all snippets pass:
$ scaladoc -d site -project trailhead -snippet-compiler:compile classes
With the example snippet in the Route comment changed to ridge.shorter(5).name, the run fails with the position in the source
file, in the doc comment where the snippet lives:
$ scaladoc -d site3 -project trailhead -snippet-compiler:compile classes2
-- Error: src2/Route.scala:15:18 -----------------------------------------------
15 | * ridge.shorter(5).name
| ^
| Found: (5 : Int)
| Required: trailhead.Route
1 error found
Expected errors and warnings: // error and // warn (3.9)
Before 3.9 a sc:fail snippet only claimed that it failed somewhere. With the test modifier (compile+test, or
fail+test for the per-snippet form), the lines that must fail are marked, and the compiler checks that the markers and the real
diagnostics agree exactly: a marker is matched on its line number and its severity, the text after it is a free explanation, an
unmarked error or warning fails the run, and a snippet with no markers must compile cleanly. // error and // warn can appear
twice on one line when two diagnostics are expected.
A page with two snippets:
# What must not compile
A snippet that has to fail, with the failing line marked:
```scala sc:fail
val n: Int = "ridge" // error: a String is not an Int
```
A snippet that compiles with one expected warning:
```scala
enum Surface:
case Dirt, Gravel
def grade(s: Surface): Int = s match // warn: not exhaustive
case Surface.Dirt => 1
```
Built with scaladoc -d site -project trailhead -siteroot docs3 -snippet-compiler:compile+test classes, it passes without any
output: the type error and the exhaustivity warning are exactly the diagnostics the markers announce. Adding a third, unmarked,
mistake to the same page turns the build red:
```scala
val m: Int = "ridge"
```
No expected errors marked in snippet -- use // error
actual error count: 1
-- Error: docs3/_docs/errors.md:22:13
22 |val m: Int = "ridge"
| ^
| Unexpected error on line 22
No expected errors marked in snippet -- use // error
actual error count: 1
With the plain compile flag, the same file reports the compiler error itself, and the warning on the exhaustivity snippet is
accepted because compile does not look at warnings:
-- Error: docs3/_docs/errors.md:22:13
22 |val m: Int = "ridge"
| ^
| Found: ("ridge" : String)
| Required: Int
A snippet that was marked sc:fail and compiles is also reported, as Snippet should not compile but compiled succesfully
(the misspelling is the tool’s; this message is quoted from the Scaladoc manual and was not run here). Snippets in comments that
use the legacy wiki syntax are not checked.
The compiler diagnostics that make good // error examples, and the options that surface them, are in
Compiler Options, Linting & Errors.
Type-based search
The search box of a Scaladoc site searches by name. If the query contains =>, it is treated as a type signature and run by
the Inkuire search engine, a JavaScript worker that works in the browser, so there is no server. You type the shape of what you
want and the engine finds the methods and fields with that shape. For the extension method in the sample, a query of the form
Route => Double finds miles. The accepted queries from the manual:
| Query | Finds |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
The grammar is a curried function type of Scala 3, with some rules: intersections, unions and function types in parentheses;
=> Int to find parameterless members and fields; _ as a wildcard; a single letter, or a letter and a digit, is a type
variable, and others are declared as [AVariable] => AVariable => AVariable. Receivers count as the first argument and
argument lists are curried, so Num => Int => Int => Int finds def a(i: Int, j: Int), def b(i: Int)(j: Int) and a
val of function type; type aliases are expanded on both sides.
The index is generated only on request, with -Ygenerate-inkuire. For the sample library it wrote inkuire-db.json, and the
functions in it include the ones from this page:
$ scaladoc -d site6 -project trailhead -Ygenerate-inkuire classes
$ ls site6 | grep -i inkuire
inkuire-db.json
$ python3 -I -c "import json; print([f['name'] for f in json.load(open('site6/inkuire-db.json'))['functions']])"
['Route.parse', 'Route$package.miles', 'Route.named', 'shorter', 'km', 'name', 'describe']
The query itself runs in the browser, and the search page was not opened for this page: the claims above about how queries match come from the Scaladoc manual. The feature is documented as still in development.
Source: Scaladoc: Type-based search.
Settings reference
The options that matter in daily use, from the Scaladoc settings page:
| Setting | Purpose |
|---|---|
|
name and version, shown at the top left (aliases of the Scala 2 |
|
logo (with an optional |
|
|
|
enable the |
|
show |
|
also document private and protected-private members |
|
the directory with |
|
links from members to their source in a repository, pinned to a revision |
|
links to other libraries' documentation |
|
links to GitHub, Discord and similar |
|
leave packages or top-level classes out |
|
a file with the text of the root package |
|
adds canonical URLs for search engines |
|
the JSON file with all published versions |
|
the snippet checks above |
|
extra sbt configuration for the snippets run on Scastie |
|
build the side menu in the browser; recommended for 1000 pages and more |
|
silence unresolved-link warnings |
|
generate the type-based search index |
scaladoc -help prints the full list for the version in use.
Comparison with Javadoc, Groovydoc and KDoc
| Concern | Scaladoc | Javadoc | KDoc (Dokka) |
|---|---|---|---|
Comment syntax |
Markdown by default; wiki syntax still read |
HTML, with |
Markdown |
Input |
TASTy files of compiled code |
source files |
source files |
Links |
|
|
|
Tags |
|
|
|
Static pages and blog |
built in ( |
|
separate Dokka configuration |
Checked examples |
snippet compiler, with |
none built in (doclint checks the HTML) |
none built in |
Search |
by name, or by type signature with Inkuire |
by name |
by name |
The Javadoc column is covered in Javadoc. A pure Java project should use Javadoc.
Pitfalls
-
Compile first. Scaladoc reads TASTy. Pointing it at an empty directory produces an empty site without an error.
-
-groupsis off by default. A page with@grouptags and no-groupsshows the tags nowhere, and the members are listed in the default sections. -
The margin. A comment whose lines do not share one left margin loses its indentation-sensitive parts (lists, code blocks). Pick one of the three styles of the style guide.
-
$namein text. A dollar sign followed by a name is expanded when an@defineof that name is in scope. A literal dollar in an example, such as$HOME, needs$$or a code block that avoids the macro. -
Links are warnings. An unresolved link does not fail the build. Read the warnings in CI;
-Werrordid not turn them into a failure in the test above. -
Snippets and the default
docsdirectory. Scaladoc takes./docsas the site root by default. Running it from a project that has adocsfolder for something else builds that folder as a site, and with-snippet-compilercompiles its code blocks, which is how an unrelated file’s error appeared in the middle of the checks while writing this page. -
titlein the home page front matter is overridden by the project name, with a message. -
The type-based search is off in the default build. Generate the index with
-Ygenerate-inkuire.
Where the books are outdated
Material from the Scala 2 era describes Scaladoc 2, with wiki syntax as the only syntax, @usecase and @contentDiagram
as the way to simplify a signature and draw a diagram, no static sites, no blog and no snippet checking. The Scala 3
Scaladoc is a different program: Markdown first, definition links with package and this, the _docs and _blog site,
type-based search, and the snippet compiler. The // error and // warn markers are newer still, from 3.9; older descriptions
of sc:fail say it only checks that the snippet fails somewhere. Tutorials that tell you to run scaladoc on source files
should be read with the TASTy requirement in mind: the Scala 3 tool needs the compiled output.