TS-33: Java
This technical standard provides guidelines for writing Java code. It is based on Google’s Java Style Guide, which is widely adopted in the IT industry. Other sources are listed in the references section at the end of this document.
Java code is also subject to the language-neutral technical standards. TS-7: Code Design covers naming, expressiveness, error handling, and concurrency in any language. TS-13: Functional Testing covers test design, and designing code for testability. TS-57: Logging, Monitoring, Observability covers log levels and logging practice.
Terminology
Throughout this document, the following terms have the following meanings.
- Class-like construct means any ordinary class, enum, interface, record, or annotation type.
- Block-like construct means the body of a class, method, or constructor, and also array initializers, static initializers, and other similar constructs.
- Member means any field, method, constructor, or nested class within a class-like construct.
- Comment refers only to an implementation comment, while Javadoc refers to inline API documentation that is encoded as comments.
Source files
Each Java source file SHOULD contain exactly one class-like construct.
Except, in special circumstances, a source file MAY have exactly one top-level public class plus additional class-like constructs that are deliberately tightly coupled to the main class, and that are not shared with any other classes. The top-level public class MUST be the first construct within such a file.
Source files are distinguished by the .java file extension. The name of a source file MUST match the name of the main class-like construct it contains exactly, including letter case.
A source file SHOULD NOT exceed about 2,000 lines. A file of that length is hard to navigate and to review, and usually means its main class has taken on more than one responsibility. But big classes are merely a code smell, not an anti-pattern.
The contents of source files MUST be laid out in the following order, from top to bottom.
- License or copyright notice, if required by the project.
- Package statement.
- Import statements.
- The main class-like construct.
- Other class-like constructs used by the main construct (and not by any other component).
Each section MUST be separated by a single blank line. Consecutive top-level class-like constructs MUST also be separated by a single blank line.
Every Javadoc comment MUST be preceded by a single blank line, except where the comment is the first thing in the file, in which case it MAY start on line 1.
/* Copyright 2026 Example Ltd. Licensed under the Apache License 2.0. */
package com.example.orders;
import static java.util.Objects.requireNonNull;
import java.util.List;
/// An order placed by a customer.
public final class Order {
// …
}
/// A single line of an [Order]. Used only by [Order].
final class OrderLine {
// …
}Every source file except module-info.java MUST have a package statement. The package statement MUST NOT be line-wrapped, and MAY exceed the column limits.
Compact source files (JDK 25 or later), which omit the package statement and the enclosing class declaration, so a small program can be written as bare methods, MUST NOT be used. This is intended for single-file programs and teaching.
Special source files
Two files carry special structures that fall outside the ordinary top-to-bottom layout, defined above.
A package-info.java file holds only a package-level Javadoc comment, package-level annotations, any import statements those annotations need, and the package statement itself. It MUST NOT contain a class-like construct.
/// Order capture and fulfillment. @NullMarked package com.example.orders; import org.jspecify.annotations.NullMarked;
A module-info.java file declares a Java module. It contains the module declaration and its directives, and MUST NOT contain a package statement. The module directives MUST appear in this order: requires, exports, opens, uses, provides. Each directive kind forms its own block, separated from the next by a blank line. Where a requires directive has modifiers, they MUST appear in the order transitive static. The module’s own name follows the rules for package names.
module com.example.myapp {
requires java.sql;
requires transitive com.example.mylib;
requires static com.example.annotations;
exports com.example.myapp.api;
opens com.example.myapp.model to com.example.myapp.persistence;
uses com.example.myapp.spi.Plugin;
provides com.example.myapp.spi.Plugin
with com.example.myapp.plugins.DefaultPlugin;
}Encoding
Source files MUST be encoded using UTF-8.
Source files MUST use Unix-style line endings — the line feed (LF) character, represented by the escape sequence \n. Source files MUST NOT use Windows-style line endings — a carriage return and line feed (CRLF) pair, represented by the escape sequence \r\n.
Whitespace characters written verbatim in source code MUST be limited to the line termination sequence and the horizontal space character. Other whitespace characters MAY be included only in string and character literals, and they MUST be represented by escape sequences rather than literal characters.
Whitespace characters that have a special escape sequence in Java MUST be encoded using that escape sequence.
Escape sequence | Description |
|---|---|
| Insert a tab in the text at this point. |
| Insert a backspace in the text at this point. |
| Insert a newline in the text at this point. |
| Insert a carriage return in the text at this point. |
| Insert a form feed in the text at this point. |
| Insert a space in the text at this point (JDK 15 or later). |
| Insert a single quote character in the text at this point. |
| Insert a double quote character in the text at this point. |
| Insert a backslash character in the text at this point. |
System.out.println("She said \"Hello!\" to me.");Any other whitespace character MUST be encoded as a Unicode escape sequence, eg. \u2004.
System.out.println("\u2004"); // three-per-em spaceWarning
Unicode escape sequences are translated before the source code is parsed, so a Unicode escape for a line terminator — \u000a or \u000d — ends the line wherever it appears, including inside a string literal, and the code does not compile. Use \n and \r instead.
Non-whitespace (ie. printable) characters outside of the ASCII range SHOULD be written as the actual Unicode character (eg. ∞, μ). Alternatively, Unicode escape sequences (eg. \u221e) MAY be used, where doing so improves the overall readability of the code.
String unitAbbrev = "μs";
For every character that is encoded as an escape sequence, except those that are widely recognized (\t, \n, etc.), an end-of-line // comment SHOULD be included to explain the meaning of the escape sequence.
return "\ufeff" + content; // byte order mark
Naming conventions
Spelling
All file names, code, comments, and Javadoc MUST be written in English, and SHOULD use American English spelling.
Identifiers
All identifiers MUST be composed only of ASCII letters and digits and, in a small number of cases, underscores. Thus, each valid identifier is matched by the regular expression \w+.
Special prefixes or suffixes SHOULD NOT, generally, be used on identifiers. For example, do not prefix variables with s_ to indicate that they are static, or prefix interfaces with I to indicate that they are interfaces.
A single underscore (_) MAY be used as an unnamed variable or parameter wherever Java’s unnamed-variable syntax applies (JDK 22 or later), eg. for a local variable, pattern-match binding, or lambda parameter whose value is never read. It signals to the reader that the value is deliberately unused.
try {
port = Integer.parseInt(value);
} catch (NumberFormatException _) {
port = DEFAULT_PORT;
}
stockLevels.forEach((_, quantity) -> checkNotNegative(quantity));
if (shape instanceof Circle(Point center, _)) {
// …
}Naming styles
This standard uses four naming styles.
Style | Form | Example | Used for |
|---|---|---|---|
lowercase | All letters lowercase, with words concatenated and no delimiter. |
| Package names. |
UpperCamelCase | Each word starts with an uppercase letter, and the rest of the word is lowercase. No delimiter. |
| Classes, interfaces, records, enums, and annotation types. |
lowerCamelCase | As UpperCamelCase, except that the first word is entirely lowercase. |
| Methods, fields, local variables, and parameters. |
UPPER_SNAKE_CASE | All letters uppercase, with words delimited by a single underscore. |
| Constants and enum constants. |
Type variables are the one exception, and follow their own form — see Type variable names.
To form a name in any of these styles from a phrase of ordinary words, follow this procedure.
- Transliterate the phrase to plain ASCII (eg. "Müller" to "Mueller") and remove any apostrophes (eg. "Müller’s algorithm" to "Muellers algorithm").
- Split the result into words at space and punctuation boundaries, including hyphens (eg. "non-blocking" becomes "non" and "blocking"). A word that is already written in camel case in common usage SHOULD also be split into its parts (eg. "AdWords" becomes "ad" and "words"). This does not apply to a word whose capitalization follows no convention, such as "iOS", which stays one word.
- Lowercase every word, including acronyms and abbreviations (eg. "URL" becomes "url").
- For UpperCamelCase, uppercase the first letter of every word. For lowerCamelCase, uppercase the first letter of every word except the first. For UPPER_SNAKE_CASE, uppercase every letter. And for lowercase, change nothing.
- Join the words, delimited by an underscore for UPPER_SNAKE_CASE, and with no delimiter for other conventions.
Because acronyms are lowercased before the style is applied, they are treated as ordinary words, eg. UrlConnection and parseXmlDocument, not URLConnection and parseXMLDocument.
Where adjacent digits in a name have correct semantics only when separated, such as a multipart version number, they SHOULD be separated with underscores, eg. guava33_4_6 rather than guava3346.
Where the English spelling of a word is itself ambiguous about hyphenation, prefer the hyphenated version. For example, "nonempty" and "non-empty" are both correct spellings, but we should use the hyphenated form to generate the name checkNonEmpty, which is more readable than checkNonempty.
Phrase | UpperCamelCase | lowerCamelCase | UPPER_SNAKE_CASE |
|---|---|---|---|
"XML HTTP request" |
|
|
|
"new customer ID" |
|
|
|
"supports IPv6 on iOS" |
|
|
|
Package names
Package names MUST be written in lowercase using only letters and digits. Hyphens are not valid in Java identifiers, so they cannot appear in package names. Module names, declared in module-info.java, follow the same rules.
com.example.formvalidator✓com.example.formValidator✗com.example.form_validator✗com.example.form-validator✗ (illegal package name)
The convention of writing package names in lowercase has been established to avoid conflicts with the names of classes and interfaces (identifiers in Java are case-sensitive).
The convention of using a reverse domain name, eg. com.example, for the top-level namespace of a package is also long-established in the Java community. The purpose of this convention is to ensure that package names are unique across different organizations and projects. Since a domain name is unique to the person or organization that owns it, this convention makes package names unique across the global Java ecosystem.
However, for application code that will not be shared with others, the reverse domain name convention is not necessary. In this case, it is better to choose a codename for each software project, which is unique only internally within the organization. Using a codename rather than the application’s brand name helps to decouple the code from the product marketing, allowing product names to be changed independently of their source code.
In choosing codenames for their software projects, organizations may choose a theme, such as the names of planets, cities, or characters from books or movies.
adelaide.webapi.io.controllers.cli adelaide.webapi.io.controllers.http adelaide.webapi.kernel.commands adelaide.webapi.kernel.config adelaide.webapi.kernel.jobs adelaide.webapi.model.entities adelaide.webapi.model.repositories adelaide.webapi.system.dao adelaide.webapi.system.services
Package names may be a mix of plural and singular forms. Use your judgment. The general principle is that the plural form should be used for packages with homogeneous contents, and the singular for packages with heterogeneous contents. Most packages will be containers for software components of the same type (homogeneous collections), and therefore the plural form will be appropriate: "controllers", "documents", "entities", "services", "repositories", etc. An exception would be for collections of components that implement a design pattern identified by an abbreviation such as "dto" or "dao". Appending an "s" to these, while more consistent, would only increase confusion.
Some packages will be containers for diverse components from which a discrete subsystem is composed, such as a GUI or library. For many such packages, the singular form will be more appropriate, eg. "gui", "lib", "util". Similarly, configurations should go in a "config" package.
Class, interface, record, and enum names
Classes and interfaces MUST be named using UpperCamelCase.
Class names are typically nouns or noun phrases. They SHOULD be descriptive and unambiguous, and SHOULD NOT be overly long. Good examples of class names include Character, ImmutableList, PriorityQueue, and UrlConnection.
Classes from which objects are created SHOULD be named using a singular noun or noun phrase, eg. UserService, UserEntity, UserRepository, etc. Where classes are merely containers for static methods and constants, they MAY be named using a plural noun or noun phrase, eg. DataAccessUtilities, ValidationRules, etc.
Interfaces SHOULD follow the same naming conventions as classes, and MAY be nouns or noun phrases, eg. List. An interface that describes a capability MAY instead be named with an adjective, as the Java standard library does with the suffix "able" or "ible", eg. Runnable, Closeable, Serializable, Comparable, Iterable.
Interfaces SHOULD NOT mirror exactly the names of the classes that implement them. This is a code smell, since it suggests the interface is too tightly coupled to a single implementation. But if there really is no better name for an interface, the interface name MAY take the class name plus the suffix "Contract".
/* ✗ The interface only restates its one implementation. */
interface SmtpMailer { … }
class SmtpMailerImpl implements SmtpMailer { … }
/* ✓ The interface names the capability, the class names the mechanism. */
interface Mailer { … }
class SmtpMailer implements Mailer { … }Interfaces SHOULD NOT be prefixed with "I" or suffixed with "Interface". These conventions are common in other languages, such as C#, but are not widely used in Java.
Records MUST also be named using UpperCamelCase. A record’s name SHOULD be a singular noun or noun phrase describing the data the record holds, eg. Point, OrderLine.
Enums MUST also be named using UpperCamelCase, and SHOULD be named in the singular. A variable of an enum type holds one of its constants (a Protocol), not several (Protocols).
enum Protocol { HTTP, HTTPS, FTP }Test classes MUST have a name that ends with Test. If the test covers a single class, the test class name SHOULD be the name of the class being tested, with Test appended, eg. OrderValidatorTest for OrderValidator.
Method names
Methods MUST be named using lowerCamelCase.
Method names SHOULD, typically, be verbs or verb phrases, eg. sendMessage, stop, computeTotal.
Underscores MAY be used in JUnit test methods to separate logical components of the name, with each component written lowerCamelCase, eg. transferMoney_deductsFromSource.
@Test
void transferMoney_deductsFromSource() {
// …
}
@Test
void transferMoney_insufficientFunds_throwsException() {
// …
}Field, variable, and parameter names
Fields, variables (including local variables), and parameters MUST be named using lowerCamelCase.
These SHOULD generally be nouns or noun phrases, eg. index, height, computedValues.
One-character names SHOULD be avoided for fields, variables, and parameters. A single letter tells the reader nothing about what the value is for. The exception is a short-lived throwaway variable whose role is obvious from its immediate context, such as a loop index, eg. i, j, k.
In a public method, where the parameter names form part of the documented API, one-character parameter names SHOULD NOT be used at all.
/* ✗ Single letters say nothing about the values. */ private final int a; private final String m; /* ✓ Each name says what the value is. */ private final int ageInYears; private final String maidenName;
Constant names
A constant is defined here as a static final field whose contents are deeply immutable and whose methods have no detectable side effects. Merely intending to never mutate the object is not sufficient. Examples of valid constants include primitives, strings, immutable value classes, and anything set to null.
Even when final and immutable, local variables are not considered to be constants, and therefore SHOULD NOT be styled as such.
Constants MUST be named using UPPER_SNAKE_CASE.
Constant names are typically nouns or noun phrases.
/* The following are valid constants. */
static final int NUMBER = 5;
static final ImmutableList<String> NAMES = ImmutableList.of("Ed", "Ann");
static final Map<String, Integer> AGES = ImmutableMap.of("Ed", 35, "Ann", 32);
/* Joiner is immutable. */
static final Joiner COMMA_JOINER = Joiner.on(',');
static final SomeMutableType[] EMPTY_ARRAY = {};
/* The following are not valid constants. */
static String nonFinal = "non-final";
final String nonStatic = "non-static";
static final Set<String> mutableCollection = new HashSet<>();
static final ImmutableSet<SomeMutableType> mutableElements = ImmutableSet.of(mutable);
static final ImmutableMap<String, SomeMutableType> mutableValues =
ImmutableMap.of("Ed", mutableInstance, "Ann", mutableInstance2);
static final Logger logger = Logger.getLogger(MyClass.class.getName());
static final String[] nonEmptyArray = {"these", "can", "change"};Type variable names
Type variables SHOULD be named in one of two ways.
- A single capital letter, optionally followed by a single numeral, eg.
T,E,X,T2,T3. - A name in the form used for classes, followed by the capital letter
T, eg.RequestT,FooBarT.
public interface Converter<S, T> {
T convert(S source);
}
public class RpcClient<RequestT, ResponseT> {
// …
}Annotation names
Annotation types are types, and so MUST be named using UpperCamelCase like any other class-like construct, eg. @CheckReturnValue. This is the convention used throughout the Java standard library, eg. @Override, @FunctionalInterface.
An annotation name MAY be a verb phrase (@CheckReturnValue), a noun (@Entity), or an adjective (@Deprecated), depending on its purpose.
Code style
Indentation
Each time a new block or block-like construct is opened, the code MUST be indented by an additional two spaces. When the block ends, the indentation returns to the previous level.
Tab characters MUST NOT be used for indentation. Code editors SHOULD be configured to automatically replace tab characters with spaces. Integration checks SHOULD be configured to fail on them.
Comments and Javadoc MUST be indented to the same level as the code to which they relate.
One statement per line
There MUST be no more than one statement per line.
/* ✗ Two statements on one line. */ count++; total += price; /* ✓ One statement per line. */ count++; total += price;
Line length (column limits)
Lines SHOULD be no longer than 80 characters, including the whitespace used for indentation, and SHOULD NOT exceed 120 characters. A line longer than 120 characters SHOULD be line-wrapped.
Package and import statements MAY exceed the column limits. They MUST NOT be line-wrapped.
Other code MAY exceed the column limits, but only where line-wrapping it would reduce its readability (eg. command lines written in comments) or where keeping within the limits is simply not possible (eg. long URLs in comments).
A character, for the purpose of the column limits, is one Unicode code point, whatever its display width. A line containing full-width characters, which display at twice the width of ASCII, MAY be wrapped earlier than the limits strictly require.
Line-wrapping
The term line-wrapping refers to the practice of breaking a single statement across multiple lines. This is typically done to keep line lengths short, but authors MAY use line-wrapping at their discretion to improve the readability of code, even where the code does not exceed the column limits.
There are no deterministic rules for line-wrapping in Java, but the following guidelines SHOULD be followed.
- Look to refactor the code before line-wrapping is implemented. Can long statements be broken into multiple shorter ones? Can some of the code be extracted into methods?
- Otherwise, prefer to break at a higher syntactic level rather than on lower-level breaks. Examples:
- Break before non-assignment operators, as well as operator-like symbols: the dot separator (
.), the double colons of a method reference (::), the ampersand in a type bound (<T extends Foo & Bar>), and the pipe in a multi-catch block (catch (FooException | BarException e)). - Break after assignment operators. The colon in an enhanced
forstatement is treated the same way. - Never break adjacent to the arrow in a lambda expression or a switch rule, except after the arrow where what follows it is a single unbraced expression.
- Break before non-assignment operators, as well as operator-like symbols: the dot separator (
Commas SHOULD stay attached to the token that precedes them. A method, constructor, or record class name SHOULD stay attached to the opening parenthesis that follows it.
/* ✓ Break before a binary operator, so the operator leads the line. */
boolean eligible = customer.isActive()
&& customer.getAge() >= MINIMUM_AGE
&& !customer.isBlocked();
/* ✓ Break after the assignment operator. */
Map<CustomerId, List<Order>> ordersByCustomer =
orderRepository.findAllGroupedByCustomer();
/* ✗ Break after the binary operator, which hides it at the end of the line. */
boolean eligible = customer.isActive() &&
customer.getAge() >= MINIMUM_AGE;Lambda expressions are wrapped in the same way. The break goes after the assignment operator, or after the arrow where a single unbraced expression follows it.
MyLambda<String, Long, Object> lambda =
(String label, Long value, Object obj) -> {
// …
};
Predicate<String> predicate = str ->
longExpressionInvolving(str);Continuation lines SHOULD be indented by an additional four spaces — double the normal indentation level, so that they stand out from the nested block that follows. A continuation nested inside another MAY be indented further.
Where a chain of method calls is wrapped, each call SHOULD start its own line, beginning with the dot.
/* ✗ Breaks follow the line length, not the calls. */
Iterable<Module> modules = ImmutableList.<Module>builder().add(new LifecycleModule())
.add(new AppLauncherModule()).addAll(application.getModules()).build();
/* ✓ One call per line. */
Iterable<Module> modules = ImmutableList.<Module>builder()
.add(new LifecycleModule())
.add(new AppLauncherModule())
.addAll(application.getModules())
.build();The same applies to the parameter list of a method or constructor declaration that does not fit on one line. Break after the opening parenthesis and put each parameter on its own continuation line.
public String download(
Internet internet,
Tubes tubes,
Blogosphere blogs,
Amount<Long, Data> bandwidth) {
tubes.download(internet);
// …
}A conditional expression that does not fit on one line SHOULD be broken before the ? and before the :, so that each alternative starts its own continuation line.
String label = (count == 1)
? "one item"
: count + " items";Brace style
In Java code, the most common formatting options for braces are the following.
- Kernighan and Ritchie (K&R) style, also known as Egyptian brackets.
- Allman style.
- GNU style.
In the K&R style, the opening brace is placed at the end of the line that begins the compound statement, and the closing brace is placed on a line of its own at the same indentation level as that opening statement.
class Example {
public void method() {
if (condition) {
// …
} else {
// …
}
}
}The Allman style differs from K&R by moving the opening brace to a new line, aligned with the start of the compound statement. This style emphasizes vertical alignment and increases the use of whitespace, arguably improving readability.
class Example
{
public void method()
{
if (condition)
{
// …
}
else
{
// …
}
}
}The GNU style is similar to the Allman style, but each brace is indented halfway between the statement that opens the block and the code inside it.
class Example
{
public void method()
{
if (condition)
{
// …
}
else
{
// …
}
}
}Of these three, the K&R style is the most widely used, and the most widely recommended in Java coding style guides. For consistency with the prevailing industry standard, the K&R convention MUST be used to format braces. The following rules apply.
- No line break before the opening brace, in most cases. The opening brace is placed at the end of the line that begins the block-like construct.
- A line break after the opening brace.
- A line break before the closing brace.
- A line break after the closing brace, but only if that brace terminates a statement or the body of a method, constructor, or named class. Thus,
else,catch,finally, andwhilekeywords go immediately after the closing brace of the preceding block, because these represent a continuation of the preceding block statement.
Authors MAY deviate from the K&R style where doing so improves readability. For example, opening braces MAY be placed on a new line after very long statements, or where blocks are used only to limit the scope of local variables.
Where a closing brace is followed by a comma, semicolon, or other punctuation, that punctuation is placed on the same line as the closing brace.
return () -> {
while (condition()) {
method();
}
};
return new MyClass() {
@Override
public void method() {
if (condition()) {
try {
something();
} catch (ProblemException e) {
recover();
}
} else if (otherCondition()) {
somethingElse();
} else {
lastThing();
}
{
int x = foo();
frob(x);
}
}
};Braces MUST be used with if, else, for, do, and while statements, even when the body of the statement is empty or contains only a single statement.
/* ✗ A second statement added later looks guarded, but is not. */
if (cache.isStale())
cache.refresh();
/* ✓ Braces, even around a single statement. */
if (cache.isStale()) {
cache.refresh();
}For empty block-like constructs, the block SHOULD be written as {} on the same line as the statement that opens the block.
void doNothing() {}This style MAY also be used for empty blocks within multi-block statements. (This is a relaxation of the Google Java Style Guide, which forbids this.)
try {
emptyStack.pop();
fail();
} catch (NoSuchElementException expected) {}Be wary of stray ; turning conditionals into empty statements. In the following example, the trailing ; on line 1 makes the if statement empty (it doesn’t do anything), and the block from lines 2 to 4 always runs. The K&R brace style makes this mistake less likely, because the line ends with { rather than ). For better guards, enable compiler warnings or linter rules that flag empty statements.
if (isValid(input));
{
process(input);
}Vertical whitespace
A single blank line MUST separate consecutive members and initializers of a class-like construct: fields, constructors, methods, nested classes, static initializers, and instance initializers.
The one exception is fields. Logical groupings of fields MAY be created by omitting the blank lines between two or more consecutive fields.
Single blank lines MAY be added wherever doing so improves the readability of the code (eg. using vertical whitespace to separate logical sections of a method) or makes the code’s structure clearer (eg. organizing fields into logical groupings).
A blank line before the first member of a class, or after its last member, is neither required nor forbidden.
public final class Connection {
private final String host;
private final int port;
private Duration timeout = DEFAULT_TIMEOUT;
private int retries = DEFAULT_RETRIES;
public Connection(String host, int port) {
this.host = host;
this.port = port;
}
public void open() {
// …
}
}Multiple consecutive blank lines SHOULD NOT be included in any Java code.
Horizontal whitespace
Besides what is required by the Java language, and apart from within literals, comments, and Javadoc, a single ASCII space character SHOULD appear in the following scenarios, and only in these.
- To separate reserved words such as
if,for, andcatchfrom the opening parenthesis that follows them on the same line. - To separate reserved words such as
elseandcatchfrom the closing curly brace that precedes them on the same line. - Before most opening curly braces, with a couple of exceptions:
@SomeAnnotation({a, b}), andString[][] x = {{"foo"}};. - Between the type and variable of a declaration:
List<String> list. - After
,,;, and:. - After the closing parenthesis of a cast.
- On both sides of binary and ternary operators.
- Around the following operator-like symbols.
- The ampersand in a conjunctive type bound:
<T extends Foo & Bar>. - The pipe for a catch block that handles multiple exceptions:
catch (FooException | BarException e). - The colon (
:) in an enhancedforstatement. - The arrow in a lambda expression:
(String str) → str.length(). - The arrow in a switch rule:
case 1 → "one".
- The ampersand in a conjunctive type bound:
- Before and after a double slash (
//) that begins an end-of-line comment, and between the double slash and the comment’s text or commented-out code that follows. - Between a type annotation and a following
[]or…:String @NonNull [] names.
It follows that no space appears in the following places.
- Around the dot separator (
.) or the double colons of a method reference (String::length). - Between a method name and the opening parenthesis of its argument or parameter list:
length(), notlength (). The space after a keyword and the absence of one after a method name are what tell the two apart. - Between a unary operator and its operand:
-x,!done,i++,--count.
/* ✗ Spaces missing where required, and added where not. */
for(int i=0;i<count;i ++){
total+=( int )values [i];
}
/* ✓ Spaces only where the rules call for them. */
for (int i = 0; i < count; i++) {
total += (int) values[i];
}A space MAY be included inside the curly braces of an array initializer. Both new int[] {1, 2, 3} and new int[] { 1, 2, 3 } are valid.
Authors MAY include additional spaces before end-of-line comments to achieve vertical alignment.
System.out.println(sorted); // [15, 23, 51, 80] System.out.println(unsorted); // [80, 51, 23, 15]
Horizontal alignment of related tokens on multiple consecutive lines MAY be applied where doing so improves readability. But use this cautiously. The trade-off is noisy diffs, because a single line change can trip realignment of tokens across multiple other lines.
There MUST NOT be any superfluous whitespace at the end of lines. It is strongly RECOMMENDED to configure both code editors and automation pipelines to remove trailing whitespace automatically.
Grouping parentheses
Optional grouping parentheses SHOULD be kept unless the expression cannot reasonably be misread without them. Don’t assume readers have Java’s operator precedence memorized.
/* ✗ Correct, but only for a reader who knows && binds tighter than ||. */
if (isAdmin || isOwner && !isLocked) {
// …
}
/* ✓ The parentheses make the grouping explicit. */
if (isAdmin || (isOwner && !isLocked)) {
// …
}The value of a return statement is not an expression that needs grouping, so it SHOULD NOT be wrapped in parentheses, eg. return size;, not return (size);. Parentheses MAY be kept around part of the value where they make it easier to read, such as the condition of a conditional expression: return (size > 0) ? size : defaultSize;.
Programming constructs
Import statements
Wildcard imports, static or otherwise, SHOULD NOT be used. Module imports (import module java.base;, JDK 25 or later) MUST NOT be used either. The reasoning is that imports bring in every public type, which has some drawbacks when importing at a large scale via wildcard or module imports. A reader cannot easily tell where a name comes from, and a new type added to any imported package can make existing names ambiguous.
Imports SHOULD be grouped into the following categories, in this order.
- Static imports.
- Non-static imports.
There SHOULD be exactly one blank line between the two groups to separate them. There SHOULD NOT be any other blank lines between import statements.
Within each group, imports SHOULD be sorted in ASCII order of the imported names. This is not quite the same as sorting the import lines, because . sorts before ;. For example, com.example.Foo sorts before com.example.Foo.Bar by name, but import com.example.Foo.Bar; sorts before import com.example.Foo; as a line.
import static java.util.Objects.requireNonNull; import static org.junit.jupiter.api.Assertions.assertEquals; import com.example.orders.Order; import com.example.orders.OrderLine; import java.time.Instant; import java.util.List; import java.util.Map;
Static imports SHOULD NOT be used for static nested classes. They SHOULD be imported with normal imports.
Import statements MAY exceed the column limits, and MUST NOT be line-wrapped.
Variable declarations
Each variable declaration (field or local) MUST be on its own line and declare exactly one variable. Declarations such as int a, b; MUST NOT be used, except in the header of a for loop.
Local variables SHOULD NOT be habitually declared at the beginning of their containing block. Instead, local variables SHOULD be declared close to the point they are first used. The aim is to minimize the scope of local variables.
Local variable declarations SHOULD typically have initializers, or SHOULD be initialized immediately after the declaration.
Inner assignment — assigning a variable as a side effect inside a larger expression, eg. String s = Integer.toString(i = 2); — SHOULD be avoided. An assignment SHOULD occur as its own top-level statement, so a reader scanning an expression for its value is not also scanning it for side effects. The one common exception is the header of a for loop, where an assignment as part of the loop’s initialization or update clause is idiomatic.
/* ✗ Declared together, far from use, and assigned inside an expression. */
int count, total;
// …
if ((count = queue.drainTo(batch)) > 0) {
total = sum(batch);
// …
}
/* ✓ One variable per declaration, declared where first used. */
// …
int count = queue.drainTo(batch);
if (count > 0) {
int total = sum(batch);
// …
}A local variable or parameter SHOULD NOT hide a field, or any other declaration at a higher level, by reusing its name. The compiler already rejects a local that hides another local in an enclosing block, but it accepts a local that hides a field, and every use of the name inside that scope then silently refers to the local. A reader who knows the field is easily misled. And a later edit that deletes the local declaration changes the code’s meaning without a compile error.
private int count;
void recount() {
if (isStale()) {
int count = 0; // ✗ Hides the field.
// …
}
}A constructor or setter parameter that shares its name with the field it initializes, assigned with this.name = name, is the one common exception. The idiom is universal, the this. qualifier makes the distinction explicit, and the scope is a single assignment.
Local variable type inference
A local variable MAY be declared with var (JDK 10 or later) in place of an explicit type, and the compiler infers the type from the initializer. var removes a type from the source, not from the program, so it trades information the reader had for keystrokes the writer saved. Code is read far more often than it is written, so var SHOULD be used only where the variable’s type is clear from the declaration and the code around it, without requiring a code editor to reveal it.
The following rules apply to the use of var.
- Encode the type in the variable name. The variable name SHOULD say what the variable holds, and SHOULD also hint at its type, eg.
customersor (better still)customerList, notresult. - Use
varonly when the initializer shows the type. A constructor call, or a factory method whose name gives the type away, tells the reader the return type. Where the initializer does not,varSHOULD NOT be used. - Keep the scope short.
varSHOULD NOT be used where the variable is used far from its declaration. The further apart they are, the harder the type is to find, and the easier it is to miss when the initializer changes, eg. from aList, which keeps insertion order, to aSet, which may not. - Use
varfor intermediate steps. A long chained or nested expression MAY be split into steps held invarlocals. That usually reads better than one long expression, or a run of declarations with verbose generic types.
/* ✓ The type is stated explicitly. */ CustomerPage result = service.fetch(request); /* ✗ The type is not evident from the initializer, nor the variable name. */ var result = service.fetch(request); /* ✓ The name carries the type information. */ var customers = service.fetch(request);
In each of the following examples, the initializer makes the type obvious, or the var names an intermediate step.
var outputStream = new ByteArrayOutputStream();
var reader = Files.newBufferedReader(path);
var frequencies = words.stream()
.collect(groupingBy(word -> word, counting()));
var mostFrequent = frequencies.entrySet().stream()
.max(Map.Entry.comparingByValue());var MUST NOT be combined with the diamond operator or a generic method, unless the constructor or method arguments supply the type argument. With nothing to infer from, the compiler infers Object, and the variable silently loses its element type.
var queue = new PriorityQueue<>(); // ✗ PriorityQueue<Object> var queue = new PriorityQueue<>(comparator); // ✓ PriorityQueue<String> var list = List.of(); // ✗ List<Object> var list = List.of(BigInteger.ZERO); // ✓ List<BigInteger>
An integer literal with no suffix is always an int, whatever type was intended. Assigned to an explicitly declared byte, short, or long, it converts to that type. But assigned to a var, it stays an int. So var SHOULD NOT be used with an integer literal unless int is the intended type. A long variable MUST be declared either with its explicit type, or with var and a literal carrying the L suffix.
var flags = 0; // ✗ int, not byte var mask = 0x7fff; // ✗ int, not short var base = 17; // ✗ int, not long var total = 0L; // ✓ long
The same care applies to floating-point values. A variable declared double but initialized from a float constant is widened to double. But the same declaration rewritten with var infers float, silently narrowing its precision.
static final float RATE = 0.1f; double interest = RATE; // ✓ double var interest = RATE; // ✗ float
Other literals carry their type with them unambiguously, and MAY be used with var freely: true, 'a', "text", 1.0f, and 2.0.
var applies only to local variables. The types of fields, method parameters, and return types are always written explicitly.
Classes and interfaces
There SHOULD be a logical ordering to the contents of classes and other class-like constructs. There is no single ordering that works well for all classes, but the following order is a good starting point.
- Static fields.
- Instance fields.
- Constructors.
- Methods.
Class and instance fields MAY be further ordered by visibility: first public, then protected, then package-scoped (no modifier), then private. And within these groups further ordering MAY be done by name (alphabetical ordering).
However, it is generally better to order and group methods logically. Authors SHOULD order the contents of a class in whatever way they feel most helps to understand the class’s purpose and logic.
Whatever the order, its maintainer SHOULD be able to explain its rationale, and new methods SHOULD NOT be habitually appended to the end of the class.
Overloaded methods — methods of a class that share the same name, but which have different parameters — MUST be grouped together, with no other members between them. This requirement also applies to overloaded constructors.
Every constructor in a public or protected class SHOULD be explicit. An implicit default constructor SHOULD NOT be relied upon in a class-like construct that forms part of a public API. Declaring the constructor explicitly forces a deliberate decision about its access level, and prevents a class from becoming publicly instantiable by accident.
A utility class — one containing only static methods and constants — SHOULD NOT have a public constructor. Instead, explicitly declare its constructor private (or protected if the class is intended to be subclassed).
public final class Checksums {
private Checksums() {}
public static long crc32(byte[] data) {
// …
}
}A class that overrides equals() MUST also override hashCode(). The equals()/hashCode() contract requires that two objects considered equal by equals() produce the same hash code. Violating this breaks the class’s behavior in any hash-based collection (HashMap, HashSet, and similar).
public final class Sku {
private final String code;
// …
@Override
public boolean equals(Object other) {
return (other instanceof Sku that) && code.equals(that.code);
}
@Override
public int hashCode() {
return code.hashCode();
}
}A record generates equals() and hashCode() together from its components, and is the simpler choice for a class whose equality is defined entirely by its fields.
Object.finalize MUST NOT be overridden. Finalization is unreliable, and the finalization mechanism has been deprecated for removal from the Java platform since JDK 18. A class that holds a resource releases it through an explicit close() method instead.
Resource management
A class that holds a resource which must be released — a file handle, a socket, a database connection, a lock, a native buffer — SHOULD implement AutoCloseable (or its I/O subtype Closeable), and release the resource in close(). The class’s Javadoc SHOULD say that instances must be closed, and by whom — the code that created the instance, or the code it was handed to.
/// A connection to the audit log. The code that opens a connection
/// must close it.
public final class AuditLogConnection implements AutoCloseable {
private final Socket socket;
// …
@Override
public void close() throws IOException {
socket.close();
}
}Code that uses an AutoCloseable MUST close it deterministically, and SHOULD do so with a try-with-resources statement (JDK 7 or later) rather than an explicit finally block. The statement closes every resource it declares when the block exits, whether normally or by an exception, closes them in the reverse order of their initialization, and skips any resource that was never initialized to a non-null value. An exception thrown by close() while another exception is already propagating is attached to that exception with addSuppressed, rather than replacing it, so the original failure is not lost. A hand-written finally block gets all of this wrong by default.
/* ✗ If close() throws, it replaces the exception from write(), and the
original failure is lost. */
final var connection = AuditLogConnection.open(host);
try {
connection.write(entry);
} finally {
connection.close();
}
/* ✓ An exception from close() is suppressed into the one from write(). */
try (var connection = AuditLogConnection.open(host)) {
connection.write(entry);
}Where one resource wraps another, each layer SHOULD be declared as a separate resource. A single nested expression, eg. new BufferedReader(new InputStreamReader(socket.getInputStream())), leaves the inner stream open if a later constructor throws, because only the outermost object is a declared resource.
/* ✗ If the InputStreamReader constructor throws, the stream is never closed. */
try (var reader = new BufferedReader(
new InputStreamReader(socket.getInputStream(), charset))) {
return reader.readLine();
}
/* ✓ Each layer is closed, innermost last. */
try (var inputStream = socket.getInputStream();
var streamReader = new InputStreamReader(inputStream, charset);
var reader = new BufferedReader(streamReader)) {
return reader.readLine();
}An existing resource held in a final or effectively final variable MAY be named directly in the resource specification (JDK 9 or later), eg. try (connection) { … }, rather than being redeclared.
final var connection = AuditLogConnection.open(host);
connection.authenticate(credentials);
try (connection) {
connection.write(entry);
}Where the paired operation is not an AutoCloseable — releasing a Lock, restoring a changed setting, unregistering a listener — the release MUST be in a finally block that immediately follows the acquisition, so it runs however the try block exits. This applies whether or not the code in between throws checked exceptions, since any statement can throw an unchecked one.
lock.lock();
try {
updateSharedState();
} finally {
lock.unlock();
}Concurrency
The principles of concurrent design — keeping concurrency apart from business logic, minimizing shared mutable state, and designing thread-safe classes — are covered in TS-7: Code Design. This section covers the Java APIs.
Application code SHOULD NOT create and start Thread objects directly. Submit tasks to an ExecutorService instead, created from one of the Executors factory methods or configured explicitly with a ThreadPoolExecutor, so that the number of threads is bounded, threads are reused, and the tasks' lifecycle has a single owner.
/* ✗ One thread per request, unbounded, owned by nobody. A burst of
requests exhausts the machine's thread budget. */
for (var request : requests) {
new Thread(() -> handle(request)).start();
}
/* ✓ The pool bounds the concurrency, reuses its threads, and owns
their lifecycle. */
for (var request : requests) {
executor.submit(() -> handle(request));
}Every thread and every executor has a lifecycle, and the code that starts it MUST also arrange for it to stop. A platform thread is either a daemon or a non-daemon thread, and the JVM begins its shutdown sequence only once every started non-daemon thread has terminated. The threads of an executor built with the default thread factory are non-daemon threads, so an executor that is never shut down keeps the JVM alive after main returns, and the application appears to hang on exit. Making the threads daemon threads is not a fix, since the JVM then halts them mid-task at shutdown, without running their finally blocks.
An ExecutorService that is scoped to a block of code SHOULD be declared as a try-with-resources resource (JDK 19 or later, where ExecutorService implements AutoCloseable). Its close() method stops accepting new tasks, waits for submitted tasks to complete, and cancels them if the waiting thread is interrupted.
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (var request : requests) {
executor.submit(() -> handle(request));
}
} // Waits here until every task has completed.An executor that lives as long as a service MUST be shut down when the service stops, in two phases: shutdown() to reject new tasks and let submitted ones finish, then, after a bounded wait, shutdownNow() to interrupt any that are still running. The ExecutorService Javadoc gives this procedure, which also restores the interrupted status if the waiting thread is itself interrupted.
void stop() {
executor.shutdown();
try {
if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {
executor.shutdownNow();
if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {
log.warn("Executor did not terminate.");
}
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
}A task that runs for a long time SHOULD respond to interruption, by checking Thread.currentThread().isInterrupted() between units of work or by letting InterruptedException propagate from blocking calls. Otherwise shutdownNow() has no way to stop it.
void reindexAll(List<Document> documents) {
for (var document : documents) {
if (Thread.currentThread().isInterrupted()) {
return;
}
reindex(document);
}
}Scheduled work SHOULD use a ScheduledExecutorService rather than java.util.Timer — see Legacy APIs.
Enums
An enum class with no methods and no documentation on its constants MAY be formatted as a single line, similar to an array initializer.
private enum Suit { CLUBS, HEARTS, SPADES, DIAMONDS }Otherwise, the enum constants SHOULD be listed on separate lines, with a comma after each constant except the last, and the opening brace on the same line as the enum name. There SHOULD NOT be any blank lines within enum bodies, except where required around comments.
private enum Answer {
YES {
@Override
public String toString() {
return "yes";
}
},
NO,
MAYBE
}Modifiers
Class and member modifiers, when present, SHOULD appear in the following order, which combines the orders recommended by the Java Language Specification for classes, fields, and methods.
public protected private abstract default static final sealed non-sealed transient volatile synchronized native strictfp
/* ✗ Modifiers out of order. */
final static private Logger log = LoggerFactory.getLogger(Foo.class);
synchronized public static void reset() { … }
/* ✓ Modifiers in the recommended order. */
private static final Logger log = LoggerFactory.getLogger(Foo.class);
public static synchronized void reset() { … }For classes, the public access modifier MUST be added only if the class is intended to be used outside of its package.
For members, an access modifier SHOULD be declared in most cases, so that each member’s access level is an explicit choice.
Most instance fields SHOULD be private, to adhere to the principle of data hiding. Most methods will be public, unless the method is intended to be used only within the current class or its subclasses, in which case it SHOULD be protected — or private, in a final class.
Package-private members — which have no access modifier, as this is the default access level — SHOULD generally be avoided, particularly in applications. However, package-private access can be useful in some cases, particularly in libraries, and in the implementation of the aggregate root design pattern.
A member that would be private except that a test needs to reach it SHOULD be widened no further than package-private, since tests conventionally live in the same package as the code under test. The reason for the wider access SHOULD be marked on the member, so a later reader does not treat it as part of the class’s intended surface and start calling it from production code. Guava’s @VisibleForTesting annotation is the common marker. The JDK has no equivalent, and a project without Guava SHOULD use a short comment instead. A test that needs many such members is usually a sign that the class should be tested through its public interface, or split.
class ConfigReader {
@VisibleForTesting static final String USER_FIELD = "user";
// …
}Annotations
A declaration annotation MUST precede all other modifiers of the element it annotates, eg. @Deprecated public static, not public @Deprecated static. Type-use annotations are the exception, and are placed as described below.
Field annotations SHOULD all be listed on the same line.
@Partial @Mock DataLoader loader;
Type-use annotations MUST appear immediately before the type they are annotating.
final @Nullable String name; public @Nullable Person getPersonByName(String name);
Annotations applying to a class MUST each be listed on separate lines before the class declaration, and immediately after any preceding Javadoc. The same rule applies to annotations on a package declaration (in package-info.java) and a module declaration (in module-info.java). The line breaks between these annotations are not line-wrapping, so the lines that follow are not given continuation indentation.
@Deprecated
@CheckReturnValue
public final class Frozzler {
// …
}The rules for method and constructor annotations are the same.
@Deprecated
@Override
public String getNameIfPresent() {
// …
}The one exception is a method or constructor with a single annotation that takes no parameters, which MAY share the first line of the signature.
@Override public int hashCode() {
// …
}There are no specific rules for formatting annotations on parameters or local variables.
@Override
The @Override annotation MUST be used on every method declaration where it is legal. That covers a method that overrides a superclass method, a method that implements an interface method, an interface method that re-specifies a method of a superinterface, and an explicitly declared accessor method for a record component.
record Team(List<Member> members) {
@Override
public List<Member> members() {
return List.copyOf(members);
}
}There is one exception, which is that @Override MAY be omitted when the parent method is @Deprecated.
@SuppressWarnings
@SuppressWarnings SHOULD be used only where a compiler or static-analysis warning cannot be eliminated by changing the code, eg. an unchecked cast that the type system cannot express but the surrounding logic guarantees is safe. A warning that can be fixed MUST be fixed rather than suppressed.
Where a suppression is necessary, it MUST be scoped as narrowly as possible, on the single local variable declaration or method that needs it, never on a whole class. Where the warning arises inside a larger method, extract the offending code into a small method or a local variable so that only that piece is annotated. A comment MUST explain why the warning cannot be fixed, so a later reader can tell whether the reason still holds.
/* The map only holds values of the key's type parameter, as put() ensures. */
@SuppressWarnings("unchecked")
T value = (T) values.get(key);Static members
References to static class members MUST be qualified with the name of the class, not with a reference or expression of that class’s type.
Foo myFoo = new Foo(); Foo.doSomething(); // ✓ myFoo.doSomething(); // ✗ somethingThatReturnsFoo().doSomething(); // ✗ Also evaluates the call.
Exceptions
It is very rarely correct to do nothing in response to a caught exception. Where a catch block does nothing, the reason MUST be documented in a comment within it.
try {
int i = Integer.parseInt(response);
return handleNumericResponse(i);
} catch (NumberFormatException ok) {
/* It's not numeric; that's fine, just continue. */
}
return handleTextResponse(response);An empty catch block is a last resort. Before writing one, consider the following alternatives, in this order of preference.
- Propagate it. Don’t catch the exception at all, and declare it in the method’s
throwsclause, so the caller decides what to do with it. - Wrap it. Catch the exception and throw a new one at the level of abstraction of the method that catches it, passing the original as the cause so its stack trace is kept. A configuration loader that fails to parse a number throws a
ConfigurationException, not aNumberFormatException. - Substitute a default. Where there is a sensible fallback value, use it in the
catchblock, and document the fallback in the method’s Javadoc. - Rethrow it unchecked. Where the failure means the program cannot usefully continue, wrap it in an unchecked exception, again passing the original as the cause.
void setServerPort(String value) throws ConfigurationException {
try {
serverPort = Integer.parseInt(value);
} catch (NumberFormatException e) {
throw new ConfigurationException("Port " + value + " is not valid.", e);
}
}Where an API declares a checked exception that the arguments rule out — eg. new String(bytes, "UTF-8") declares UnsupportedEncodingException, although every Java platform is required to support UTF-8 — the catch block SHOULD throw an AssertionError with the caught exception as its cause. If the "impossible" case ever happens, it fails loudly instead of being absorbed.
try {
digest = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
/* Every Java platform is required to support SHA-256. */
throw new AssertionError(e);
}A method SHOULD declare the narrowest exception types it throws, and MUST NOT declare throws Exception, which forces every caller either to catch everything or to declare throws Exception in turn. Equally, a public method SHOULD NOT expose the exception types of its implementation, such as a SQLException thrown from a repository whose callers are not meant to know it is backed by SQL. Wrap such exceptions in a type that belongs to the method’s own API, as in the second alternative above.
/* ✗ Too broad, and leaks the storage technology. */ Customer findCustomer(CustomerId id) throws Exception; Customer findCustomer(CustomerId id) throws SQLException; /* ✓ A narrow exception type that belongs to the method's API. */ Customer findCustomer(CustomerId id) throws CustomerNotFoundException;
A caught InterruptedException MUST NOT be swallowed. Catching it clears the thread’s interrupted status, so code further up the stack that checks the status — a thread pool shutting down, for example — no longer sees that the thread was asked to stop. Either let the exception propagate, or restore the status before continuing.
try {
task = queue.take();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}In tests, the following is a common idiom for ensuring that the code under test does throw an exception of the expected type. The variable name expected explains the empty catch block, so a comment is not necessary here. Where the test framework provides an assertion for this, such as JUnit’s assertThrows, that assertion SHOULD be preferred.
try {
emptyStack.pop();
fail();
} catch (NoSuchElementException expected) {}java.lang.Throwable, java.lang.Exception, java.lang.Error, and java.lang.RuntimeException SHOULD NOT be caught directly. Catching one of these broad types risks silently swallowing a failure the catch block was never written to handle — a NullPointerException or an OutOfMemoryError, for example — that should be allowed to propagate rather than being absorbed by a handler meant for a narrower, expected failure. Instead, catch the most specific exception type the code can meaningfully recover from.
Where several specific exceptions need the same handling, catch them together in a multi-catch block (JDK 7 or later), rather than widening the catch to a common supertype.
try {
handler = loadHandler(className);
} catch (ClassNotFoundException | NoSuchMethodException e) {
throw new ConfigurationException("No handler " + className + ".", e);
}Where the specific exceptions need different handling, split the code into smaller try blocks, each with its own narrow catch, or let the exceptions propagate to a level that can handle them.
The one permitted case for a broad catch is top-level code whose job is to stop any failure escaping — a thread’s run() loop that must keep a batch job alive, a request dispatcher that must turn every failure into an error response, or a test harness — MAY catch Exception or Throwable. The catch block MUST carry a comment saying why the broad catch is intended, and MUST record the failure rather than discard it.
for (var job : jobs) {
try {
job.run();
} catch (Exception e) {
/* One failed job must not stop the rest of the batch. */
log.error("Job {} failed.", job.id(), e);
}
}Preconditions and assertions
A public constructor or method SHOULD check its arguments on entry, and throw immediately when they are invalid. A check at the boundary reports the failure where the bad value came in, with a message naming it, rather than letting it travel on and fail later — possibly on another thread, or long after the caller has returned — with a stack trace that points nowhere near the cause.
Object parameters of public constructors and methods MUST be checked for null, unless the Javadoc states that null is allowed. Use Objects.requireNonNull, which throws a NullPointerException carrying the given message. Other invalid arguments throw IllegalArgumentException, and a call that is invalid for the object’s current state throws IllegalStateException.
void readLater(Path file, Consumer<String> callback) {
Objects.requireNonNull(file, "file");
Objects.requireNonNull(callback, "callback");
if (!Files.isReadable(file)) {
throw new IllegalArgumentException("Not readable: " + file);
}
executor.schedule(() -> callback.accept(read(file)), 1, TimeUnit.HOURS);
}A project that already depends on Guava MAY use its Preconditions class (checkNotNull, checkArgument, checkState) instead, which reads more compactly. A project SHOULD NOT mix the two styles.
The assert statement SHOULD NOT be used. Assertions are disabled by default, and enabled only when the JVM is started with -ea, so an invariant checked with assert is typically not checked at all in production, which is where the check matters. An argument check MUST NOT use assert. An internal invariant that is worth checking is worth checking always, with an explicit test that throws IllegalStateException or AssertionError.
/* ✗ Not checked unless the JVM runs with -ea. */
assert balance >= 0 : "Negative balance";
/* ✓ Always checked. */
if (balance < 0) {
throw new IllegalStateException("Negative balance: " + balance);
}Switch statements
After a switch label (case <label>:, default:), there SHOULD be a line break, and then the indentation increased by one level (ie. two spaces) for the statement group.
The comment // fall through SHOULD be included at the bottom of any statement group where execution will or might continue into the next statement group. This special comment is NOT REQUIRED for the last statement group. It is also NOT REQUIRED for empty statement groups.
switch (input) {
case 1:
case 2:
prepareOneOrTwo();
// fall through
case 3:
handleOneTwoOrThree();
break;
default:
handleLargeNumber(input);
}Each switch statement MUST include a default statement group, even if it has no code. Only a switch statement over an enum or sealed type MAY omit the default statement group, and only if it includes explicit cases that cover all possible values of the type. This enables static analysis tools to issue warnings if cases are missed.
switch (suit) {
case CLUBS:
case SPADES:
drawBlack(card);
break;
case HEARTS:
case DIAMONDS:
drawRed(card);
break;
}defaultArrow-style switch
Java also supports arrow-style switch syntax (JDK 14 or later). Each label is followed by → and either a single expression, a block, or a throw statement — never a break — and there is no fall-through between labels.
String result = switch (input) {
case 1, 2 -> "one or two";
case 3 -> "three";
default -> "large number";
};A switch expression — one whose result is assigned or returned, as in the example above — MUST use the new-style arrow syntax. A switch statement, which does not produce a value, MAY use either form, but arrow syntax is RECOMMENDED for consistency and to avoid unintended fall-through.
As with a colon-style switch, the contents of the switch block are indented by two spaces, and each switch rule starts at that indentation. A rule MAY sit on a single line, as in the example above, provided it fits within the column limits. A rule whose arrow is followed by a non-empty block MUST break after the opening brace, and the block’s contents are indented a further two spaces relative to the rule’s label.
switch (event) {
case Started s -> log.info("Started.");
case Failed f -> {
log.error("Failed.", f.cause());
scheduleRetry(f);
}
default -> {}
}The exhaustiveness rule in Switch statements applies to every switch, whichever syntax it uses. Switch expressions, and switches that match patterns, are already required by the compiler to be exhaustive. An arrow-style switch statement over constants is not, and MUST include a default rule, even an empty one, unless its cases cover every value of an enum or sealed type.
Types
Numeric literals
An uppercase L suffix MUST be used for long literals, eg. 3000000000L, not 3000000000l. The lowercase l can be easily confused with the digit 1.
Text blocks
A multi-line string literal SHOULD use a text block (""" … """, JDK 15 or later) rather than concatenated single-line string literals joined with \n or +.
String html =
"""
<html>
<body>
<p>Hello, world.</p>
</body>
</html>
""";The opening """ MUST begin a new line. That line follows the same indentation rules as any other continuation line, or MAY start at the left margin with no indentation at all, which leaves the most room for wide content. The opening """ is always followed by a line break, as the language requires.
The closing """ MUST be on a new line with the same indentation as the opening """, and MAY be followed on that line by further code, eg. """.formatted(name);. Each line of text within the block MUST be indented at least as much as the two delimiters. The compiler strips the indentation of the least-indented line from every line, so a line indented further than the delimiters keeps the extra spaces at the start of that line in the string’s value.
String query =
"""
SELECT id, name
FROM customer
WHERE region = ?
""";
String greeting =
"""
Dear %s,
Your order has shipped.
""".formatted(name);Unlike an ordinary string literal, a text block’s contents MAY exceed the column limits, since reflowing the text would change the string’s value.
Arrays
Array initializers MAY be treated as block-like constructs. The following styles are all valid.
new int[] { 0, 1, 2, 3 }
new int[] {
0, 1, 2, 3
}
new int[] {
0,
1,
2,
3
}Authors SHOULD NOT write C-style array declarations. The square brackets SHOULD form part of the type, not the variable:
/* ✓ The brackets are part of the type. */ String[] args /* ✗ C-style, with the brackets on the variable. */ String args[]
Generics and collections
A field, method parameter, or return type SHOULD be declared with the most general type that supports what the code does with it — usually an interface such as List, Map, Collection, or Iterable — rather than a concrete implementation such as ArrayList or HashMap. A concrete type in a signature leaks an implementation detail into the API. Callers become able to depend on it, and the implementation can no longer change without breaking them.
/* ✗ Every implementation must return an ArrayList. */ ArrayList<User> getUsers(); /* ✓ Implementations are free to return any list, or a lazy view. */ List<User> getUsers();
Choose the general type by what callers need, not by what is available. Iterable<User> promises only iteration, Collection<User> adds size and containment, and List<User> adds ordering and indexed access. Returning a narrower promise leaves the implementation more room to change.
Local variables are the exception. A local variable’s type does not leak beyond the method, so a local MAY be declared with its concrete type.
A parameterized type MUST NOT be used as a raw type. A raw type such as List rather than List<String> switches off the compiler’s type checks for every use of the variable, and moves any mismatch from a compile-time error to a ClassCastException somewhere else at run time. Where the type argument is genuinely unknown, use a wildcard, eg. List<?>, or a broad type argument, eg. List<Object>, so the omission is explicit and still checked.
List names = new ArrayList(); // ✗ Raw type. List<String> names = new ArrayList<>(); // ✓ List<?> items = loadItems(); // ✓ Element type unknown.
A method MUST NOT return a mutable collection that is part of its object’s internal state. A caller that modifies the returned collection modifies the object behind its back, bypassing whatever invariants the object maintains. Return an unmodifiable copy instead, or an unmodifiable view where the caller needs to see later changes.
public final class Team {
private final List<Member> members = new ArrayList<>();
/* ✗ Callers can add or remove members directly. */
public List<Member> getMembers() {
return members;
}
/* ✓ An unmodifiable snapshot. */
public List<Member> getMembers() {
return List.copyOf(members);
}
}List.copyOf, Set.copyOf, and Map.copyOf (JDK 10 or later) return an unmodifiable copy, and reject null elements. Collections.unmodifiableList and its siblings return an unmodifiable view, which reflects later changes to the underlying collection. The same care applies in reverse. A constructor or setter that stores a collection passed to it SHOULD store a copy, so the caller cannot mutate the object’s state through the reference it kept.
public Team(List<Member> members) {
this.members = List.copyOf(members);
}Strings
== and != MUST NOT be used to compare String values. Use .equals() instead. == compares reference identity — whether two variables point to the same String object — not content, so two String instances holding the same characters can compare unequal with == if they were not interned to the same object.
String a = new String("foo");
String b = new String("foo");
System.out.println(a.equals(b)); // true
System.out.println(a == b); // falseNumeric precision
float and double SHOULD NOT be used for values that require exact decimal precision, such as currency amounts. Binary floating-point cannot represent most decimal fractions exactly, so arithmetic on float/double values accumulates small rounding errors that compound over repeated calculations.
Instead, use BigDecimal for monetary and other precision-sensitive decimal values.
System.out.println(0.1 + 0.2); // 0.30000000000000004
var total = new BigDecimal("0.1").add(new BigDecimal("0.2"));
System.out.println(total); // 0.3A BigDecimal SHOULD be created from a String or with BigDecimal.valueOf, not with the BigDecimal(double) constructor. The constructor takes the double exactly as stored, so new BigDecimal(0.1) is 0.1000000000000000055511151231257827021181583404541015625, and the rounding error the class was chosen to avoid is already in the value.
Nullability
A null reference lets a variable point to nothing, which makes it possible to define an uninitialized reference-typed variable. Most mainstream languages carry some form of this concept, under different names: None in Python, null in Java, JavaScript, Kotlin, and Scala, NULL in PHP, and nil in Ruby and Swift. Tony Hoare, who introduced the null reference into ALGOL W in 1965, later called it his "billion-dollar mistake" for the errors, vulnerabilities, and system crashes it has caused since.
Java has neither non-nullable types nor null-safe operators, which makes a NullPointerException easy to trigger by accident. Consider the following method chain.
var baz = getFoo().getBar().getBaz();
Every method in this chain can potentially return null, and calling a method on that null result raises a NullPointerException. Avoiding this safely means checking every intermediate return value, which quickly becomes verbose.
Foo foo = getFoo();
Bar bar = null;
Baz baz = null;
if (foo != null) {
bar = foo.getBar();
if (bar != null) {
baz = bar.getBaz();
}
}Java 8 introduced Optional, a wrapper type around a value that may be absent, comparable to Maybe or Option in other languages.
This standard RECOMMENDS using Optional as Java’s nullability mechanism. A method SHOULD return type X where X cannot be null, and Optional<X> where it can.
Applying this to the getFoo, getBar, and getBaz methods in the above example allows the method chain to be refactored in a null-safe way, using flatMap to chain the calls.
final var baz = getFoo()
.flatMap(Foo::getBar)
.flatMap(Bar::getBaz)
.orElse(null);A method whose return type is not Optional<X> MUST NOT return null, except in the narrow cases, described at the end of this section, where its return type is annotated @Nullable. Where a meaningful value genuinely cannot be produced, throw an exception instead of returning null from a non-Optional return type. Outside those cases, the possibility of an absent return value MUST be communicated using type Optional<X>.
/* ✗ Nothing in the signature warns the caller to check for null. */
Customer findByEmail(String email) {
return customersByEmail.get(email);
}
/* ✓ The return type tells the caller the result may be absent. */
Optional<Customer> findByEmail(String email) {
return Optional.ofNullable(customersByEmail.get(email));
}Optional does not eliminate NullPointerException instances entirely. Java gives no guarantee that an Optional reference itself is not null.
Optional SHOULD NOT be used for method input parameters. It exists to communicate an absent return value, not to express an optional argument.
/* ✗ Callers must wrap every argument, and can still pass null. */ List<Order> findOrders(CustomerId id, Optional<Status> status); /* ✓ An overload for each form of the call. */ List<Order> findOrders(CustomerId id); List<Order> findOrders(CustomerId id, Status status);
A number of annotation libraries exist to make nullability explicit in code that does not use Optional, each providing its own @NonNull/@Nullable (or similarly named) annotations.
Library | Package |
|---|---|
| |
| |
| |
| |
| |
| |
| |
|
None of these libraries is bulletproof. Each works differently, and none can guarantee null-safety the way a language with built-in non-nullable types can. But where Optional does not fit, an annotation library is the best mechanism Java offers. A codebase SHOULD use a single library throughout.
Every reference is non-null by default. A field, parameter, local variable, or return value that may hold null MUST be annotated @Nullable, whatever its visibility, including private fields and methods. The absence of the annotation is then a statement that the value is never null, which a reader, and a static-analysis tool, can rely on.
class Database {
private @Nullable Connection connection;
@Nullable Connection getConnection() {
return connection;
}
void setConnection(@Nullable Connection connection) {
this.connection = connection;
}
}The annotation does not relax the rule for return values given earlier in this section. A method’s return type is Optional<X> wherever it can be. The @Nullable return type is for the cases where it cannot, an implementation of an interface from outside the codebase whose contract returns null, such as Map.get, and code not yet migrated to Optional.
Implementation comments
Java code uses four comment notations.
//— single-line comment./* … */— multi-line comment./** … */— classic Javadoc comment.///— Markdown Javadoc comment (JDK 23 or later).
Java’s single-line (//) and multi-line (/* … */) comment syntax MUST be used only for implementation comments, while Javadoc comments (/** … */ or ///) MUST be used only for parseable API documentation (see Javadoc).
The single-line implementation comment syntax MUST be used in only two cases.
- To temporarily comment out blocks of code. Commented-out code SHOULD be removed before it reaches production.
- For short end-of-line comments that decode or explain a value assigned, returned, or printed by the statement. End-of-line
//comments MAY exist in production code.
Java’s multi-line implementation comment syntax, /* … */, SHOULD be used for both single-line and multi-line implementation comments. For short comments that fit within the 80-character line length, the opening /* and closing */ SHOULD be written on the same line as the comment. But as soon as the comment text needs to be wrapped to two or more lines, the opening /* and the closing */ SHOULD both be moved to their own lines, with the text lines sandwiched between them.
/* No timeout, since the upstream batch export can take several minutes. */
response = client.send(request);
/*
The upstream API returns 200 with an error body for rate-limited requests,
so the status code alone cannot tell success from failure. Check the body's
"error" field before treating the response as a success.
*/
if (response.body().has("error")) {
// …
}/* ✗ A block-level comment written with //. */ // No timeout, since the upstream batch export can take several minutes. /* ✗ Wrapped text with the delimiters on the text lines. */ /* The upstream API returns 200 with an error body for rate-limited requests, so the status code alone cannot tell success from failure. */
Where the multi-line comment syntax /* … */ is used to enclose a single-line comment, there MUST be exactly one space after the opening /* and another before the closing */.
Multi-line block-level comments SHOULD have a blank line both before and after the comment block. Single-line block-level comments SHOULD have a blank line before them, and MAY have a blank line after.
The single-line comment syntax //, whether used for commenting-out code or EOL comments, MUST be followed by exactly one space, and then the code or comment.
Block-level comments MUST be indented to the same level as the code to which they relate.
Comments MUST NOT be enclosed in boxes drawn with asterisks or other characters.
/* ✗ A box drawn with asterisks. */ /************************************************* * Rebuild the search index from scratch. * *************************************************/ /* ✓ The comment alone. */ /* Rebuild the search index from scratch. */
All text within /* … */ comments MUST be written in full sentences, each starting with a capitalized word and terminated by a period (full stop). Blank lines MAY be written within the text of block-level comments, to break it up into paragraphs. This is particularly beneficial for the readability of very long comments.
int num1 = 7; int num2 = 5; // int num0 = 0; /* The remainder operator (%) returns the remainder after the first operand is divided by the second operand. In this case, 7 / 5 = 1, with a remainder of 2. */ int remainder = num1 % num2; System.out.println(remainder); // 2 /* 3 divides into 7 twice (3 x 2 = 6), with a remainder of 1 (7 - 6 = 1). */ System.out.println(7 % 3); // 1 /* The remainder operator is often used to determine whether a number is even or odd. If x % 2 is 0, then x is even, otherwise it is odd. */ System.out.println(6 % 2); // 0 System.out.println(7 % 2); // 1
Implementation comments SHOULD communicate only information that is not readily available from the code itself, but which is relevant to the understanding of that code. For example, implementation comments SHOULD be used to document the reasons behind a particular choice of design pattern that, without context, may seem unusual or even counterintuitive to developers who are looking at the code for the first time.
/* ✗ Restates the code. */
/* Loop over the entries in reverse order. */
for (int i = entries.size() - 1; i >= 0; i--) {
// …
}
/* ✓ Explains what the code cannot. */
/* Iterate in reverse so that removing an entry does not shift the rest. */
for (int i = entries.size() - 1; i >= 0; i--) {
// …
}See TS-7: Code Design for further general guidance on code-level implementation comments.
Javadoc
Javadoc is a developer tool, intended to help developers understand, maintain, change, and extend the code. Javadoc comments are read by humans in passing, as they read through the code. They’re also parsed by tools. Code editors display them alongside the code they document, and the Javadoc tool generates API documentation from them.
Purpose
Javadoc comments SHOULD be used as API specification. They specify an interface contract, including parameter ranges and thrown exception types, plus behavior that a caller can rely on, including edge cases.
Javadoc comments SHOULD NOT be used as a programming guide — illustrative material such as extended usage examples, term definitions, conceptual overviews, and notes on known bugs or workarounds. That material SHOULD instead be placed in a README or other out-of-band document, which the Javadoc links to.
A single Javadoc comment MUST NOT mix the two concerns. Where a comment would need to both specify a contract and explain a concept, the contract SHOULD stay in the Javadoc and the explanation SHOULD move out-of-band, unless it is short.
A short, focused usage example MAY be included in a Javadoc comment where it clarifies the contract itself — for example, showing the shape of an accepted input.
Javadoc MUST NOT be used to document implementation details, such as the algorithm used in a method. Implementation comments MUST be used for that purpose. Information about a class, interface, field, or method that is not appropriate for a Javadoc comment SHOULD instead go in an implementation comment immediately after the declaration.
Scope
Javadoc SHOULD be used to document every visible top-level class-like construct and every visible member — visibility here meaning any class, interface, record, field, or method that is not private, plus every component of a public or protected record.
Javadoc MAY be skipped for any member that is simple and obvious, such as a trivial getter or setter. It MAY also be skipped for a method that overrides a supertype method which is already documented, since the supertype method’s documentation is inherited.
A private member is not required to carry Javadoc, but it SHOULD where it is complex, or where it is called from multiple places. Where a private method is called from only one place, its documentation MAY instead live in the calling method’s Javadoc.
The Javadoc tool does not document anonymous classes. Any content that would document one SHOULD instead be written in the Javadoc comment of its enclosing class.
Javadoc MAY also be written for any other declaration, where it helps the reader.
Placement
A Javadoc comment MUST immediately precede the declaration it documents, and MUST come before any annotations on that declaration. It MUST NOT be positioned inside a method or constructor body. The Javadoc tool only recognizes a Javadoc comment that immediately precedes the declaration of a module, package, class, interface, field, constructor, or method, so a Javadoc comment placed anywhere else documents nothing.
/* ✗ The annotation separates the comment from the declaration. */
@Override
/// Closes this connection and releases its socket.
public void close() {
// …
}
/* ✓ The Javadoc comes before the annotation. */
/// Closes this connection and releases its socket.
@Override
public void close() {
// …
}Content
Javadoc SHOULD NOT duplicate the information encoded in method names or signatures, or other adjacent code. For example, the following Javadoc is redundant. It restates the method’s name and return type, and tells the reader nothing new.
/// Returns the canonical name of this object.
///
/// @return the canonical name of this object
public String getCanonicalName() {
// …
}The Javadoc would be better used to explain the meaning of "canonical name" in this context:
/// {@return the canonical name of this object} The canonical name is the
/// object's name with all aliases resolved and converted to lowercase. It is
/// unique within a [Registry].
public String getCanonicalName() {
// …
}Javadoc MUST distinguish overloaded methods and constructors from each other. Each overload’s summary fragment SHOULD identify what makes it distinct, rather than repeating the same description.
/// Parses a date in ISO 8601 format, such as `2026-09-19`.
public static LocalDate parse(String text) {
// …
}
/// Parses a date in the given format.
public static LocalDate parse(String text, DateTimeFormatter format) {
// …
}Where a class or method is safe for concurrent use, its Javadoc MUST say so, and state the guarantees it makes — for example, whether it is immutable, or which of its operations are atomic. Absent such a statement, a caller is entitled to assume that the class or method is not thread-safe.
/// A counter of requests per client. This class is thread-safe. Each
/// method is atomic, but a sequence of calls is not.
public final class RequestCounter {
// …
}Markup
Java supports two notations for Javadoc comments: the classic /** … */ notation, with HTML markup, and the Markdown /// notation, introduced by JEP 467 in JDK 23 as a final (not preview) feature. The Markdown notation is the newer of the two, but it does not replace the classic notation, which remains fully supported and is not deprecated. Most existing Java code, including most of the JDK’s own source, still uses the classic notation.
A codebase that targets JDK 23 or later SHOULD write Javadoc comments in Markdown. Before adopting it, check that the tools in use — code editors, formatters, and linters — support Markdown Javadoc comments, as tool support is still maturing.
Important
The Javadoc tool in JDK 22 and earlier treats /// lines as ordinary line comments, and does not process them as Javadoc. A codebase that targets a runtime older than JDK 23 MUST use classic HTML Javadoc comments instead. A codebase MUST NOT mix the two notations.
In the Markdown notation, each line of the comment begins with ///, and a run of consecutive /// lines forms a single comment.
A Javadoc comment may be a single line or multiple lines.
/// Multiple lines of Javadoc text are written here, /// wrapped normally… /// An especially short bit of Javadoc.
The single-line form SHOULD be used for a very short comment with no block tags, such as @return.
Each /// MUST be indented to the same level as the declaration it documents, and MUST be followed by a single space before any text.
Paragraphs are separated by a line containing only ///. HTML elements SHOULD NOT be used where Markdown has an equivalent.
Where a Javadoc comment refers to a Java keyword, a package name, a class, interface, method, or field name, a parameter name, or a code fragment, that text MUST be formatted as inline code, with backticks. A multi-line code example MUST be written as a fenced code block.
A link to another class, method, or field SHOULD be written as a Markdown reference link — for example, [List], [List#add(Object)], or [the list][List]. The Javadoc tool resolves and checks these links. Inline tags such as {@return} and {@inheritDoc} are still available in Markdown Javadoc comments.
/// Splits a string into its comma-separated fields, trimming whitespace
/// from each. Empty fields are kept, so the result always has one more
/// element than the number of commas in `text`.
///
/// ```java
/// var fields = Fields.split("a, b,,c"); // ["a", "b", "", "c"]
/// ```
///
/// To join the fields again, use [#join(List)].
///
/// @param text the string to split
/// @return the fields, in order, as a [List] of strings
public static List<String> split(String text) {
// …
}Classic HTML Javadoc comments
A codebase that targets a runtime older than JDK 23 writes Javadoc comments in the classic /** … */ notation, with HTML markup. The rules in the rest of this section apply, with the following substitutions.
/** * Multiple lines of Javadoc text are written here, * wrapped normally… */ /** An especially short bit of Javadoc. */
In the multi-line form, the opening / MUST be on its own line, indented to the same level as the declaration it documents. Each subsequent line MUST begin with a aligned under the first of the opening /, and the closing */ MUST be on its own line, aligned the same way.
Each paragraph after the first MUST begin with <p>, placed immediately before the first word of the paragraph with no space after it. A blank line (containing only the aligned leading *) SHOULD separate paragraphs. The tag of any other block-level HTML element, such as <ul>, <ol>, <table>, or <pre>, begins its own block and MUST NOT be preceded by <p>.
Code MUST be formatted with the {@code …} inline tag rather than <code> tags, since {@code} escapes HTML special characters. A multi-line code example MUST be written as <pre>{@code … }</pre>.
A link to another class, method, or field SHOULD be written with the {@link} inline tag.
/**
* Splits a string into its comma-separated fields, trimming whitespace
* from each. Empty fields are kept, so the result always has one more
* element than the number of commas in {@code text}.
*
* <pre>{@code
* var fields = Fields.split("a, b,,c"); // ["a", "b", "", "c"]
* }</pre>
*
* <p>To join the fields again, use {@link #join(List)}.
*
* @param text the string to split
* @return the fields, in order, as a {@link List} of strings
*/
public static List<String> split(String text) {
// …
}The {@return} inline tag requires JDK 16 or later. A codebase that targets an older runtime MUST use the @return block tag instead.
The summary
The first sentence of a Javadoc comment is its summary. The Javadoc tool treats everything up to the first period followed by whitespace as the summary, and shows it in member lists and indexes.
The summary SHOULD be a brief fragment — a noun phrase or verb phrase, not a complete sentence, but capitalized and punctuated as if it were one. It SHOULD NOT be a detailed description.
A method’s summary SHOULD start with a third-person descriptive verb (eg. "Gets the label", not "Get the label"). A class, interface, or field summary SHOULD instead state what the thing represents (eg. "A button label"), avoiding phrasing such as "This class…" or "This method…".
/* ✗ Wordy, and phrased as "This method…" and "This class…". */ /// This method will get the label of the button. /// This class is used to represent a label for a button. /* ✓ A verb phrase for the method, a noun phrase for the class. */ /// Gets the label of this button. /// A button label.
Where the first sentence would not make a good summary — because it runs long, or because it contains a period followed by a space that the tool would mistake for the end of the sentence — the {@summary} inline tag (JDK 10 or later) MAY be used to mark the summary explicitly. It MUST be the first thing in the comment, and it MUST NOT be combined with {@return}, which generates a summary of its own.
/// {@summary Approximates pi to the given precision.} The result is
/// accurate to within 1.0 e-6 of the true value.
public static double pi(int digits) {
// …
}Wording
Where Javadoc refers to the object it is attached to, it SHOULD use "this" rather than "the" (eg. "Gets the toolkit for this component"). Where it refers to a method in its general form, it SHOULD omit parentheses and argument types (eg. "the add method"), and include argument types only when referring to one specific overload (eg. "the add(int, Object) method").
Curly (smart) quotes MUST NOT appear in Javadoc text. Use straight quotes throughout.
Latin abbreviations SHOULD be avoided in favor of their English equivalents: "also known as" rather than "aka", "that is" rather than "i.e.", "for example" rather than "eg.", and "in other words" or "namely" rather than "viz.". An abbreviation such as "eg." also ends the summary early, because its period is followed by a space.
Block tags
The standard block tags are:
@paramfor method, constructor, and type parameters.@returnfor the return value of a method.@throwsfor exceptions thrown by a method or constructor.@seefor a cross-reference to related material.@deprecatedto indicate that an API element is deprecated.
Block tags MUST come after the comment’s description, and MUST NOT appear in a single-line Javadoc comment. A common mistake is to write a single-line comment that consists of only a block tag.
/// @return the customer ID /** @return the customer ID */
This is not permitted. It should instead be written with the {@return} inline tag, which generates both the summary and the return-value description.
/// {@return the customer ID}
/** {@return the customer ID} */Each block tag MUST be written on its own line, in the order listed above. Each block tag MUST be followed by a space and then a description, which MUST NOT be empty. When the description is long, it SHOULD be wrapped with continuation lines indented from the position of the @ symbol, by exactly two spaces in a Markdown Javadoc comment, and by four spaces in a classic HTML Javadoc comment. (The Markdown notation needs the smaller indent because a line indented four spaces can be read as the start of an indented code block.)
/// @param timeout the maximum time to wait for a connection before /// giving up, or zero to wait indefinitely
A blank line (containing only the comment’s line prefix) SHOULD appear before a group of block tags, separating them from the preceding prose.
A block tag’s description is tag content, not prose. It SHOULD be a single sentence fragment, without a capitalized first word or a terminal period.
Columns MAY be aligned across a run of block tags, but this is a cosmetic choice, not a requirement.
/// Transfers an amount between two accounts, as a single transaction.
///
/// @param source the account to debit
/// @param target the account to credit
/// @param amount the amount to transfer, which must be positive
/// @return the ID of the completed transfer
/// @throws IllegalArgumentException if `amount` is zero or negative
/// @throws InsufficientFundsException if `source` cannot cover `amount`
/// @see Ledger#reverse(TransferId)
public TransferId transfer(Account source, Account target, Money amount)
throws InsufficientFundsException {
// …
}@param and @return
@param and @return are REQUIRED — for every parameter, including type parameters, and for every non-void method, respectively — even where their purpose seems obvious from the signature alone.
@param takes the parameter name, not its data type, and MUST NOT format that name as code. A type parameter is named in angle brackets (eg. @param <T>). Where a method has more than one @param tag, they MUST appear in the same order as the parameters are declared.
Where @return describes a collection, it SHOULD state the type of element the collection contains.
The {@return} inline tag MAY be used in place of a @return block tag. It MUST appear at the start of the comment, where it generates both the summary and the return-value description.
@throws
@throws SHOULD cover every checked exception a method can throw, and any unchecked exception a caller might reasonably want to catch — except NullPointerException, which SHOULD NOT be documented. Subclasses of Error SHOULD NOT be documented, either. An unchecked exception tied to a specific implementation SHOULD be documented by its general supertype rather than the concrete subtype (eg. IndexOutOfBoundsException, not ArrayIndexOutOfBoundsException).
Where a method has more than one @throws tag, they MUST appear in alphabetical order by exception name.
The @throws tag is distinct from the method’s throws clause. An unchecked exception SHOULD NOT be declared in the throws clause. It SHOULD be documented with @throws only.
@see
@see is used for a cross-reference that stands apart from the description, rather than a link inline within it. Where more than one @see tag is present, they SHOULD be ordered by their proximity to the documented element — nearest first.
@deprecated
@deprecated MUST be paired with the @Deprecated annotation. The annotation makes the compiler warn about any use of the deprecated element, while the tag documents the reason.
The tag’s description SHOULD explain why the element is deprecated and link to its replacement. Where there is no replacement, it SHOULD say "no replacement".
Unlike other block tag descriptions, it MAY be written in complete sentences.
/// Gets the customer's given and family names, separated by a space.
///
/// @return the full name
/// @deprecated This drops any middle names, and assumes a name order
/// that not every culture uses. Use [#displayName()] instead.
@Deprecated
public String fullName() {
// …
}Other block tags
The @since, @author, and @version tags are of most use to maintainers of published, versioned APIs — libraries and frameworks with multiple contributors and consumers who can’t see the source history. In that context, @since in particular earns its place. It tells a caller which release they need, which is something the version control system can’t tell them from the published Javadoc alone. Where a project already uses these tags, they MUST be kept accurate. A stale @since is worse than none.
In closed-source application code, these tags SHOULD NOT be used. The version control system already records when and by whom each declaration was introduced, and duplicating that in a comment only creates something else to get out-of-date. In general, don’t restate in a Javadoc tag what version control already provides.
The serialization tags each belong on a specific kind of declaration: @serial on a serializable field, @serialField on the serialPersistentFields member, and @serialData on a serialization method such as writeObject. Where used, they MUST come after @see and before @deprecated.
For reference, the full block tag order defined by the Javadoc tool’s documentation is: @author, @version, @param, @return, @throws, @see, @since, @serial/@serialField/@serialData, @deprecated.
Package-level Javadoc
Package-level Javadoc is written in the Javadoc comment that precedes the package declaration in a package-info.java file (see Special source files). It SHOULD follow a three-part structure: a summary sentence describing the package’s purpose, a "Package Specification" section for any formal contract the package as a whole makes, and a "Related Documentation" section linking out to other relevant material.
/// Order capture and fulfillment: the entities, repositories, and services /// that take an order from checkout to dispatch. /// /// ## Package Specification /// /// Every public method in this package rejects a `null` argument with a /// `NullPointerException`, unless its documentation says otherwise. /// /// ## Related Documentation /// /// - [Order lifecycle](https://docs.example.com/orders/lifecycle) package com.example.orders;
Images
Where an image is needed in Javadoc, it MUST be placed in a doc-files subdirectory of the package it documents, and named <class>-<n>.<ext>, matching the class it illustrates and a sequence number. It SHOULD be in PNG or SVG format.
Java API specifications
This section covers best practices for using particular Java APIs.
Jakarta Persistence (JPA)
Jakarta Persistence, formerly known as the Java Persistence API (JPA), is a specification for object-relational mapping (ORM) in Java applications. It defines a set of interfaces and annotations for managing relational data, allowing developers to interact with databases using abstractions in the form of Java classes and objects, rather than raw SQL queries.
JPA interfaces provide interoperability between ORM libraries that are compliant with the JPA specification, such as Hibernate and EclipseLink. This makes it easier to swap out data persistence implementations without changing — or at least while minimizing the changes required in — the application code.
JPA uses JPQL (Jakarta Persistence Query Language) to query data from relational databases. JPQL is similar to SQL, but it operates on the Java entity objects rather than directly on database tables.
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
// …
}import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Long> {
// …
}JPA is widely used in enterprise applications and is a key part of the Jakarta EE ecosystem. It is therefore RECOMMENDED to use JPA in Java applications that implement simple interactions with relational databases.
For more complex interactions, such as queries that involve deep joins, or otherwise where performance optimization is a key design constraint, it may be more appropriate to use a lower-level abstraction, or no abstraction at all.
Legacy APIs
New code MUST NOT use a deprecated API, whether in the JDK or in a library. Existing code that already uses one MAY keep doing so when it is changed for another reason, to stay consistent with its surroundings, but the deprecation is a reason to migrate it, not a style to copy.
Several JDK classes are not deprecated, but have been superseded by replacements that new code SHOULD use instead.
Instead of | Use | Reason |
|---|---|---|
|
|
|
|
| The legacy classes synchronize every call. Where a collection genuinely is shared between threads, use a |
|
| A |
|
| The legacy date classes are mutable, and |
/* ✗ Legacy classes. */ Stack<Node> pending = new Stack<>(); Date due = new Date(System.currentTimeMillis() + 86_400_000L); /* ✓ Their modern replacements. */ Deque<Node> pending = new ArrayDeque<>(); Instant due = Instant.now().plus(Duration.ofDays(1));
Console output
Production code MUST NOT write diagnostic output to System.out or System.err, whether directly, eg. with System.out.println(), or indirectly, eg. with Throwable.printStackTrace(). Output written this way bypasses the logging framework. It carries no level, timestamp, or logger name, it cannot be filtered, routed, or rate-limited, and in some runtime environments it is discarded altogether. Use the project’s logging API instead.
/* ✗ Bypasses the logging framework. */
} catch (IOException e) {
System.err.println("Could not load config: " + e.getMessage());
e.printStackTrace();
}
/* ✓ Logged, with the exception as the last argument. */
} catch (IOException e) {
logger.error("Could not load config from {}.", path, e);
}A command-line program whose purpose is to write to standard output or standard error is the exception, for that output only.
Logging practice, in general, is covered by TS-57: Logging, Monitoring, Observability.
Deferred log formatting
As per the logging rules defined in TS-57, log statements SHOULD NOT do work that is thrown away when their level is disabled. In the first example below, because Java evaluates a method’s arguments before calling it, the string concatenation is built in full, and then discarded. The second example instead passes a message template and its arguments, rather than a pre-built string. With SLF4J, the {} placeholders are filled in only when the entry is written.
/* Builds the string even when DEBUG is disabled. */
logger.debug("Loaded " + items.size() + " items for " + user);
/* Formats the message only when DEBUG is enabled. */
logger.debug("Loaded {} items for {}", items.size(), user);The arguments themselves are still evaluated. What is deferred to the log writer is the formatting of the log message. Where computing an argument is itself expensive, pass a Supplier that the logging API calls. Every level method of java.util.logging.Logger has an overload that accepts a Supplier<String>, and the fluent API of SLF4J 2 accepts a Supplier for each argument.
/* java.util.logging. The lambda runs only when FINE is enabled. */
logger.fine(() -> "Cache state: " + cache.describe());
/* SLF4J 2. The supplier runs only when DEBUG is enabled. */
logger.atDebug()
.addArgument(() -> cache.describe())
.log("Cache state: {}");Where neither form fits, for example where several statements share one expensive computation, guard them with a level check, such as logger.isDebugEnabled() in SLF4J or logger.isLoggable(Level.FINE) in java.util.logging.
if (logger.isDebugEnabled()) {
var snapshot = cache.snapshot();
logger.debug("Cache entries: {}", snapshot.size());
logger.debug("Cache hit rate: {}", snapshot.hitRate());
}References
- Google Java Style Guide
- Oracle: Code Conventions for the Java Programming Language
- Oracle: The Java Tutorials
- Oracle: How to Write Doc Comments for the Javadoc Tool
- Oracle: Code Conventions for the Java Programming Language — Comments
- JEP 467: Markdown Documentation Comments
- Android Open Source Project. Java Code Style for Contributors.
- Marks, S (2018). Local Variable Type Inference: Style Guidelines.
- OpenJDK. JEP 511: Module Import Declarations.
- OpenJDK. JEP 512: Compact Source Files and Instance Main Methods.
- Oracle (2023). Interface ExecutorService, Java SE 21 API Specification.
- Oracle (2023). The Java Language Specification, Java SE 21 Edition.
- QOS.ch. SLF4J User Manual.
- Twitter. Java Style Guide.