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
| Release | Status | Spec |
|---|---|---|
| Java 22 and earlier | Not available — /// is just a line comment | — |
| Java 23 | Final feature, no preview flag | JEP 467 |
| Java 25 LTS | Available 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 want | HTML form | Markdown 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.
Links: brackets instead of {@link}
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:
- Leading whitespace and the three
/characters are removed from each line. - Lines are shifted left until the non-blank line with the least indentation has none left.
- 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.