Objectos Weekly #005: Java's contextual keywords, class names and method names

Marcio Endo
December 11, 2022

Welcome to Objectos Weekly issue #005.

This is the first issue sent via the email newsletter. If you have received this issue in your email and had any troubles opening it, I apologize in advance. I would really appreciate it if you sent me a few words explaining what went wrong. And thank you in advance.

Please know that I will be open for Java freelancing work in 2023. You can find my contacts on this page. All work will be provided via Objectos Software LTDA based in São Paulo, Brazil.

Contextual keywords

Java 9 introduced the concept of restricted keywords. Since Java 17 they are called contextual keywords.

Contextual keywords allow for the same token to be interpreted as either a keyword or as a different token, an identifier typically. It depends on where the token appears in a compilation unit.

Reserved keywords

To understand contextual keywords, let's first discuss reserved keywords.

In Java, identifiers cannot have the same character sequence of a reserved keyword. You cannot name a method try because try is a reserved keyword.

// does not compile!
// 'try' is a reserved keyword
public void try() {
  ...
}

The same rule applies to other declarations having one or more identifiers. Examples include package names, module names, field names, local variable names, class names and others.

Backward compatibility

Reserved keywords represent a problem when evolving the language. As an example, let's take the introduction of records in Java 14.

Suppose that you are responsible for an application that currently targets Java 11. It contains a class called Record. So spread throughout your codebase you have many fields, methods and local variables named record:

class Foo {
  private Record record;
  
  public static Record of(int value) {
    var record = Record.create();
    record.set(value);
    return record;
  }

  public Record record() {
    return record;
  }
}

Suppose now that you want to upgrade to Java 17.

If the language designers decided to make record a reserved keyword, your application would not compile anymore.

So they made record a contextual keyword instead.

List of contextual keywords

As of Java 19, the contextual keywords are the following:

exports      opens      requires     uses
module       permits    sealed       var
non-sealed   provides   to           with
open         record     transitive   yield

We can use them in almost any place an identifier is required. As an example, consider the following:

package post.yield;

public class Contextual {
  public static void main(String[] uses) {
    var var = "Contextual keywords example";

    exports(var);
  }

  private static void exports(String record) {
    System.out.println(record);
  }
}

The Java program above uses contextual keywords:

The program compiles and runs. It prints:

Contextual keywords example

Contextual keywords and class names

In the last section I emphasized that contextual keywords can be used in almost any place an identifier is required.

A place where a few of the contextual keywords are restricted is class names. To be precise, by 'class name' I mean the name of any type declaration, i.e, a class, an interface (annotation included), an enum or a record.

The following jshell session illustrates all of the contextual keywords that are not allowed as class names. I have edited the output slightly so it is easier to read:

jshell> interface permits {}
|  Error:
|  'permits' not allowed here
|    as of release 15, 'permits' 
|      is a restricted type name 
|      and cannot be used for type declarations
|  interface permits {}
|            ^

jshell> record record(int value){}
|  Error:
|  'record' not allowed here
|    as of release 14, 'record' 
|      is a restricted type name 
|      and cannot be used for type declarations
|  record record(int value){}
|         ^

jshell> enum sealed {INSTANCE;}
|  Error:
|  'sealed' not allowed here
|    as of release 15, 'sealed'
|      is a restricted type name 
|      and cannot be used for type declarations
|  enum sealed {INSTANCE;}
|       ^

jshell> class var {}
|  Error:
|  'var' not allowed here
|    as of release 10, 'var'
|      is a restricted type name 
|      and cannot be used for type declarations
|  class var {}
|        ^

jshell> @interface yield {}
|  Error:
|  'yield' not allowed here
|    as of release 14, 'yield'
|      is a restricted type name 
|      and cannot be used for type declarations
|  @interface yield {}
|             ^

So the following contextual keywords are not allowed: permits, record, sealed, var and yield.

As usual, this is defined by the JLS section 3.8. It defines the production TypeIdentifier:

TypeIdentifier:
  Identifier but not permits, record, sealed, var, or yield

So class names can be any identifier except those five contextual keywords.

Let's try with contextual keywords not listed in the TypeIdentifier production:

jshell> class opens {}
|  created class opens

jshell> class exports {}
|  created class exports

Other keywords work.

Contextual keywords and method names

Method names do not suffer from the same restriction of class names.

However, if you name a method yield you must observe one thing. The following class does not compile:

// does not compile!
public class YieldMethod {
  public void callYield() {
    yield(); // <-- compilation error here
  }

  void yield() {
    System.out.println("yield() called!");
  }
}

The problem is in the method callYield. Defining the class above in jshell causes the following error message:

|  Error:
|  invalid use of a restricted identifier 'yield'
|    (to invoke a method called yield, 
|        qualify the yield with a receiver or type name)
|      yield();
|      ^---^

So, to fix the compilation error, we must rewrite the callYield method to:

public void callYield() {
  this.yield();
}

Let's test this fix using jshell:

jshell> public class YieldMethod {
   ...>   public void callYield() {
   ...>     this.yield();
   ...>   }
   ...> 
   ...>   void yield() {
   ...>     System.out.println("yield() called!");
   ...>   }
   ...> }
   ...> 
|  created class YieldMethod

jshell> new YieldMethod().callYield();
yield() called!

Why though?

I don't know the reason why. But, as usual, this is defined in the Java Language Specification.

Section 15.12 defines method invocation expressions:

MethodInvocation:
  MethodName ( [ArgumentList] )
  TypeName . [TypeArguments] Identifier ( [ArgumentList] )
  ExpressionName . [TypeArguments] Identifier ( [ArgumentList] )
  Primary . [TypeArguments] Identifier ( [ArgumentList] )
  super . [TypeArguments] Identifier ( [ArgumentList] )
  TypeName . super . [TypeArguments] Identifier ( [ArgumentList] )

Notice that, except for the first form, the method name is given by an Identifier token. The first is the form of the unqualified method invocation. In the unqualified form the method name is given by:

MethodName:
  UnqualifiedMethodIdentifier

UnqualifiedMethodIdentifier:
  Identifier but not yield

So you cannot invoke a method named yield using the unqualified form.

This is the reason why, in our previous example, we had to qualify the yield method invocation.

Until the next issue of Objectos Weekly

So that's it for today. I hope you enjoyed reading.

The source code of all of the examples are in this GitHub repository.

Please send me an e-mail if you have comments, questions or corrections regarding this post.

P.S. Thank you Dr. Heinz M. Kabutz for the encouragement to start the newsletter