Java 11 - Nest-Based Access Control (JEP 181)

In Java 11, the JVM supports the arrangement of classes and interfaces into a new access control context, called a nest. Nests allow classes and interfaces that are logically part of the same code entity, but which are compiled to distinct class files, to access each other's private members without the need for compilers to insert accessibility-broadening bridge methods. Nests are a low-level mechanism of the Java SE Platform; there are no changes to the access control rules of the Java programming language. This is purely a Java class bytecode level change (that's why this changes categorized under hotspot/runtime).

Let's check JEP181Reflection to understand this feature:

JEP181Reflection.java
package com.dariawan.jdk11;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

public class JEP181Reflection {

    private class Nest {

        private int intNest;
    }

    public static void main(String[] args)
            throws NoSuchFieldException,
            InvocationTargetException,
            IllegalAccessException {
        
        JEP181Reflection me = new JEP181Reflection();        
        Nest n = me.new Nest();
        
        // this is working
        n.intNest = 11;
        System.out.println("Nest.intNest = " + n.intNest);
        
        // this is not working before Java 11
        final Field f = Nest.class.getDeclaredField("intNest");
        f.setInt(n, 2018);
        System.out.println("Nest.intNest = " + n.intNest);
    }
}
                    

If you run this code with Java version prior 11, then you get IllegalAccessException. The outputs are similar to this:

Nest.intNest = 11 Exception in thread "main" java.lang.IllegalAccessException: Class com.dariawan.jdk11.JEP181Reflection can not access a member of class com.dariawan.jdk11.JEP181Reflection$Nest with modifiers "private" at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102) at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296) at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288) at java.lang.reflect.Field.setInt(Field.java:946) at com.dariawan.jdk11.JEP181Reflection.main(JEP181Reflection.java:27)

The javac compiler has been updated to use nests when compiling nested classes and interfaces in Java source code, by generating new class files attributes that place a top-level class (or interface) and all its nested classes and interfaces in the same nest. The Java Virtual Machine has been updated to use these attributes when checking the accessibility of a private constructor, method, or field, including via core reflection and the java.lang.invoke.MethodHandles.Lookup API. Membership in a nest is exposed through the new getNestHost and getNestMembers methods of java.lang.Class.

And when we compile it using Java 11 and run it, we will get:

Nest.intNest = 11 Nest.intNest = 2018

Analyzing Bytecode

