Annotation Interface IntentionallyStatic
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 ClassesModifier and TypeClassDescriptionstatic enumThe four sanctioned reasons to leave a field at application scope, one per terminal verdict of the sweep's decision tree. -
Required Element Summary
Required ElementsModifier and TypeRequired ElementDescriptionWhy 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
-
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 forIntentionallyStatic.Reason.COUNTER, whose verdict rests on a judgement about the app rather than a property of the field.
- Default:
""
-