Stray semicolons in Java

Marcio Endo
February 11, 2024

Consider the following Java class:

public class ClassMembers {
  ;
  
  int field;;;
  
  void method() {} ;;
  
  ;
  ;;
  ;;;
  
  class Inner {}
  
  ;  
}

It contains a number of stray semicolons.

Is it legal Java code? In other words, does it compile?

Let's learn.

Class members and stray semicolons

You may or may not be surprised to learn that the previous example compiles without errors.

A stray semicolon is an allowed class member.

It's all in the JLS

It is all defined in the Java Language Specification (JLS).

Section 8.1.7 is called "Class Body and Member Declarations". It defines the following productions:

ClassBody:
  { {ClassBodyDeclaration} }
  
ClassBodyDeclaration:
  ClassMemberDeclaration
  InstanceInitializer
  StaticInitializer
  ConstructorDeclaration
  
ClassMemberDeclaration:
  FieldDeclaration
  MethodDeclaration
  ClassDeclaration
  InterfaceDeclaration
  ;

It says that the body of a class may contain zero or more class body declarations.

The allowed class body declarations are:

And, in turn, the allowed class members are:

So the semicolon character is an allowed class "member".

However, they are not a real member.

Stray semicolons and generated class file

While stray semicolons are allowed in the source code they do not produce any effect to the compiled class file.

So the following Java class:

public class OnlySemiColons {
  ;
  ;;
  ;;;
}

Is equivalent to the following Java class:

public class Empty {}

In other words, their difference is solely in the class name.

Here's the difference between the javap -v outputs of the both classes:

1,5c1,5
< Classfile /tmp/blog/OnlySemiColons.class
<   Last modified Feb 10, 2024; size 207 bytes
<   SHA-256 checksum 7ab6fcd28276909c579669b2f0a6401ee9785c1bb1afb3119dcd7fd829907ba2
<   Compiled from "OnlySemiColons.java"
< public class blog.OnlySemiColons
---
> Classfile /tmp/blog/Empty.class
>   Last modified Feb 10, 2024; size 189 bytes
>   SHA-256 checksum b276e6806d458674f9178b351dd486b20943d7bf7b72634545140f03c94c0346
>   Compiled from "Empty.java"
> public class blog.Empty
9c9
<   this_class: #7                          // blog/OnlySemiColons
---
>   this_class: #7                          // blog/Empty
19,20c19,20
<    #7 = Class              #8             // blog/OnlySemiColons
<    #8 = Utf8               blog/OnlySemiColons
---
>    #7 = Class              #8             // blog/Empty
>    #8 = Utf8               blog/Empty
24c24
<   #12 = Utf8               OnlySemiColons.java
---
>   #12 = Utf8               Empty.java
26c26
<   public blog.OnlySemiColons();
---
>   public blog.Empty();
37c37
< SourceFile: "OnlySemiColons.java"
---
> SourceFile: "Empty.java"

So, apart from the name of the classes, the contents of their class files are the same.

Top-level declarations and stray semicolons

The following Java code compiles without errors:

public class TopLevel {
};

;;;;

interface Foo {};;

;

record Bar() {};

A stray semicolon is an allowed top-level declaration.

It's all in the JLS

Once again, it is all defined in the Java Language Specification.

Section 7.3 defines compilation units.

Here's the production for the ordinary compilation unit:

OrdinaryCompilationUnit:
  [PackageDeclaration] {ImportDeclaration} {TopLevelClassOrInterfaceDeclaration}

So an ordinary compilation unit is composed of:

As a result an empty file is a valid compilation unit. It will not produce a class file but javac will "compile" it without errors.

But I digress...

Top-level declarations are defined a little later in Section 7.6:

TopLevelClassOrInterfaceDeclaration:
  ClassDeclaration
  InterfaceDeclaration
  ;

The allowed top-level declarations are:

So the semicolon character is an allowed top-level declaration.

Not allowed as a package declaration

Just like the following does not compile:

public class Foo {}
// does not compile!
package com.example;
class Example {}

The following does not compile either:

;;; // does not compile!
package com.example;
class Example {} 

The package declaration, if present, must be the first declaration of the source file.

Not allowed as an import declaration

And just like the following does not compile:

import java.util.ArrayList;
public class Example {}
// does not compile
import java.util.List;
class Util {}

The following does not compile either:

import java.util.ArrayList;
;;; // does not compile
import java.util.List;
class Util {}

You cannot have top-level declarations between import declarations.

But...

On the other hand, the following example compiles without errors:

;;;
class Stray {}
;;;

public class TopLevelStart {
}

It is as class of the unnamed package with no import declarations.

So, in this case, the file may start with a top-level declaration. And, as a semicolon is an allowed top-level declaration, the file may start with a semicolon.

Just because you can doesn't mean you should

Here's an excerpt from Section 7.6 of the JLS (emphasis mine):

Extra ";" tokens appearing at the level of class and interface declarations in a compilation unit have no effect on the meaning of the compilation unit. Stray semicolons are permitted in the Java programming language solely as a concession to C++ programmers who are used to placing ";" after a class declaration. They should not be used in new Java code.

So it tells us two things.

First, it hints on the reason why stray semicolons were added to the language. It was so that Java's syntax would be familiar to C++ programmers.

Second, it clearly states that stray semicolons should not be used in new Java code

It is not idiomatic and may confuse readers of your code for no reason.

Note on the empty statement

Stray semicolons must not be confused with the empty statement.

They are different concepts.

Conclusion

Do not use stray semicolons in your code.

They might be an interesting bit of Java trivia to know. But, as already mentioned:

You can find the source code of the examples in this GitHub repository.