When we compile JEP181Reflection with a pre Java 11 version (In my case, it's Java 8) I got three class files:

JEP181Reflection$1.class JEP181Reflection$Nest.class JEP181Reflection.class

And when analyze the compiled bytecodes via javap:

$ javap com.dariawan.jdk11.JEP181Reflection$Nest
Compiled from "JEP181Reflection.java" class com.dariawan.jdk11.JEP181Reflection$Nest { final com.dariawan.jdk11.JEP181Reflection this$0; com.dariawan.jdk11.JEP181Reflection$Nest(com.dariawan.jdk11.JEP181Reflection, com.dariawan.jdk11.JEP181Reflection$1); static int access$102(com.dariawan.jdk11.JEP181Reflection$Nest, int); static int access$100(com.dariawan.jdk11.JEP181Reflection$Nest); }
$ javap -v com.dariawan.jdk11.JEP181Reflection$Nest
Classfile /D:/Projects/dallanube/dariawan/target/classes/com/dariawan/jdk11/JEP181Reflection$Nest.class Last modified 21 Jan, 2019; size 1081 bytes MD5 checksum 46745b115522203ff8351229ecc5bda8 Compiled from "JEP181Reflection.java" class com.dariawan.jdk11.JEP181Reflection$Nest minor version: 0 major version: 52 flags: ACC_SUPER Constant pool: *** CONSTANT POOL PRINTED HERE *** { final com.dariawan.jdk11.JEP181Reflection this$0; descriptor: Lcom/dariawan/jdk11/JEP181Reflection; flags: ACC_FINAL, ACC_SYNTHETIC com.dariawan.jdk11.JEP181Reflection$Nest(com.dariawan.jdk11.JEP181Reflection, com.dariawan.jdk11.JEP181Reflection$1); descriptor: (Lcom/dariawan/jdk11/JEP181Reflection;Lcom/dariawan/jdk11/JEP181Reflection$1;)V flags: ACC_SYNTHETIC Code: stack=2, locals=3, args_size=3 0: aload_0 1: aload_1 2: invokespecial #2 // Method "<init>":(Lcom/dariawan/jdk11/JEP181Reflection;)V 5: return LineNumberTable: line 8: 0 LocalVariableTable: Start Length Slot Name Signature 0 6 0 this Lcom/dariawan/jdk11/JEP181Reflection$Nest; 0 6 1 x0 Lcom/dariawan/jdk11/JEP181Reflection; 0 6 2 x1 Lcom/dariawan/jdk11/JEP181Reflection$1; static int access$102(com.dariawan.jdk11.JEP181Reflection$Nest, int); descriptor: (Lcom/dariawan/jdk11/JEP181Reflection$Nest;I)I flags: ACC_STATIC, ACC_SYNTHETIC Code: stack=3, locals=2, args_size=2 0: aload_0 1: iload_1 2: dup_x1 3: putfield #1 // Field intNest:I 6: ireturn LineNumberTable: line 8: 0 LocalVariableTable: Start Length Slot Name Signature 0 7 0 x0 Lcom/dariawan/jdk11/JEP181Reflection$Nest; 0 7 1 x1 I static int access$100(com.dariawan.jdk11.JEP181Reflection$Nest); descriptor: (Lcom/dariawan/jdk11/JEP181Reflection$Nest;)I flags: ACC_STATIC, ACC_SYNTHETIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: getfield #1 // Field intNest:I 4: ireturn LineNumberTable: line 8: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 x0 Lcom/dariawan/jdk11/JEP181Reflection$Nest; } SourceFile: "JEP181Reflection.java" InnerClasses: static #20; //class com/dariawan/jdk11/JEP181Reflection$1

As seen above, to access nested class field 'Nest.intNest', bridge package-private method access$102 and access$100 are syntactically generated. This is because of the reason that the outer and nested classes are compiled to different files and they need package-private visibility to access each other's private members (instance/static). From a user perspective, however, these two classes are considered to be in "the same class", and therefore users expect them to be accessed via a single common access control mechanism.

Now, when we compile JEP181Reflection with a Java 11 version I got two class files:

JEP181Reflection$Nest.class JEP181Reflection.class

Then we do similar javap to analyze compiled bytecodes:

$ javap com.dariawan.jdk11.JEP181Reflection$Nest
Compiled from "JEP181Reflection.java" class com.dariawan.jdk11.JEP181Reflection$Nest { final com.dariawan.jdk11.JEP181Reflection this$0; }
$ javap -v com.dariawan.jdk11.JEP181Reflection$Nest
Classfile /D:/Projects/dallanube/dariawan11/target/classes/com/dariawan/jdk11/JEP181Reflection$Nest.class Last modified 21 Jan 2019; size 556 bytes MD5 checksum 607c78372ec1ae340f6ec1c5d67a4e09 Compiled from "JEP181Reflection.java" class com.dariawan.jdk11.JEP181Reflection$Nest minor version: 0 major version: 55 flags: (0x0020) ACC_SUPER this_class: #3 // com/dariawan/jdk11/JEP181Reflection$Nest super_class: #4 // java/lang/Object interfaces: 0, fields: 2, methods: 1, attributes: 3 Constant pool: *** CONSTANT POOL PRINTED HERE *** { final com.dariawan.jdk11.JEP181Reflection this$0; descriptor: Lcom/dariawan/jdk11/JEP181Reflection; flags: (0x1010) ACC_FINAL, ACC_SYNTHETIC } SourceFile: "JEP181Reflection.java" NestHost: class com/dariawan/jdk11/JEP181Reflection

The Java 8 compiler generates the access$100 and access$102 methods, but Java 11 compiler does not. Instead, it has a nesting host field in the class file (See: NestHost: class com/dariawan/jdk11/JEP181Reflection). Now, we also javap JEP181Reflection to analyze the bytecode:

Classfile /D:/Projects/dallanube/dariawan11/target/classes/com/dariawan/jdk11/JEP181Reflection.class Last modified 21 Jan 2019; size 1805 bytes MD5 checksum 0e2ec1fdfdec9b3d96a6758344418555 Compiled from "JEP181Reflection.java" public class com.dariawan.jdk11.JEP181Reflection minor version: 0 major version: 55 flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #2 // com/dariawan/jdk11/JEP181Reflection super_class: #14 // java/lang/Object interfaces: 0, fields: 0, methods: 2, attributes: 4 Constant pool: *** CONSTANT POOL PRINTED HERE *** { public com.dariawan.jdk11.JEP181Reflection(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 6: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/dariawan/jdk11/JEP181Reflection; public static void main(java.lang.String[]) throws java.lang.NoSuchFieldException, java.lang.reflect.InvocationTargetException, java.lang.IllegalAccessException; descriptor: ([Ljava/lang/String;)V flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=4, locals=4, args_size=1 *** CODE PRINTED HERE *** LineNumberTable: *** LINENUMBERTABLE PRINTED HERE *** LocalVariableTable: Start Length Slot Name Signature 0 75 0 args [Ljava/lang/String; 8 67 1 me Lcom/dariawan/jdk11/JEP181Reflection; 22 53 2 n Lcom/dariawan/jdk11/JEP181Reflection$Nest; 51 24 3 f Ljava/lang/reflect/Field; Exceptions: throws java.lang.NoSuchFieldException, java.lang.reflect.InvocationTargetException, java.lang.IllegalAccessException } SourceFile: "JEP181Reflection.java" NestMembers: com/dariawan/jdk11/JEP181Reflection$Nest InnerClasses: public static final #89= #88 of #91; // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles BootstrapMethods: 0: #50 REF_invokeStatic java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite; Method arguments: #51 Nest.intNest = \u0001

In here we get:

NestMembers: com/dariawan/jdk11/JEP181Reflection$Nest

And with this the connection between NestHost and NestMembers established, and with that the runtime can detect if two classes are NestMates or not.

Conclusion

Java 11 introduces JVM level support for private access within outer/nested classes via NestMembers and NestHost attributes. NestMembers are referring to nested classes and NestHost is referring to the enclosing outer class. They are generally called Nests or Nestmates. Outer and nested classes are linked by these attributes rather than synthetically generated package-private bridge methods as in previous version.

Although JEP-181 is a small change in Java and most of the developers will not even notice this, it's better to know how our compiler and runtime (JVM) works, and how we can maximize this features.