The AsciiDoc Documentation Generator Antora

A (heavily edited) German version of this article has been published in iX 10/2024.

Table of Contents

Abstract
Introduction
AsciiDoc as a Lightweight Markup Language
Showcase: What Antora Looks Like
Getting Started with the Eclipse Jetty Example
Docs as Code Par Excellence: What Antora Has to Offer
More Than Just Gimmicks: The Playbook
Working with the Content
Nothing Lasts Forever: Versioning Documentation Correctly
Names and Versions Combined: Component Versions
Everything in Its Place: The Antora Directory Structure
Linking with Resource IDs
Completing the Picture: Adding Custom Images and Kroki Diagrams
Conclusion
References
Jetty Links

Abstract

AsciiDoc is excellent for individual documents. However, it becomes more challenging when entire documentation needs to be consistently structured and maintained across multiple repositories. That’s exactly what Antora is for: a documentation generator for static websites whose content is based on AsciiDoc.

Introduction

Software documentation is important—despite all the lazy excuses. Lightweight markup languages and Docs as Code approaches are indispensable when it comes to sustainably documenting the various abstraction layers of a software architecture.

Classic markup languages, such as HTML or LaTeX, do have the advantage that, unlike binary formats like those produced by office programs, they reveal all “settings” and “parameters” and thus are visible to the human eye. However, they also have the disadvantage of often being quite difficult to read and even more difficult to write.

This is where so-called lightweight markup languages come in. They are characterized by simpler syntax that doesn’t interrupt the writing and reading flow. The best-known example is Markdown, but also the world-famous Wikipedia uses a lightweight markup language (MediaWiki), as does Jira (Jira Formatting Notation).

The English Wikipedia now lists over 20 lightweight markup languages. In [Hei23], I compared them with the aim of finding not just a language for software developers but one for everyone. Anyone still looking for (undeniable) arguments for the necessity of software documentation can find them in [Hei22]. Both articles are available for direct viewing (in English) and downloading (in German).

AsciiDoc as a Lightweight Markup Language

The more flexible a markup language is, the more complex it becomes, as qualitatively shown in the diagram below. With Markdown at the lower end (very limited functionality but very simple) and LaTeX at the upper end of the scale (virtually unlimited functionality but relatively cumbersome), AsciiDoc and reStructuredText emerge as the perfect compromise candidates.

Flexibility vs. complexity

While AsciiDoc meets all the requirements of a general-purpose lightweight markup language, reStructuredText is more at home in the Python world. As a language and with its standard documentation generator Sphinx [Sph] (essentially the counterpart to Antora for AsciiDoc), it is noticeably more geared toward “techies.”

Docs as Code means that documentation is treated like source code. The most important prerequisite for this—namely a text format instead of a binary format—is already fulfilled with AsciiDoc as a lightweight markup language. To edit AsciiDoc documents, the same integrated development environment (IDE), the same version control, the same integration into organizational and technical processes, and the same CI/CD pipeline as for the actual program code can (and should) be used.

For the most common IDEs, there is the AsciiDoc plugin (for JetBrains IDEs and Visual Studio Code) or the Asciidoctor Editor (for Eclipse). For browsers, the Asciidoctor.js Live Preview plugin is recommended. GitHub and GitLab can render AsciiDoc documents directly.

The standard generator for individual AsciiDoc documents is called Asciidoctor [AscDr]. This also represents the reference implementation of the AsciiDoc language and hosts the official language documentation [AscDocLanDoc]. Anyone wanting to learn AsciiDoc—for which there is unfortunately not enough space in this article—should therefore always consult Asciidoctor and not the original AsciiDoc sources. Probably the most concise introduction to AsciiDoc can be found on the Antora website [AntAscDocPri].

Showcase: What Antora Looks Like

