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 scaladoc launcher in the distribution

scaladoc -d site -project trailhead -project-version 1.0.0 classes

Scala CLI

scala doc . -o site -f

sbt

doc task, with options in Compile / doc / scalacOptions

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.)

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

@constructor

documents the primary constructor, which in Scala is part of the class definition

@param name

a value parameter of a method, constructor or class; one per parameter

@tparam name

a type parameter of a method, constructor or class

@return

the return value of a method; one per method

@throws Exception

an exception that the method or constructor may throw

@see

a link to another entity or an external document

@note

a pre- or post-condition or another restriction

@example

example code or related documentation

@author, @version, @since

provenance; @author is shown only when the -author option is given

@deprecated

marks the entity deprecated, with the replacement and the version or date

@group, @groupname, @groupdesc, @groupprio

member groups; shown only with -groups (see below)

@define name text

defines $name for the other comments of the same source file

@inheritdoc

explicit inheritance of the comment of the overridden member

@syntax markdown|wiki

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 (an object, a val, a given) 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.

  • package stands for the enclosing package and this for 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.

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

compile

check that the snippet compiles

nocompile

do not check

fail

check that the snippet does not compile

compile+test, fail+test

additionally check // error and // warn markers (3.9)

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.

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

List[Int] => (Int => Long) => List[Long]

map

(A, B) => A

_1

Set[Long] => Long => Boolean

contains

String => Int => Char

apply

(Int & Float) => (String | Double)

toDouble, toString

F[A] => Int

length

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.

Settings reference

The options that matter in daily use, from the Scaladoc settings page:

Setting Purpose

-project, -project-version

name and version, shown at the top left (aliases of the Scala 2 -doc-title and -doc-version)

-project-logo, -project-footer

logo (with an optional _dark variant) and footer text

-comment-syntax

markdown (default) or wiki

-groups

enable the @group tags

-author

show @author

-private

also document private and protected-private members

-siteroot

the directory with _docs, _blog, _layouts and _assets; default ./docs

-source-links, -revision

links from members to their source in a repository, pinned to a revision

-external-mappings

links to other libraries' documentation

-social-links

links to GitHub, Discord and similar

-skip-by-id, -skip-by-regex

leave packages or top-level classes out

-doc-root-content

a file with the text of the root package

-doc-canonical-base-url

adds canonical URLs for search engines

-versions-dictionary-url

the JSON file with all published versions

-snippet-compiler

the snippet checks above

-scastie-configuration

extra sbt configuration for the snippets run on Scastie

-dynamic-side-menu

build the side menu in the browser; recommended for 1000 pages and more

-no-link-warnings

silence unresolved-link warnings

-Ygenerate-inkuire

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 {@code} and {@link} inline tags

Markdown

Input

TASTy files of compiled code

source files

source files

Links

[[pkg.Class.member]]

{@link Class#member}

[Class.member]

Tags

@param, @tparam, @return, @throws, @see, @note, @example, plus groups and @define

@param, @return, @throws, @see, @since, @deprecated

@param, @return, @throws, @see, @sample

Static pages and blog

built in (_docs, _blog)

doc-files and overview files

separate Dokka configuration

Checked examples

snippet compiler, with // error and // warn markers

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.

  • -groups is off by default. A page with @group tags and no -groups shows 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.

  • $name in text. A dollar sign followed by a name is expanded when an @define of 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; -Werror did not turn them into a failure in the test above.

  • Snippets and the default docs directory. Scaladoc takes ./docs as the site root by default. Running it from a project that has a docs folder for something else builds that folder as a site, and with -snippet-compiler compiles its code blocks, which is how an unrelated file’s error appeared in the middle of the checks while writing this page.

  • title in 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.