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 <[email protected]> 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.)


Reply via email to