Annotation Interface IntentionallyStatic


@Documented @Target(FIELD) @Retention(CLASS) public @interface IntentionallyStatic
Marks a static field that went through the migration's static sweep and is correct as a static — application scope, shared by every user, deliberately:
@IntentionallyStatic(JVM_INFRASTRUCTURE)
private static final SessionFactory sessionFactory = buildFactory();

@IntentionallyStatic(value = COUNTER, note = "IDs are internal; gaps are fine")
private static int idSequence = 1000;

The sweep's build-time gate flags every static field that is not provably immutable, and this annotation is its allowlist. A flagged field with neither an annotation nor a new home is an unfinished sweep — which is the whole point, since a mis-scoped static compiles, boots, logs no warning, and surfaces in production as another user's data.

Add it only after running the decision tree and reaching application scope. Annotating is cheaper than routing, which is exactly why value() is mandatory and closed: if none of the four reasons fits, the field does not belong here — route it.

Changes nothing at runtime. It is a claim, addressed to a reviewer and to a lint — the lint being com.vaadin.swingbridge.migration.guardrails.MigrationGuardrails (artifact com.vaadin.swingbridge:swingbridge-migration-guardrails, test scope), which implements the rule this javadoc defines and carries a fixture per category below.

What passes with no annotation — both conditions, since final governs whether the reference can change and the type governs whether the referent can: the field is final, and its type is effectively immutable (primitive or box, String, BigDecimal, BigInteger, Class, an enum, a java.time type, an immutable record, Color, Font, KeyStroke, vaadinx.text.SimpleDateFormat, a logging facade — org.slf4j.Logger, org.apache.log4j.Logger, java.util.logging.Logger). Roughly three in five statics in a real app pass here untouched; over-migration is a large diff that fixes nothing.

The logging facades and the date-format emulator are the entries that are not provably immutable, and the claim made for them is narrower.

vaadinx.text.SimpleDateFormat keeps no formatting state on the instance: every format/parse runs on a per-session backing cloned from a configuration template that freezes on first use, and reconfiguring a frozen one off a background thread throws rather than rewriting a shared formatter for every user. A shared instance is therefore safe in the only direction this rule cares about, which is why the import swap is the whole fix for the classic static final SimpleDateFormat idiom and why the migration guides tell the migrator in three places to keep that field exactly as written. Leave the type off and each of those three sentences becomes false at this gate. Note the narrowness: vaadinx.util.Calendar / vaadinx.util.GregorianCalendar do not join it — those zone their construction and are otherwise plain thread-confined calendars, so a shared mutable one stays flagged, and the answer for it is to unshare rather than to annotate.

The logging facades' claim: nobody reassigns a logger, and no logging call mutates state a migration cares about. They earn the place by measurement — the log4j-era static Logger logger = … idiom omits final and appears once per class, so loggers ran between a quarter and three-fifths of the non-final statics counted in the app we measured. Leave them off and the fix prescribed just below lands on a final field of a flagged type, so the rule contradicts itself on the commonest shape it sees.

Everything else is flagged. Any non-final static, whatever its type — public static boolean isLoggedIn is a primitive and is also the sharpest cross-user bug the sweep hunts (one login logs everybody in). Where the field is really a constant that lost its modifier — the static Logger logger = … idiom — add final rather than annotating, which lands it on the passing side above. And a final field of a mutable type: arrays, collections, containers by type argument (static final Map<String, JPanel>), models, ButtonGroup, Action, renderers, or an app-authored object holding any of those.

Transitive reachability is deliberately not part of the rule: a holder that reaches a component is already flagged for being a mutable type, so the walk buys nothing here.

This list is not the sweep's module-classification list, though it shares type names — a trap worth knowing, because a reader arriving from Step 0 will assume they match. There, Dimension / Insets / Point / Rectangle count as ordinary value objects, because the question is whether a module is UI-bearing and mutability is beside the point. Here mutability is the entire question, and all four expose public mutable fields — so static final Insets PAD is flagged, and any caller can repad every user's UI.

See Also:
  • Nested Class Summary

    Nested Classes
    Modifier and Type
    Class
    Description
    static enum 
    The four sanctioned reasons to leave a field at application scope, one per terminal verdict of the sweep's decision tree.
  • Required Element Summary

    Required Elements
    Modifier and Type
    Required Element
    Description
    Why the field is correct at application scope; one of four, because a free-text justification would make the allowlist unreviewable.
  • Optional Element Summary

    Optional Elements
    Modifier and Type
    Optional Element
    Description
     
  • Element Details

    • value

      Why the field is correct at application scope; one of four, because a free-text justification would make the allowlist unreviewable.
    • note

      String note
      Returns:
      evidence for the reason, or "". Worth writing wherever the claim is not self-evident from the declaration — always for IntentionallyStatic.Reason.COUNTER, whose verdict rests on a judgement about the app rather than a property of the field.
      Default:
      ""