Ad hoc type restriction

Brian Goetz brian.goetz at oracle.com
Wed Oct 15 01:09:46 UTC 2025


There's nothing wrong with annotations being scanned by frameworks.  
Indeed, the entire point of annotations is to allow code authors to 
decorate source declarations with "structured comments" in a way that is 
scrutable, both statically and dynamically, to frameworks and tooling*.  
What annotations are _not_ for is to impart semantics _at the Java 
language level_.  Annotation plumbing is a service the language and 
compiler perform for the benefit of libraries and frameworks, but it 
recuses itself from being a beneficiary of that service.


(*At this point someone will typically pipe in "But the compiler is a 
tool.  Ha!  I am very clever."  But remember, the Java compiler has no 
discretion whatsoever about program semantics. That discretion belongs 
purely to the language specification.)

On 10/14/2025 8:49 PM, Olexandr Rotan wrote:
>
>     The next question in this dialog (which I've only had a few
>     zillion times) is "what about frameworks that use reflection to
>     drive semantics."  But that one kind of answers itself when you
>     think about it, so I'll just skip ahead now.)
>
>
> Just out of curiosity, what was the motivation behind the annotations 
> with runtime retention if they are not expected to be scanned for by 
> frameworks? Even if talking about things like aspect-oriented 
> programming, if advice does not alter the behaviour of the invocation, 
> it will most likely be designed to produce some side-effect, which is 
> also a semantics change
>
> On Tue, Oct 14, 2025 at 7:32 PM Brian Goetz <brian.goetz at oracle.com> 
> wrote:
>
>>     WHAT I WANT: To be able to instead say this:
>>
>>         public void dial(@PhoneNumber String number) {
>>             ... // do whatever
>>         }
>>
>>     AND have the following be true:
>>
>>       * At compile time...
>>           o I get a warning or error if any code tries to invoke
>>             dial() with a "plain" String parameter, or assign a plain
>>             String to a @PhoneNumber String
>>           o There is some well-defined, compiler-sanctioned way to
>>             validate a phone number, using custom logic I define, so
>>             I can assign it to a @PhoneNumber String without said
>>             error/warning. Even if it involves @SuppressWarnings,
>>             I'll take it.
>>       * At runtime...
>>           o No explicit check of thenumber parameter is performed by
>>             the dial() method (efficiency)
>>           o Thedial() method is guaranteed (modulo sneaky tricks)
>>             that number is always a valid phone number
>>
>>     Obviously you can replace @PhoneNumber with any other assertion.
>>     For example:public void editProfile(@LoggedIn User user) { ... }
>>
>>     Is the above possible using the checker framework? I couldn't
>>     figure out how, though that may be due to my own lack of ability.
>
>     Yes, but you get no implicit conversion from String to
>     @PhoneNumber String -- you have to call a method to explicitly do
>     the conversion:
>
>         @PhoneNumber String validatePhoneNumber(String s) { ... do the
>     thing ... }
>
>     This is just a function from String -> @PN String, which just
>     happens to preserve its input after validating it (or throws if
>     validation fails.)
>
>     A custom checker can validate that you never assign to, pass,
>     return, or cast a non-PN String when a PN String is expected, and
>     generate diagnostics accordingly (warnings or errors, as you like.)
>
>>     But even if it is possible via checker framework or otherwise, I
>>     don't see this being done in any widespread fashion, which seems
>>     like pretty strong evidence that it's too hard.
>
>     It's not that hard, but it _is_ hard to get people to adopt this
>     stuff.  Very few anno-driven type system extensions have gained
>     any sort of adoption, even if they are useful and sound.  (And
>     interestingly, a corpus search found that the vast majority of
>     those that are used have to do with nullity management.)
>
>     Why don't these things get adopted?   Well, friction is definitely
>     a part of it.  You have to set up a custom toolchain
>     configuration.  You have to do some work to satisfy the stricter
>     type system, which is often fussy and annoying, especially if you
>     are trying to add it to existing code.  You have to program in a
>     dialect, often one that is underspecified.   Libraries you use
>     won't know that dialect, so at every boundary between your code
>     and library code that might result in a new PhoneNumber being
>     exchanged, you have to introduce some extra code or assertion at
>     the boundary. And to many developers, this sounds like a lot of
>     extra work to get marginally increased confidence.
>
>     There is similar data to observe in less invasive static analysis,
>     too.  When people first encounter a good static analysis tool,
>     they get really excited, it finds a bunch of bugs fairly quickly,
>     and they want to build it into their methodology.  But somewhere
>     along the line, it falls away. Part of it is the friction (you
>     have to run it in your CI, and on each developer workstation, with
>     the same configuration), and part of it is diminishing returns. 
>     But most developers don't feel like they are getting enough for
>     the effort.
>
>     Of course, the more we can decrease the friction, the lower the
>     payback has to be to make it worthwhile.
>
>>     But I think it's OK for certain "sticky notes" to be understood
>>     by the compiler, and have the compiler offer corresponding
>>     assistance in verifying them (which it is already doing - see
>>     below). I also agree that having annotations affect the generated
>>     bytecode ("runtime semantics") is a big step beyond that, but
>>     maybe that's not necessary in this case.
>
>     There are a few "sticky notes" that the "compiler" does in fact
>     understand, such as @Override or @FunctionalInterface. (I put
>     "compiler" in quotes because the compiler doesn't get to have an
>     opinion about anything semantic; that's the language spec's job.) 
>     But these have a deliberately limited, narrow role: they capture
>     scrutable structural assertions that require (per language spec!)
>     the compiler to statically reject some programs that don't conform
>     to the assertions, but they never have any lingusitic semantics
>     for correct programs.   That is, for a correct program P with
>     annotations, stripping all annotations out of P MUST produce a
>     semantically equivalent program.  (The next question in this
>     dialog (which I've only had a few zillion times) is "what about
>     frameworks that use reflection to drive semantics."  But that one
>     kind of answers itself when you think about it, so I'll just skip
>     ahead now.)
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/amber-dev/attachments/20251014/25e6087f/attachment-0001.htm>


More information about the amber-dev mailing list