Antora provides—although hidden behind a rather unassuming “ticket”—a collection of projects that use Antora to generate their documentation [AntSho]. As everywhere, you’ll find the full spectrum in terms of content, appearance, and quality. If you want to see particularly well-designed Antora documentation (many of which have their own UI), the following projects are recommended for viewing, all of which rank fairly high in the list:

  • Couchbase
  • Neo4j
  • Spring Security
  • Open Liberty
  • Apache Camel

You can also try out how different versions can be selected in the showcased projects—not everywhere, but at least in some sections of the respective documentation.

Antora uses Node.js as its runtime environment [AntRunQui]. To minimize local installations and keep your own system clean, however, in this article it will always be run as a Docker container.

How Antora is set up with Docker is officially described in [AntRunCon]. There, however, you will often find long and unwieldy command-line prompts. In addition, it makes sense to install Kroki right away as a local diagram generator. What Kroki diagrams are all about is explained in the second-to-last section.

As an add-on to this article, I therefore provide you with both a custom-tailored Dockerfile (antora_with_kroki_extension.Dockerfile) and a custom-tailored Compose file (compose_antora_with_kroki.yaml) [DocMinPro]. All explanations in this article refer to this Docker configuration, which you are of course welcome to use for your own Antora projects. I would, however, appreciate attribution and reference.

Getting Started with the Eclipse Jetty Example

Of course, it is possible to set up an Antora project from scratch, but within the scope of an article this is neither didactically useful nor practical for demonstrating the concepts. Therefore, I selected a representative open-source project from the showcase in the previous section on which all the steps can be directly illustrated.

The choice fell on Eclipse Jetty [EclJet]. Jetty is a Java web server and servlet container, maintained by the Eclipse Foundation, and it can be used both privately and commercially under either the EPL-2.0 or the Apache-2.0 license [EclJetLic]. Its documentation—and only this is relevant here—includes both local and external content sources, contains multiple versions, generates diagrams from code, uses a custom UI (although UIs are not covered in this article), requires very few plugins, and can be generated with relatively little effort.

The following step-by-step guide explains how to generate the Jetty documentation with Antora for the first time, using the accompanying Docker configuration.

  1. First, clone the GitHub project of the Jetty website into the local folder jetty_site:

    git clone https://github.com/jetty/jetty.website jetty_site
  2. Then change into the newly created directory; from now on, it will serve as the working directory for all upcoming steps.

    cd jetty_site
  3. Reset the local repository to the state before August 8, 2024:

    git reset --hard 8975f7f

    You should then see the message HEAD is now at 8975f7f add slack notification.

    (Background: In August 2024, plugins were added to the Antora documentation project that significantly complicate generating the documentation locally in a clear, didactic manner.)

  4. Copy the two provided Docker files

    • antora_with_kroki_extension.Dockerfile and
    • compose_antora_with_kroki.yaml

    into the jetty_site directory.

  5. If you are working on Windows:

    Replace the three occurrences of $PWD in the file compose_antora_with_kroki.yaml with the absolute path of your local Jetty directory, though in two different notations. If your checked-out Jetty repository is located, e.g., at C:\Projects\jetty_site, then

    1. replace the first $PWD with C:/Projects/jetty_site (using / instead of \),

    2. replace the second and third $PWD with /c/projects/jetty_site (using /c instead of C:),

    3. remove the :Z, and

    4. enclose the line under volumes: in quotation marks.

    The compose file should therefore look like this in the relevant section:

    volumes:
      - "C:/Projects/jetty_site:/c/Projects/jetty_site"
      working_dir: /c/Projects/jetty_site
  6. The following command downloads the Antora and Kroki images (for the first time), starts the local Kroki server, runs the Antora generator based on the playbook antora-playbook.yml, and shuts down the Kroki server again once Antora is finished:

    docker compose -f compose_antora_with_kroki.yaml up --abort-on-container-exit

    The first run may take about two minutes depending on your internet speed. The message pull access denied for antora_with_kroki_extension, repository does not exist or may require 'docker login' simply means that the image is not yet available locally (and is therefore being downloaded).

  7. The generated documentation can then be viewed in the web browser at [local]/jetty_site/target/site/index.html.

