Documentation comments have used HTML since 1995. That is why a three-item list in a Javadoc comment still costs you <ul>, <li>, and a stray <p> you have to remember to open but never close.

Java 23 adds a second dialect. Write the same comment as adjacent /// lines and the content is parsed as Markdown instead of HTML.

Same tool, same generated pages, different source syntax. This is Markdown Documentation Comments, delivered as a final feature by JEP 467.

/// Converts between temperature scales.
///
/// - Input must be a finite `double`.
/// - Rounding is the caller's problem.
public final class Temperature { }

The problem /// solves

Here is an ordinary class comment written the traditional way. Nothing is wrong with it — it is just a lot of typing for three sentences and a list.

/**
 * Converts between temperature scales.
 * <p>
 * Values are in degrees, not kelvin. Use {@link #toFahrenheit(double)}
 * for the reverse direction.
 * <ul>
 * <li>Input must be a finite {@code double}.</li>
 * <li>Rounding is the caller's problem.</li>
 * </ul>
 */
public final class Temperature { }

The same comment in Markdown form drops every HTML element and both inline tags:

/// Converts between temperature scales.
///
/// Values are in degrees, not kelvin. Use [#toFahrenheit(double)]
/// for the reverse direction.
///
/// - Input must be a finite `double`.
/// - Rounding is the caller's problem.
public final class Temperature { }

A blank /// line is the paragraph break. - starts a list item. Backticks give you monospace. Square brackets give you a link. The JEP’s own motivation cites a JDK-wide analysis where over 95% of inline tag uses were code fragments and links — exactly the two things Markdown makes shortest.

When it shipped

ReleaseStatusSpec
Java 22 and earlierNot available — /// is just a line comment—
Java 23Final feature, no preview flagJEP 467
Java 25 LTSAvailable on the current LTS—

Note: There was no preview round; the feature landed final in Java 23 and works on any JDK from 23 onward. It is a javadoc and Compiler Tree API change, not a language change — javac already treated /// as a comment, so the grammar did not move.

Mental model

Three rules cover most of what you need:

  • A run of adjacent lines each starting with /// forms one documentation comment.
  • The content is CommonMark, plus GFM pipe tables and an extended link form for program elements.
  • All Javadoc tags still work, and traditional /** */ comments stay valid.
You wantHTML formMarkdown form
Paragraph break<p>blank /// line
Monospace text{@code x}`x`
Emphasis<em>x</em>_x_
Bullet list<ul><li>- item
Link to an element{@link List}[List]
Link with own text{@linkplain List a list}[a list][List]

Ordinary // comments are not documentation. Only the three-slash form is, and the third slash is what echoes the extra * in /**.

Why not Markdown inside /** */

The JEP gives two concrete reasons. A block comment cannot contain */ (JLS §3.7), which rules out samples holding a /* */ comment, a glob, or a regex with those characters. And the leading * on continuation lines is optional in traditional comments, which collides with Markdown constructs that themselves start with * — emphasis, list items, thematic breaks.

/// The sample below would be illegal inside a `/** */` comment:
///
///     var glob = "src/*/*.java";
public void scan() { }

Lab: run javadoc on a tiny type

Confirm your toolchain first. You need 23 or newer; the doclet version in the build log is the number that matters.

java -version
javadoc --version

Create one file, Temperature.java, using /// for both the class and the method.

/// Converts between temperature scales.
///
/// Values are in degrees, not kelvin. See [#toFahrenheit(double)]
/// for the only conversion this class performs.
///
/// - Input must be a finite `double`.
/// - Rounding is the caller's problem.
public final class Temperature {

    /// Converts Celsius to Fahrenheit.
    ///
    /// @param celsius the temperature in degrees Celsius, or `NaN`
    /// @return the same temperature in degrees Fahrenheit
    public static double toFahrenheit(double celsius) {
        return celsius * 9 / 5 + 32;
    }
}

Compile it before documenting it. To javac these are plain line comments, so a compile error here means your source is broken, not your docs.

