Reflective Check of a Non-Runtime Annotation

ID

java.reflection_check_non_runtime_annot

Severity

high

Remediation Complexity

medium

Remediation Risk

low

Remediation Effort

medium

Resource

Code Smell

Language

Java

Tags

reliability

Description

Reports a call to isAnnotationPresent(X.class) where the annotation type X does not carry RUNTIME retention. Only annotations declared @Retention(RetentionPolicy.RUNTIME) are kept in the loaded class and available to reflection; SOURCE and CLASS retained annotations (CLASS is also the default when @Retention is omitted) are discarded before the check can ever see them.

Rationale

A reflective presence check against a non-runtime annotation always evaluates to false, regardless of how the inspected element is actually annotated in source:

@Retention(RetentionPolicy.CLASS)
@interface LegacyCompatShim { }

void inspect(Method handler) {
  if (handler.isAnnotationPresent(LegacyCompatShim.class)) { // always false
    // this branch never runs, even when @LegacyCompatShim is present in source
  }
}

The same happens with a handful of well-known JDK annotations that keep SOURCE retention, such as @Override, @SuppressWarnings and @SafeVarargs.

Remediation

Give the annotation RUNTIME retention so the JVM keeps it available to reflection:

@Retention(RetentionPolicy.RUNTIME)
@interface LegacyCompatShim { }

void inspect(Method handler) {
  if (handler.isAnnotationPresent(LegacyCompatShim.class)) { // now reflects the real annotation state
    // ...
  }
}

© 2026 Xygeni. All rights reserved