With every change to the Antora configuration or the content, step 6 must be executed again. It is recommended to delete the local output directory target beforehand each time.

To finally remove the containers from the Docker list, the following command is sufficient:

docker compose -f compose_antora_with_kroki.yaml down

Docs as Code Par Excellence: What Antora Has to Offer

Antora collects AsciiDoc files and resources, such as images or code snippets, from one or more local and external Git repositories and generates static HTML pages from them.

Following the DRY principle (“Don’t Repeat Yourself”), Antora allows the reuse of resources, such as code snippets (even directly from the original source code), or elements that need to appear in multiple documents, such as copyrights, disclaimers, or references.

The focus on Git repositories plays to the strengths of developers. Documentation should be located close to the actual source code, and today that is almost always in Git, whether on GitHub, GitLab, Bitbucket, or in a local repository.

The artifacts produced by the Antora generator are purely static websites. They can be viewed offline, uploaded to any web host, or moved between hosts. Since they are independent of content management systems (CMS), both vendor lock-in and the typical security risks of such systems are avoided.

With templates and UIs, websites can be designed consistently and—after a certain initial effort—adapted to a company’s corporate design. Antora already provides a fully functional standard UI, which may not win a design award but more than fulfills its purpose.

Regardless of where the data comes from or where the generated websites are exported to, the linking of resources always follows a platform-independent scheme. After a migration, authors therefore don’t have to deal with inconsistent links, relative paths, or broken references. Antora has its own internal “coordinate system.”

Just as the software being documented has different versions, the corresponding Antora documentation can also contain multiple versions. The appropriate software—and thus documentation—version can easily be selected from a dropdown menu on the website.

Navigation structures on the left side can also be easily implemented using AsciiDoc lists and the references they contain.

More Than Just Gimmicks: The Playbook

For Antora to know what it should do, it first refers to the playbook—a simple configuration file usually written in YAML syntax. The common, though not mandatory, filename is antora-playbook.yml.

The playbook defines, among other things, the following information, many of which are optional:

  • global properties such as the title, base URL, or start page of the generated documentation
  • which local and external repositories, branches, and tags serve as content sources
  • Asciidoctor extensions and AsciiDoc attributes
  • the UI bundle to be used, including layout, style, and page behavior
  • target location and format of the published pages
  • how Antora handles repository updates and its cache

In short: the playbook defines which sources Antora should use, which settings it should apply, and where the finished documentation should be generated.

Some playbook entries are best explained directly in Jetty’s antora-playbook.yml. This file is located in the root directory jetty_site of the Git repository cloned earlier.

site:
  title: Eclipse Jetty
  url: https://jetty.org
  keys:
    google_analytics: G-VS4ZRD6HVM
  • Only title is mandatory.
  • url specifies where the documentation can be accessed once it has been published. It must always be provided—even for possible subdirectories—without a trailing slash /.
  • keys contains various key-value pairs for services, UI, templates, and extensions, with this example showing only one for Google Analytics.
  • start_page (not present in the example) specifies the Resource ID of the start page. How such an ID is structured will be explained further below. If the start page is located at ROOT:index.adoc, its explicit specification can be omitted—as is the case here.
  • robots (also not present here) can contain instructions such as allow or disallow for search engines, similar to the well-known robots.txt file.

Working with the Content

Antora collects the content, i.e., the actual material, from all the local and external Git repositories specified in the playbook. For Antora to actually process the content, there must be a file named antora.yml and a specific directory structure in addition to the exact location (Content Source Root). How exactly these two must look will be explained in the following sections.

content:
  sources:
    - url: .
      branches: HEAD
      start_paths: [home, docs-home, contribution-guide]
    - url: https://github.com/jetty/jetty.project
      branches: jetty-{12,11,10}.0.x
      start_path: documentation/jetty

