javajavadocjava-record

Avoiding Javadoc duplication in Java records


The new Java record is supposed to cut down on boilerplate. I can quickly create an immutable FooBar class with foo and bar components without worrying about local variables, constructor value copying, and getters, like this:

/**
 * Foo bar record.
 */
public record FooBar(String foo, String bar) {
}

Of course I want to document what the record components are, so they will show up in the generated Javadocs! So I add this:

/**
 * Foo bar record.
 * @param foo That foo thing; cannot be <code>null</code>.
 * @param bar That bar thing; cannot be <code>null</code>.
 */
public record FooBar(String foo, String bar) {
}

That works nicely—the record components show up in the documentation.

Except that for almost 100% of records, as a good developer of course I need to validate the foo and bar to ensure they are non-null, right? (Right.) Records allow this easily:

/**
 * Foo bar record.
 * @param foo That foo thing; cannot be <code>null</code>.
 * @param bar That bar thing; cannot be <code>null</code>.
 */
public record FooBar(String foo, String bar) {

  /** Constructor for argument validation and normalization. */
  public FooBar {
    Objects.requireNonNull(foo);
    Objects.requireNonNull(bar);
  }

}

And now, since I am using -Xdoclint:all, I get a warning because the construct doesn't document its (implicit) parameters:

[WARNING] Javadoc Warnings
[WARNING] …/FooBar.java:xx: warning: no @param for foo
[WARNING] public FooBar {
[WARNING] ^
[WARNING] …/FooBar.java:xx: warning: no @param for bar
[WARNING] public FooBar {
[WARNING] ^
[WARNING] 2 warnings

If you say "just turn off -Xdoclint:all", you're missing the point. The warning is valid, because indeed no documentation shows up for the constructor in the generated Javadocs! If there is a constructor, there should be documentation for the parameters. So I'm forced to do the old copy and paste:

/**
 * Foo bar record.
 * @param foo That foo thing; cannot be <code>null</code>.
 * @param bar That bar thing; cannot be <code>null</code>.
 */
public record FooBar(String foo, String bar) {

  /**
   * Constructor for argument validation and normalization.
   * @param foo That foo thing; cannot be <code>null</code>.
   * @param bar That bar thing; cannot be <code>null</code>.
   */
  public FooBar {
    Objects.requireNonNull(foo);
    Objects.requireNonNull(bar);
  }

}

Wait, what happened to cutting down on boilerplate? The boilerplate seems almost as bad as before, if not worse, because now it's complete duplication. Sure, maybe the semantics are slightly different, so maybe I could tweak the wording. Maybe one should say "can never be null" and the other should say "throws an exception if null", but really, this is not ideal.

Javadoc isn't this unintelligent when I'm overriding methods. If I leave off the Javadoc for a method with an @Override annotation, then Javadoc copies over the documentation from the overridden class or interface. And if I decide I want to tweak the documentation, I even have a {@inheritDoc} Javadoc tag that allows me to copy the documentation from the method I'm overriding.

What's the solution here? Is there a way I can tell Javadoc to use the main record parameter documentation for the constructor or vice versa? Is there some Javadoc tag I can use to make it happen automatically? Because the situation as-is is very far from ideal.


Solution

  • I just filed an OpenJDK improvement request for this. The request is now public at JDK-8309252, although it's not clear if they have verified that the Javadoc comments are copied over to an explicit record constructor, or if the warnings have merely been removed. (I sent a follow-up query but have not yet received any reply or ticket update.)

    Here is an excerpt from my request:

    Please improve Javadoc so that if a constructor is provided for a record, for each missing @param Javadoc will use the @param provided in the record description. This is analogous to how Javadoc already copies method API documentation for a method @Override if no documentation is given.

    An analogous situation which Javadoc (now) handles correctly is when overriding methods. A developer may leave off documentation for a method annotated with @Override, and Javadoc will copy over the documentation from the overridden class or interface. In this case there is no need for an @Override annotation, as the semantics are implicit by the context.

    Javadoc provides an {@inheritDoc} mechanism for when the developer wants to duplicate the documentation for an overridden method and then add to it. Perhaps Javadoc might provide a {@defaultDoc} or {@recordDoc} or some similar mechanism to add to the record-level documentation.

    Still by default, if no documentation at all is provided for a custom constructor, Javadoc should copy over the record-level @param documentation as it does already if no custom constructor is provided, and emit no doclint warning.

    In the meantime, for a workaround, it appears (see JDK-8275351) that in Java 18 I may be able to suppress the warnings using @SuppressWarnings("doclint:missing"). I have not verified this. I'll update this answer later this year when I move to Java 21 after it is released.

    Update 2023-11-23: I have confirmed that Java 21 brings an improvement. A warning is emitted if the record fields themselves are not documented, but no warning is generated if a validating constructor is present, whether or not it has a Javadoc comment. Unfortunately as Holger mentioned, the record-level field documentation is not copied over (reflecting the unfinished state of JDK-8309252). Instead, boilerplate phrases to the effect of "the value for the foo record component" is inserted. If main documentation is provided for the validating, I still gate "warning: no comment". And if I do provide a main description for the validating constructor, no field documentation is copied at all. At least the suppression of the warning is an improvement.