javac -d classes Temperature.java
javadoc -d out Temperature.java

The build log looks the same as it always has, and the doclet version tells you the parser you got:

Loading source file Temperature.java...
Constructing Javadoc information...
Creating destination directory: "out/"
Building index for all the packages and classes...
Standard Doclet version 23.0.2+7
Building tree for all the packages and classes...
Generating out/Temperature.html...
Generating out/package-summary.html...

Now verify that the Markdown actually became HTML rather than literal text. Two greps are enough — one for the list item, one for the bracket link.

grep -o '<code>double</code>' out/Temperature.html
grep -o 'href="#toFahrenheit(double)"' out/Temperature.html

If both print a match, the backticks became <code> and [#toFahrenheit(double)] became a real anchor. If neither matches and the page shows a literal - Input must be..., you are on a JDK older than 23 and your comment was skipped as an ordinary line comment.

Enclose a normal Javadoc reference in square brackets. The text is derived from the element and rendered in monospace, exactly like {@link}.

/// - a module [java.base/]
/// - a package [java.util]
/// - a class [String]
/// - a field [String#CASE_INSENSITIVE_ORDER]
/// - a method [String#chars()]
public void referenceEveryKind() { }

Use [text][element] when you want your own wording. That form is equivalent to {@linkplain}, so it renders in the surrounding font and accepts markup in the text. Ordinary Markdown URL links work too.

/// - [the `java.base` module][java.base/]
/// - [a method][String#chars()]
/// - the rules in [JEP 467](https://openjdk.org/jeps/467)
public void referenceWithText() { }

Escape square brackets inside a reference. Array parameters are where this bites: write [String#copyValueOf(char\[\])], not [String#copyValueOf(char[])].

Tables: GFM pipes instead of <table>

Simple pipe tables from GitHub Flavored Markdown are supported, which covers most parameter and status grids.

/// | Scale      | Freezing | Boiling |
/// |------------|----------|---------|
/// | Celsius    | 0        | 100     |
/// | Fahrenheit | 32       | 212     |
public void scales() { }

Note: Captions and other accessibility features are not part of the supported table syntax. When you need them, the JEP still recommends an HTML table — and HTML remains legal inside a /// comment.

Tags keep doing the structural work

Markdown replaces markup, not tags. Block tags still declare parameters, returns, and exceptions; their content is now parsed as Markdown.

/// Looks up a reading by station id.
///
/// {@inheritDoc}
/// In addition, this implementation consults [#cache()].
///
/// @param id the station id, or `null` for the default station
/// @return the reading, or `null` if the station is unknown
/// @throws IllegalStateException if the cache is closed
public Reading find(String id) { return null; }

User-defined tags work as well. The JDK’s own docs use {@jls ...}, @implSpec, and @implNote inside /// comments with no extra configuration.

{@inheritDoc} crosses formats. A /// comment can inherit from a /** */ supertype comment and the other way around, so you can migrate one declaration at a time.

interface Base {
    /** A method. */
    void m();
}

class Derived implements Base {
    /// {@inheritDoc}
    public void m() { }
}

Code samples inside ///

Inside code spans and code blocks, @... and {@...} lose their tag meaning and are literal text. That is why an annotation in a sample no longer needs escaping.

/// Both forms below are literal, not tags:
///
/// The span `{@inheritDoc}` stays as written, and so does
/// this fenced block:
///
/// ```java
/// /** A traditional comment inside a sample — legal now */
/// @Override public void m() { }
/// ```
public void sample() { }

The first word of a fence info string becomes a CSS class in the generated HTML, which is what highlighters such as Prism and diagram renderers such as Mermaid hook into. Ship the library with javadoc --add-script.

Whitespace is significant

Markdown cares about spaces, so the comment content is derived carefully. Three steps, in order:

  1. Leading whitespace and the three / characters are removed from each line.
  2. Lines are shifted left until the non-blank line with the least indentation has none left.
  3. Any remaining leading whitespace and all trailing whitespace is preserved.

Step 3 is the one to internalize. Extra leading spaces mean an indented code block or a list continuation; trailing spaces mean a hard line break.

/// Usage:
///
///     var f = Temperature.toFahrenheit(21.0);
///
/// The four spaces above make an indented code block.
public void usage() { }

The JEP compares step 2 to String.stripIndent(). The practical consequence: indent every line of the comment the same way. One under-indented /// line resets the baseline for the whole comment and your code blocks collapse into prose.

Gotchas

A blank line inside the comment must itself start with ///. A truly empty line ends the comment.

/// This is the comment for the declaration below.
/// It has ...
///
/// ... a blank line in the middle.
public void kept() { }

If you leave a genuinely empty line in the middle, you get two comments — and only the last one documents the declaration. The earlier one becomes a dangling comment and is silently discarded.

/// Discarded: a dangling comment.

/// This is the comment for the following declaration.
public void m() { }

The same split happens if any comment that does not start with /// sits between two /// comments — including an ordinary // TODO line.

An unclosed code span is the other quiet failure. In a traditional comment, {@code abc produces a diagnostic and a visible invalid @code marker in the page. In Markdown, an unmatched backtick is specified to be literal text, so nothing warns you — the page just shows a stray backtick.

Headings get re-levelled by context, which is usually what you want but surprises people once. A level 1 Markdown heading in a class, package, or module comment renders as level 2 in the page; the same heading in a field, constructor, or method comment renders as level 4. HTML headings are left exactly as written.

Mixed comment styles are per declaration, not per file. That is fine and intended, but a file that alternates for no reason is harder to read than one that picks a side.

Beyond source comments

Markdown files in doc-files subdirectories are processed like the HTML files that used to live there, Javadoc tags included, and the top-level overview file can be Markdown too.

javadoc -d out -overview overview.md Temperature.java

Note: The page title comes from the first heading. YAML metadata blocks — the Pandoc-style front matter you may expect — are not supported, so use a heading, not a title: key.

Cheat sheet

Java 23 / JEP 467: final, no preview flag, javadoc + Compiler Tree API

Comment form:     adjacent lines each starting with ///
Dialect:          CommonMark + GFM pipe tables + all javadoc tags

Paragraph:        blank /// line          (no <p>)
List item:        - item                  (no <ul>/<li>)
Monospace:        `text`                  (was {@code text})
Emphasis:         _text_                  (was <em>)
Element link:     [String#chars()]        (was {@link ...})
Labelled link:    [a method][String#chars()]  (was {@linkplain ...})
Escape brackets:  [String#copyValueOf(char\[\])]

Tags:             @param / @return / @throws / {@inheritDoc} / custom tags work
Literal zones:    @... and {@...} are NOT tags inside code spans / fences

Whitespace:       strip /// -> shift left to least-indented line -> keep the rest
Blank line:       must start with /// or the comment ends
Empty line:       splits comments; earlier ones are dangling and dropped

Do:

  • Use /// for new comments where lists, tables, and code samples dominate.
  • Reach for [Element] and backticks before {@link} and {@code}.
  • Keep @param, @return, and @throws — Markdown has no replacement.
  • Indent every line of one comment identically.
  • Read the generated HTML after converting a comment.

Don’t:

  • Bulk-convert existing comments; JEP 467 makes automated conversion an explicit non-goal.
  • Expect a plain // line or a broken /// run to be documentation.
  • Leave a genuinely empty line inside a /// comment.
  • Assume a mistyped code span will be reported — it will not.

Wrap-up

Markdown documentation comments give you a second, shorter way to say the same things: adjacent /// lines, CommonMark with GFM tables, brackets for element links, and every Javadoc tag you already use. Nothing is deprecated — /** */ keeps working, {@inheritDoc} bridges the two forms, and the generated pages come out the same shape either way.

Start with the comments that hurt most, the ones full of <ul>, <p>, and {@code}. Convert them one declaration at a time and check the generated HTML before you commit.