In the Jetty playbook, essentially two content sources are defined. Each source, whether local or external, must always be a Git repository.

  • The upper url source refers to the local directory . (i.e., jetty_site). With branches, the branch of the Git HEAD (i.e., main) is specified. Here the three local start_paths home, docs-home, and contribution-guide are of interest. The documentation (apart from the following, more extensive external part) is assembled from these three local parts [JL1], [JL2], and [JL3]. More specific local path specifications must always begin with ./ if they are given relative to the location of the playbook (and not to the current working directory).
  • The lower url source refers to the external repository https://github.com/jetty/jetty.project. To avoid any confusion: when cloning the entire Jetty documentation project a few sections earlier, the repository https://github.com/jetty/jetty.website was used, not https://github.com/jetty/jetty.project[…/jetty.project]. https://github.com/jetty/jetty.website[jetty.website] uses sources from https://github.com/jetty/jetty.project[jetty.project] for its documentation. The latter also contains the actual Jetty program code. The documentation is located—in line with the Docs as Code approach—in proximity to the code, namely in the subdirectory documentation/jetty referenced by start_path. Since separate documentation should be generated for each of versions 12, 11, and 10, the three relevant branches are listed in short form (jetty-12.0.x, jetty-11.0.x, jetty-10.0.x) [JL4][JL5][JL6].
output:
  dir: target/site
  • The specification of the output directory with dir is self-explanatory.
  • With clean: true (not present here), the directory can be automatically deleted before regeneration. However, this option should only be used with caution.

Nothing Lasts Forever: Versioning Documentation Correctly

Authors should maintain each documentation version (Component Version) in Git in its own branch (the branches key in the playbook), not with tags. At first glance, this may seem contradictory, since software versions are usually marked with tags. However, documentation must remain editable even after the release and thus “freezing” of the software version—whether for typos, new chapters, or later insights. Often the documentation is not yet fully updated at the time of a release anyway. With tags, it would remain frozen.

Antora does support tags in the playbook. Those who still want to use them should employ dedicated tags independent of software releases, such as docs/1.3.9 instead of release/1.3.9. This way, there is at least a grace period after the software release to complete the documentation, since—as is well known—the two are rarely finished at the same time.

Alternatively, Antora can work with separate version directories (the start_paths key in the playbook), meaning one directory per documentation version. The actual version is then specified in the Component Version Descriptor (antora.yml). More details on this will follow in the section after next.

Names and Versions Combined: Component Versions

Jetty’s playbook refers, among other things, to the large external branch [JL4]. The antora.yml file contained in the Content Source Root begins as follows:

name: jetty
version: '12'
title: Eclipse Jetty
  • title is optional but recommended. It appears in the title of the generated HTML pages.
  • name and version together define a Component Version. Antora consolidates all resources with the same name and version, meaning a Component Version can also consist of multiple sources, each containing its own antora.yml file. As long as name and version are identical, they are considered one and the same. Issues arise only if settings within them conflict or source files collide.

Because it is always the antora.yml file that defines the name and version of a component (and not the playbook, a directory name, or the version number of a Git branch), it is referred to as the Component Version Descriptor.

In the Component Version Descriptor of the external branch for version 11 [JL5], version: '11' appears with the same component name: jetty.

If no versions are to be used for a component—which may make sense for something like an employee handbook—then the reserved value ~ must be set (i.e., version: ~).

A Component Version Descriptor may also contain additional entries, as seen in Jetty’s external branch, such as AsciiDoc attributes (asciidoc.attributes:), navigation (nav:), or configurations for plugins and extensions (ext:). However, these will not be discussed here.

Optionally, you can specify a start_page, as long as it is not index.adoc.

Everything in Its Place: The Antora Directory Structure

Each Content Source Root, i.e., each location declared as content in the playbook, must follow a specific directory structure:

[Content Source Root] ➊
│
├── antora.yml ➋
│
└── modules ➌
    │
    └── ROOT ➍
        │
        └── pages ➎
            │
            └── source_file.adoc

