Primitive type patterns - an alternative approach (JEP 507)

Brian Goetz brian.goetz at oracle.com
Thu Oct 16 16:22:38 UTC 2025


>> Zooming out, design almost always involves "lump vs split" choices; do we highlight the specific differences between cases, or their commonality?
> Another way to express this distinction is "what level of magic is acceptable?"

Heh, that's a pretty loaded way to express it.

Having semantics depend on static types is not "magic", whether or not 
the types are repeated at every line of code they are used. When we say

     int x = (int) anObject

vs

     int x = (int) aLong

the two casts to int have different semantics _based on the type of what 
is being cast_; one will attempt to cast the Object to Integer and then 
unbox (possibly CCEing), and the other will attempt to narrow the long 
to an int (possibly losing data).  And yet, they both appear to be "the 
same thing" -- casting a value to int.

Your arguments about JEP 507 could equally well be applied to the 
semantic difference between pair of statements above.  So why is this 
not a disaster, or even "magic"?  Because static types are a core part 
of the Java language!  Appealing to them, even if they are explicitly 
denoted only somewhere else, is not magic.

It would be a useful thought experiment to ask yourself why the above 
two examples don't offend you to the point of proposing new syntax.  
Because all the implicit, type-driven variation in semantics that is 
present in `anObject instanceof int x` is equally present in `int x = 
anObject`.  (In fact, it should be, because they are _the same thing_.)

So no, I can't agree that this is about "magic" at all.  Let's use the 
right word: "implicit".  Your core argument is that "too much is left 
implicit here, and therefore no one will be able to understand what is 
going on."  These sort of "it's OK now, but if we do one more thing it 
will get out of hand" arguments remind me of previous arguments around 
previous features that involved new implicit behaviors driven by static 
types, which were predicted by their detractors to be raging disasters, 
and which turned to be ... fine.

Example 1: autoboxing

Prior to Java 5, there was no implicit or explicit conversion between 
`int` and `Integer` (not even casting); boxing and unboxing were done 
manually through `new Integer(n)`, `Integer.valueOf(n)`, and 
`Integer::intValue`.  In Java 5, we added boxing and unboxing 
conversions to the list of conversions, and also, somewhat more 
controversially, supported "implicit" boxing and unboxing conversions 
(more precisely, allowing them in assignment/method context) as well as 
"explicit" boxing and unboxing conversions (casting).

Of course, some people cheered ("yay, less ceremony") but others gasped 
in horror.  An assignment that ... can throw?  What black magic is 
this?  This will make programs less reliable!   And the usual 
bargaining: "why does this have to be implicit, what's wrong with 
requiring an explicit cast?"  20 years later, this may seem comical or 
hard to believe, but there was plenty of controversy over this in its day.

While the residue of complexity this left in the spec was nontrivial 
(added to the complexity of both conversions and overload selection, 
each nontrivial areas of the language), overall this was a win for Java 
programmers.  The static type system was still in charge, clearly 
defining the semantics of our programs, but the explicit ceremony of "go 
from int to Integer" receded into the background. The world didn't end; 
Java programs didn't become wildly less reliable.  And if we asked 
people today if they wanted to go back, the answer would surely be a 
resounding "hell, no."

Example 2: local variable type inference (`var`)

The arguments on both sides of this were more dramatic; its supporters 
went on about "drowning in ceremony", while its detractors cried "too 
much! too much!", warning that Java codebases would collapse into 
unreadability due to bad programmers being unable to resist the 
temptation of implicitness.  Many strawman examples were offered as 
evidence of how unreadable Java code would become.  (To be fair, these 
people were legitimately afraid for how such a feature would be used, 
and how this would affect their experience of programming in Java, 
fearing it would be overused or abused, and that we wouldn't be able to 
reclose Pandora's box.  (But some were just misguided mudslinging, of 
course; the silliest of them was "you're turning Java into Javascript", 
when in fact type inference is based entirely on ... static types.  
Unfortunately there is no qualification exam for making strident 
arguments.))

Fortunately, some clearer arguments eventually emerged from this chaos.  
People pointed out that for many local variables, the _variable name_ 
carried far more information than the variable type, and that the 
requirement to manifestly type all variables led to distortions in how 
people coded (such as leading to more complicated and deeply nested 
expressions, that could have benefited by pulling out subexpressions 
into named variables).

