Unify the basic primitives (
double, etc.) with objects by modeling
the basic primitive values as instances of primitive classes (introduced by
JEP 401) and repurposing the wrapper class declarations to act as the
basic primitives' class declarations. As a result of this change, all Java
values will be objects. This is a
preview language and VM feature.
Migrate the eight wrapper classes (
java.lang.Double, etc.) to be reference-favoring primitive classes.
In the Java programming language, treat basic primitive values as instances of the migrated wrapper classes, and the primitive type keywords (
double, etc.) as aliases for their primitive value types. Support method invocation, primitive reference conversion, and array covariance on these types.
In the Java virtual machine, treat the basic primitive array types as equivalent to the corresponding primitive object array types.
In the core reflection API, change the behavior of the eight
Classobjects representing the basic primitive types (
double.class, etc.) to model their class declarations.
The core functionality of primitive objects and classes is introduced by JEP 401. This JEP is only concerned with applying those features to the eight basic primitive types.
This JEP does not address the interaction of primitive value types, including
double, etc., with Java's generics. Separate JEPs will address the need for primitive value types as type arguments, and eventually optimize the performance of these parameterizations.
This JEP does not propose any new kinds of numeric primitives, or any new capabilities for Java's unary and binary operators.
Java is an object-oriented programming language, but its basic primitive values—booleans, integers, and floating-point numbers—are not objects. This was a sensible design choice when the language was created, since each object required a significant amount of overhead and indirection. But it meant that the basic primitive values did not support some of the useful features of objects such as instance methods, subtyping, and, later, generics.
As a workaround, the original standard library provided wrapper classes, each of which stored a single primitive value and presented it as an object. In Java 5, implicit boxing and unboxing conversions were introduced, transparently converting the basic primitive values to wrapper class instances, and vice versa, as required by the program.
But the wrapper class workaround is imperfect. It doesn't entirely hide the
effects of conversions—boxing the same value twice, for example, may yield two
objects that are not
== to each other. More importantly, in many applications
wrapping primitive values in objects has significant runtime costs, and
developers must weigh those costs against the benefit of greater
The primitive objects feature, introduced by JEP 401, eliminates most of the overhead of modeling identity-free values as objects. As a result, it is now practical to treat the basic primitive values as first-class objects in all contexts. At last, we can claim that every value is an object!
Each primitive object needs a primitive class. To which class should the
values belong? A lot of existing code assumes that an
Object modeling a basic
primitive value will belong to a wrapper class. Since there is no longer any
need to wrap basic primitive values, we can minimize disruption by repurposing
the wrapper classes to treat
int values as instances of
boolean values as instances of
By defining the basic primitive types with primitive class declarations, we are able to provide them with instance methods and integrate them into the class subtyping graph. Interoperability of primitive value types with generics will be pursued in a future JEP.
The features described below are preview features, enabled with the
--enable-preview compile-time and runtime flags.
Basic primitive classes
The eight basic primitive classes are the following:
The compiler and bootstrap class loader use special logic to locate these class files; when preview features are enabled, modified versions of the classes are located.
The modified versions are primitive classes. They are reference-favoring, meaning
Double, etc., continue to refer to the reference types of the
public constructors of these classes were deprecated for removal in Java
16 by JEP 390. To avoid subtle binary compatibility issues involving
identity and primitive class constructors being compiled differently, the
constructors in the modified classes are
Java language model
The eight primitive type keywords—
double—are now aliases for the basic primitive classes,
and for the corresponding primitive value types. The
.ref syntax can be used
to refer to the corresponding reference types.
Because these are aliases, there are two ways to refer to each class, value type, and reference type, as outlined in the following table:
Primitive class Value type Reference type
As a matter of style, the lower-cased, keyword-based convention is preferred.
The restrictions on primitive class declarations include a special exception for
the basic primitive classes: It is permitted for a basic primitive class to
recursively declare an instance field with its own primitive value type. (For
int class has a field of type
Java supports a number of conversions between different basic primitive value
types, such as
double; those behaviors are unchanged. For clarity, we
now call them widening numeric conversions and narrowing numeric
conversions. There are no similar conversions between reference types, such as
The boxing and unboxing conversions are superseded by primitive classes' primitive reference and primitive value conversions. The supported types are the same, but the runtime behavior is more efficient.
Java provides a number of unary and binary operators for manipulating basic
primitive values (e.g.,
!true). The rules and behaviors of these
operators are unchanged.
Because the basic primitive values are objects, they also have instance methods,
as defined by their class declarations. Code such as
23.compareTo(42) is now
legal. (To do: does this introduce any parsing problems? And do the behaviors of
compareTo make sense?)
As with other primitive value types, arrays of basic primitive value types are
int can now be treated as an
Compilation and run time
In the JVM, the basic primitive types are distinct from primitive class types:
D represents 64-bit floating-point values that span two stack slots
and support a full suite of dedicated opcodes (
dcmpg, etc.), while the type
Qjava/lang/Double$val; represents primitive
objects of class
Double that span a single stack slot and respond to the
object opcodes (
A Java compiler is responsible for adapting between the two types as needed, via
methods such as
Double.doubleValue. The resulting
bytecode will look similar to boxing and unboxing code, but the runtime overhead
is greatly reduced.
For consistency, basic primitive value types appearing in field types and method
signatures are always translated to basic primitive JVM types (
Compiler adaptations are not sufficient for basic primitive arrays. For example,
an array of type
[D created with
newarray may be passed to a method
[Ljava/lang/Double;, and an array of type
anewarray may be cast to type
[D. To support this behavior, the
JVM treats the types
[Qjava/lang/Double$val; as compatible with each
other, and supports both families of opcodes on their values (
aastore), regardless of how the arrays were created.
There are two
Class objects that developers will typically encounter for each
basic primitive class. In the case of class
double, these are:
Double.val.class), corresponding to the JVM descriptor type
isPrimitive(). When preview features are enabled, to align with the language model, this object uses the
java.lang.Double$valclass declaration to respond to most queries (e.g.,
double.ref.class), corresponding to the JVM descriptor type
isPrimitive(). Behaves like a standard
Classobject modeling a primitive reference type.
getClass method of a basic primitive object returns a
Class object of
the first kind—
int.class, etc. As with all primitive objects,
the method's result is the same whether invoked via the value type
(23.0).getClass()) or the reference type (
is a behavioral change that may break some
val.getClass().equals(Double.class) is not a safe substitute for
val instanceof Double.
Class object exists, corresponding to the JVM descriptor type
Qjava/lang/Double$val;, but is rarely useful in practice since a Java
compiler never names this type in a descriptor. There is no class literal for this object.
isPrimitive(), and behaves like a standard
modeling a primitive value type.
The language could be left unchanged—primitive objects are a useful feature without treating the basic primitive values as objects. But it will be useful to eliminate the rift between basic primitives and objects, especially as Java's generics are enhanced to work with primitive objects.
New classes could be introduced as the basic primitive classes
java.lang.int, say), leaving the wrapper classes behind as a legacy API. But
assumptions about boxing behavior run deep in some code, and a new set of
classes would break those programs.
The JVM could follow the Java language in fully unifying its basic primitive
D, etc.) with its primitive class types
Qjava/lang/Double$val;, etc.) But this would be an
expensive change for little ultimate benefit. For example, there would have to
be a way to reconcile the two-slot size of type
D with the single-slot size of
Qjava/lang/Double$val;, perhaps requiring a disruptive versioned change
to the class file format.
Risks and Assumptions
Removing the wrapper class constructors breaks binary compatibility for a significant subset of legacy Java programs. There are also behavioral changes associated with migration to primitive classes. JEP 390, along with some expected followup efforts, mitigates these concerns. But some programs that invoke the constructors or rely on boxed object identity will break.
Changes in reflection behavior, due to the new status of basic primitive types
as class types, may cause problems for some programs. The existence of a
distinct class object representing the type
Qjava/lang/Double$val; is easy to
overlook and may catch some developers by surprise.
JEP 401 (Primitive Objects) is a prerequisite.
In anticipation of this feature we already added warnings about potential
incompatible changes to primitive class candidates to
javac and HotSpot, via
JEP 390. Some followup work will come in additional JEPs.
We anticipate modifying the generics model in Java to make type parameters universal—instantiable by all types, both reference and value. This will be pursued in a separate JEP.