➊ The root of a repository automatically forms a Content Source Root, unless another root (i.e., a subdirectory of the respective repository) is specified in the playbook with start_path or start_paths.

➋ Every Content Source Root must at the top level contain a file named antora.yml, which serves as the Component Version Descriptor (see previous section).

➌ Every Content Source Root must also at the top level (and thus parallel to the antora.yml descriptor file) contain a directory named modules. This modules “container directory” must contain at least one module (i.e., at least one module subdirectory).

➍ It is recommended to use a module named ROOT because it has some special properties, particularly with regard to the start page and linking. You can think of ROOT as an alias for an empty module name.
For small documentation projects, a single module (ROOT) is likely sufficient, whereas larger projects (such as Jetty) can consist of additional modules. In Jetty’s case, these are operations-guide [JL7], programming-guide [JL8], and code, the latter containing only source code snippets (examples) that are included in the pages of other modules.
A module directory must contain at least one family subdirectory.

➎ A family can be understood as the “type” of a resource. The name of the directory determines the family of the files (resources) it contains.

  • pages: AsciiDoc files (with the .adoc extension) from which HTML pages are generated
  • partials: typically, but not necessarily, AsciiDoc files whose contents (“snippets”) are included in other AsciiDoc files, such as common descriptions, terminology, or reference tables
  • examples: source code, terminal output, datasets, log entries, or other non-AsciiDoc files that are inserted, wholly or in part, into pages
  • images: image files in .png, .jpg, .svg, or .gif formats, embedded as images in pages
  • attachments: downloadable files such as .pdf or .zip that are usually referenced in AsciiDoc using xref:

Antora also supports symbolic links, such that, e.g., the examples family directory can directly reference the source code (often a src directory) of a software project.

Navigation files (nav.adoc) are not covered here. In the Jetty example project, however, it is apparent that they mainly consist of lists with cross-references (xref:).

Linking with Resource IDs

Antora automatically assigns a Resource ID to every resource, i.e., every file located in a valid family directory. This ID can be uniquely constructed and reconstructed, making it completely independent of file systems, relative path starting points, URLs, or even whether the resource is stored locally or externally. Resource IDs can be used in AsciiDoc pages everywhere files are referenced—most notably in xref: and image: macros or in include:: directives.

A Resource ID consists of five coordinates, though in practice not all five need to be specified:

version@component:module:family$file
  • version@: Specifies the version of the target resource’s component and must always end with @. If omitted, the latest Component Version is used.
  • component:: Specifies the name of the target resource’s component and must always end with :. If omitted, the current component is used.
  • module:: Same idea, but for the module. The ROOT module can be specified as a kind of “nameless” module with a standalone colon :.
  • family$: Specifies the name of the family directory, without the plural s but with a trailing $, e.g., page$, partial$, example$, image$, or attachment$. For image$ and page$ targets, the family can be omitted—except when embedding images via the xref: macro. In all other cases, the family must be specified.
  • file: Contains the path or filename of the resource and is always interpreted relative to the family directory.

In the Jetty example project, you’ll find many cross-references (sometimes very simple ones) using Resource IDs, such as [JL9] (linking to other documents in the same module), [JL10] (embedding an image from the same module—with no need to specify the image$), or [JL11] (including a code snippet from a Java file in another module). The corresponding references are shown with highlighting in the following listing:

[JL9] xref:protocols/index.adoc#http3[Configure HTTP/3]

[JL10] image::jmc-server-dump.png[]

[JL11] include::code:example$src/main/java/org/eclipse/jetty/docs/programming/client/ClientConnectorDocs.java[tags=typical]

Completing the Picture: Adding Custom Images and Kroki Diagrams

The Antora documentation for Eclipse Jetty includes both local and external content. Local pages are particularly convenient for making quick custom adjustments.