In the end, it was mostly a nothingburger.  Developers learned to use 
`var` mostly responsibly, and there was no collapse in maintainability 
or readability of Java code.  The fears were unfounded.


One of the things that happens when people react to new features that 
are not immediately addressing a pain point that they happen to be 
personally in, is to focus on all the things that might go wrong.  This 
is natural and usually healthy, but one of the problems with this 
tendency is that in this situation, where the motivation of the feature 
doesn't speak directly to us, we often don't have a realistic idea of 
how and when and how often it will come up in real code.  In the absence 
of a concrete "yes, I can see 100 places I would have used this 
yesterday", we replace those with speculative, often distorted examples, 
and react to a fear of the unrealistic future they imply.

Yes, it is easy to imagine cases where something confusing could arise 
out of "so much implicitness" (though really, its not so much, its just 
new flavors of the same old stuff.)  But I will note that almost all of 
the example offered involve floating point, which mainstream Java 
developers _rarely use_.  Which casts some doubt on whether these 
examples of "look how confusing this is" are realistic.


(This might seem like a a topic change, but it is actually closer to the 
real point.)  At this point you might be tempted to argue "but then why 
don't we 'just' exclude floating point from this feature?" And the 
reason is: that would go against the _whole point_ of this feature.  
This JEP is about _regularization_.  Right now, there are all sorts of 
random and gratuitous restrictions about what types can be used where; 
we can only use reference types in instanceof, we can't switch on float, 
constant case switches are not really patterns yet, we can't use `null` 
in nested pattern context, etc etc.  Each of these restrictions or 
limitations may have been individually justifiable at the time, but in 
the aggregate, they are a pile of pure accidental complexity, make the 
language harder to use and learn, create unexpected interactions and 
gaps, and make it much much harder to evolve the language in the ways 
that Valhalla aims to, allowing the set of numeric types that can "work 
like primitives" to be expanded.  We can get to a better place, but we 
can't bring all our accidental complexity with us.

When confronted with a new feature, especially one that is not speaking 
directly to pain points one is directly experiencing, the temptation is 
to respond with a highly localized focus, one which focuses on taking 
the claimed goals of this feature and trying to make it "safer" or 
"simpler" (which usually also means "smaller".) But such localized 
responses often have two big risks: they risk missing the point of the 
feature (which is easy if it is already not speaking directly to you), 
and they risk adding new complexity elsewhere in the language in aid of 
"fixing" what seems "too much" about the feature in front of you.

This feature is about creating level ground for future work to build on 
-- constant patterns, numeric conversions between `Float16` and 
`double`, etc.  But to make these features possible, we first have to 
undo the accidental complexity of past hyperlocal feature design so that 
there can be a level ground that these features can be built on; the 
ad-hoc restrictions have to go.  This JEP may appear to create 
complicated new situations (but really, just less familiar ones), but it 
actually makes instanceof and switch _simpler_ -- both by by removing 
restrictions and by defining everything in terms of a small number of 
more fundamental concepts, rather than a larger pile of ad-hoc rules and 
restrictions.  Its hard to see that at first, so you have to give it 
time to sink in.

*If* it turns out, when we get to that future, that things are still too 
implicit for Java developers to handle, we still have the opportunity 
_then_ to offer new syntactic options for finer control over conversions 
and partiality.  But I'm not compelled by the idea of going there 
preemptively (and I honestly don't think it is actually going to be a 
problem.)



> Circling back to "what level of magic is acceptable?". The trouble
> here is that partial type patterns and unconditional type patterns
> already share the same syntax, and that is bad enough. To add in type
> conversions is just way too far. This isn't lumping, it is magic.
>
> Trying to read and decipher code with merged type checks and type
> conversions in patterns simply isn't possible without an excessive
> amount of external context, which is potentially very difficult to do
> in PRs for example.
>
> All my proposal really argues is that alternative syntaxes are
> available that make the code readable again. With ~ the visible syntax
> question becomes "if I can convert to an int ....". Other options are
> available.
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/amber-dev/attachments/20251016/727daf01/attachment-0001.htm>


More information about the amber-dev mailing list