In the previous section, embedding an image didn’t look particularly impressive because the image was included from the same module as the page itself. Slightly more interesting is embedding the same image (namely jmc-server-dump.png) in the local documentation home page [JL12]. There, it must be inserted below image::jetty-logo.svg[…] (i.e., after line 3):

image::jetty:operations-guide:image$jmc-server-dump.png[alt=JMC Server Dump,width=50%]

This insertion links from the local component ROOT to the external component jetty and then into the module operations-guide. Since this is a “regular” image inclusion, the family specification image$—as mentioned in the previous section—is optional, meaning the following variant works just as well:

image::jetty:operations-guide:jmc-server-dump.png[alt=JMC Server Dump,width=50%]

Finally, let’s see how text-based diagrams—also called Diagrams as Code—can be rendered with Antora.

There is a wide range of diagram types that can be described textually in AsciiDoc. Kroki provides a unified API and a web service to render these diagrams efficiently. Those who (understandably) prefer not to have their diagrams rendered externally can also use a local Kroki installation.

A picture is worth a thousand words, and 30 pictures are worth 30,000 words, which is why we’ll skip further explanation and instead refer you directly to Kroki’s extensive example collection, which is well worth exploring [KroExa].

The Docker files provided at [DocMinPro] install the asciidoctor-kroki extension for Asciidoctor (which is used by Antora as its processor) in the […].Dockerfile and start a local Kroki server in the compose_[…].yaml, allowing diagrams to be rendered locally without accessing the kroki.io server. Both Docker files are thoroughly documented.

By default, Jetty’s documentation already contains around 30 textually encoded diagrams. Up until now, however, they haven’t been rendered because the corresponding extension was not yet activated.

To change this, add the following key-value pairs in the asciidoc block (not the antora block!) in the antora-playbook.yml under attributes:

kroki-server-url: http://kroki:8000
kroki-fetch-diagram: true

And under extensions, add the list entry:

- asciidoctor-kroki

The following listing summarizes this section with the necessary adjustments:

asciidoc:
  attributes:
    experimental: ''
    idprefix: ''
    idseparator: '-'
    page-pagination: ''
    kroki-server-url: http://kroki:8000
    kroki-fetch-diagram: true
  extensions:
    - ./lib/feed-block-macro.js
    - ./lib/javadoc-block-macro.js
    - ./lib/jetty-block.js
    - ./lib/skip-include-processor.js
    - ./lib/absolute-path-include-processor.js
    - asciidoctor-kroki

You may have noticed that the Jetty playbook already provides an option in the antora section to register the asciidoctor-kroki extension by setting enabled: true. Jetty also supplies its own JavaScript file for the extension. However, if you want to use your own local Kroki server instead of the external kroki.io server, you still need to add the two kroki- attributes shown above. For custom Kroki projects unrelated to Jetty, the originally described method using the two Docker files and the playbook adjustments is usually simpler and more flexible.

Once the documentation is regenerated after the mentioned adjustments (where it is recommended to always delete the entire target directory beforehand), the whole process takes about a minute longer because several diagrams are newly created. Under [JL13] and [JL14] you’ll find two pages of the documentation that each showcase a series of such diagrams.

The demonstrated diagrams are fairly extensive and cannot easily be edited since their code resides in an external repository. It’s more interesting anyway to create your own diagrams “from scratch” to experience firsthand the simplicity and efficiency of Diagrams as Code. The following two listings contain a block diagram and a UML class diagram, which you can add directly to the local documentation home page from the beginning of section [JL12]. After running Antora again, the two diagrams will appear on the local Jetty start page in the browser.

[blockdiag,width=500px]
....
blockdiag {
  A -> B -> C -> D;
  A -> E -> F -> G;
}
....
Kroki Blockdiagram
[plantuml,height=500px]
....
class Animal
class Dog
class Cat
class Main

Animal <|-- Dog
Animal  Animal
Main ..> Dog
Main ..> Cat
....
Kroki PlantUML Diagram

Conclusion

With the concepts presented here—and perhaps a bit unfamiliar at first—the path is now clear for your first documentation project with Antora. To make getting started even easier, I provide an Antora minimal project for download at [DocMinPro]. Before first use, it only needs to be converted into a local Git repository. A short guide on how to do this is included there as well.

References

[AntAscDocPri]
Antora AsciiDoc Primer, docs.antora.org/antora/latest/asciidoc/asciidoc/
[AntRunCon]
Antora Run in a Container, docs.antora.org/antora/latest/antora-container/
[AntRunQui]
Antora Install and Run Quickstart, docs.antora.org/antora/latest/install-and-run-quickstart/
[AntSho]
Antora Sites Showcase, gitlab.com/antora/antora.org/-/issues/20
[AscDocLanDoc]
AsciiDoc Language Documentation, docs.asciidoctor.org/asciidoc/latest/
[AscDr]
Asciidoctor, asciidoctor.org
[DocMinPro]
Docker Files and Minimal Project for Antora, link.simplexacode.ch/8xdv
[EclJet]
Eclipse Jetty, jetty.org
[EclJetLic]
Eclipse Jetty License, github.com/jetty/jetty.project/blob/jetty-12.0.x/LICENSE
[Hei22]
C. Heitzmann, Javadoc with Style, link.simplexacode.ch/gh32
[Hei23]
C. Heitzmann, Markup Languages in Software Documentation, link.simplexacode.ch/4gwr
[KroExa]
Kroki Examples, kroki.io/examples.html
[Sph]
Sphinx, www.sphinx-doc.org

Jetty Links

Reference Navigation in the Browser Local Git or External GitHub Directory
[JL1] Home [local]/jetty_site/home/
[JL2] Documentation [local]/jetty_site/docs-home/
[JL3] Documentation → Contribution Guide [local]/jetty_site/contribution-guide/
[JL4] Documentation → 12 github.com/jetty/jetty.project/tree/jetty-12.0.x/documentation/jetty
[JL5] Documentation → 11 github.com/jetty/jetty.project/tree/jetty-11.0.x/documentation/jetty
[JL6] Documentation → 10 github.com/jetty/jetty.project/tree/jetty-10.0.x/documentation/jetty
[JL7] Documentation → Jetty 12 | Operations Guide github.com/jetty/jetty.project/tree/jetty-12.0.x/documentation/jetty/modules/operations-guide
[JL8] Documentation → Jetty 12 | Programming Guide github.com/jetty/jetty.project/tree/jetty-12.0.x/documentation/jetty/modules/programming-guide
[JL9] Documentation → Jetty 12 | Operations Guide → Eclipse Jetty How-Tos github.com/jetty/jetty.project/blob/jetty-12.0.x/documentation/jetty/modules/operations-guide/pages/howtos/index.adoc?plain=1
[JL10] Documentation → Jetty 12 | Operations Guide → Troubleshooting github.com/jetty/jetty.project/blob/jetty-12.0.x/documentation/jetty/modules/operations-guide/pages/troubleshooting/index.adoc?plain=1
[JL11] Documentation → Jetty 11 | Programming Guide → Client Libraries → I/O Architecture github.com/jetty/jetty.project/blob/jetty-11.0.x/documentation/jetty/modules/programming-guide/pages/client/io-arch.adoc?plain=1
[JL12] Home [local]/jetty_site/home/modules/ROOT/pages/index.adoc
[JL13] Documentation → Jetty 12 | Operations Guide → Jetty Connectors and Protocols github.com/jetty/jetty.project/blob/jetty-12.0.x/documentation/jetty/modules/operations-guide/pages/protocols/index.adoc?plain=1
[JL14] Documentation → Jetty 12 | Programming Guide → Server Libraries → HTTP Session Management github.com/jetty/jetty.project/blob/jetty-12.0.x/documentation/jetty/modules/programming-guide/pages/server/session.adoc?plain=1

Shortlink to this blog post: link.simplexacode.ch/g7qi2025.10

Leave a Reply