Type qualifier annotations for the Checker Framework static analysis tool
—
String and format annotations validate string formats, regular expressions, and format strings to prevent runtime errors from malformed strings, invalid regex patterns, and mismatched format arguments.
Annotations for validating regular expression patterns.
/**
* Indicates that the annotated String is a valid regular expression
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
@SubtypeOf(UnknownRegex.class)
public @interface Regex {}
/**
* Indicates a partial regular expression (part of a larger pattern)
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
@SubtypeOf(Regex.class)
public @interface PartialRegex {}
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
@SubtypeOf({})
@DefaultQualifierInHierarchy
public @interface UnknownRegex {}
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
@SubtypeOf(Regex.class)
public @interface RegexBottom {}
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
@PolymorphicQualifier(UnknownRegex.class)
public @interface PolyRegex {}Usage Examples:
import org.checkerframework.checker.regex.qual.*;
import java.util.regex.Pattern;
public class RegexExample {
// Method requires a valid regex pattern
public Pattern compile(@Regex String pattern) {
return Pattern.compile(pattern); // Safe - pattern is validated
}
// Method that validates and returns regex
public @Regex String createEmailPattern() {
return "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}";
}
// Polymorphic method preserves regex properties
public @PolyRegex String process(@PolyRegex String input) {
return input.trim();
}
public void example() {
@Regex String emailRegex = createEmailPattern();
Pattern emailPattern = compile(emailRegex); // Safe
String userInput = getUserInput(); // Unknown if valid regex
// compile(userInput); // Would cause type error
if (isValidRegex(userInput)) {
@Regex String validPattern = (@Regex String) userInput;
Pattern pattern = compile(validPattern); // Safe after validation
}
}
private boolean isValidRegex(String pattern) {
try {
Pattern.compile(pattern);
return true;
} catch (Exception e) {
return false;
}
}
}Annotations for validating printf-style format strings and their arguments.
/**
* Indicates a valid format string with specified conversion categories
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
@SubtypeOf(UnknownFormat.class)
public @interface Format {
ConversionCategory[] value();
}
/**
* Indicates an invalid format string
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
@SubtypeOf(UnknownFormat.class)
public @interface InvalidFormat {}
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
@SubtypeOf({})
@DefaultQualifierInHierarchy
public @interface UnknownFormat {}
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
@SubtypeOf(Format.class)
public @interface FormatBottom {}
/**
* Method that returns a format string
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ReturnsFormat {}
/**
* Method that uses format strings (like printf)
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FormatMethod {}
/**
* Categories of printf conversion specifiers
*/
public enum ConversionCategory {
UNUSED, // %% - literal %
GENERAL, // %s, %b, %h - any type
CHAR, // %c - char, Character, byte, Byte, short, Short, int, Integer
INT, // %d, %o, %x - integral types
FLOAT, // %e, %f, %g - floating point types
TIME, // %t - date/time types
CHAR_AND_INT, // %c or %d
INT_AND_TIME // %d or %t
}Usage Examples:
import org.checkerframework.checker.formatter.qual.*;
public class FormatExample {
// Method that requires a format string with specific argument types
@FormatMethod
public void logMessage(@Format({ConversionCategory.GENERAL, ConversionCategory.INT}) String format,
Object message, int count) {
System.out.printf(format, message, count); // Safe - format matches arguments
}
@ReturnsFormat({ConversionCategory.GENERAL, ConversionCategory.FLOAT})
public String createFormatString() {
return "User: %s, Score: %.2f";
}
public void example() {
// Valid format strings
@Format({ConversionCategory.GENERAL}) String nameFormat = "Hello, %s!";
@Format({ConversionCategory.INT, ConversionCategory.FLOAT}) String scoreFormat = "Score: %d/%.1f";
// Safe usage
System.out.printf(nameFormat, "Alice");
System.out.printf(scoreFormat, 85, 100.0);
// Using method-created format
@Format({ConversionCategory.GENERAL, ConversionCategory.FLOAT}) String userFormat = createFormatString();
System.out.printf(userFormat, "Bob", 92.5);
// Log with specific format requirements
logMessage("Processing %s: %d items", "data.txt", 150);
}
// Method that validates and converts unknown format strings
public @Format({ConversionCategory.GENERAL}) String validateSimpleFormat(String input) {
if (input.matches("^[^%]*%s[^%]*$")) {
return (@Format({ConversionCategory.GENERAL}) String) input;
}
throw new IllegalArgumentException("Invalid format string");
}
}Annotations for Java signature strings used in reflection and bytecode operations.
/**
* Java binary name (e.g., "java.lang.Object", "java.lang.Object$Inner")
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
@SubtypeOf(SignatureUnknown.class)
public @interface BinaryName {}
/**
* Java canonical name (e.g., "java.lang.Object", "java.lang.Object.Inner")
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
@SubtypeOf(SignatureUnknown.class)
public @interface CanonicalName {}
/**
* JVM field descriptor (e.g., "I", "Ljava/lang/String;")
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
@SubtypeOf(SignatureUnknown.class)
public @interface FieldDescriptor {}
/**
* JVM method descriptor (e.g., "(I)V", "(Ljava/lang/String;)I")
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
@SubtypeOf(SignatureUnknown.class)
public @interface MethodDescriptor {}
/**
* Java identifier (valid Java name)
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
@SubtypeOf(SignatureUnknown.class)
public @interface Identifier {}
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
@DefaultQualifierInHierarchy
public @interface SignatureUnknown {}
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
@SubtypeOf(BinaryName.class)
public @interface SignatureBottom {}
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
@PolymorphicQualifier(SignatureUnknown.class)
public @interface PolySignature {}Usage Examples:
import org.checkerframework.checker.signature.qual.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class SignatureExample {
// Method that requires a valid Java binary name
public Class<?> loadClass(@BinaryName String className) throws ClassNotFoundException {
return Class.forName(className); // Safe - className is valid binary name
}
// Method that requires a field descriptor
public void analyzeField(@FieldDescriptor String descriptor) {
// Parse JVM field descriptor safely
char typeChar = descriptor.charAt(0);
switch (typeChar) {
case 'I': System.out.println("int field"); break;
case 'L': System.out.println("object field"); break;
// ... other cases
}
}
// Convert canonical name to binary name
public @BinaryName String canonicalToBinary(@CanonicalName String canonical) {
return canonical.replace('.', '$'); // Convert inner class separator
}
public void reflectionExample() throws Exception {
// Valid binary names
@BinaryName String stringClass = "java.lang.String";
@BinaryName String innerClass = "java.util.Map$Entry";
Class<?> clazz1 = loadClass(stringClass); // Safe
Class<?> clazz2 = loadClass(innerClass); // Safe
// Field descriptors
@FieldDescriptor String intDescriptor = "I";
@FieldDescriptor String stringDescriptor = "Ljava/lang/String;";
analyzeField(intDescriptor); // Safe
analyzeField(stringDescriptor); // Safe
// Method descriptors for reflection
@MethodDescriptor String voidMethod = "()V";
@MethodDescriptor String stringToInt = "(Ljava/lang/String;)I";
// Use in reflection calls (conceptual)
// Method method = getMethodByDescriptor(clazz1, stringToInt);
}
// Validate Java identifier
public boolean isValidIdentifier(@Identifier String name) {
return Character.isJavaIdentifierStart(name.charAt(0)) &&
name.chars().skip(1).allMatch(Character::isJavaIdentifierPart);
}
}Annotations for internationalized format strings and message keys.
/**
* Valid internationalized format string
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
@SubtypeOf(I18nUnknownFormat.class)
public @interface I18nFormat {
I18nConversionCategory[] value();
}
/**
* Invalid internationalized format string
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
@SubtypeOf(I18nUnknownFormat.class)
public @interface I18nInvalidFormat {}
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
@DefaultQualifierInHierarchy
public @interface I18nUnknownFormat {}
/**
* I18n conversion categories (similar to printf categories)
*/
public enum I18nConversionCategory {
UNUSED,
GENERAL,
DATE,
NUMBER
}
/**
* Valid localizable key for resource bundles
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
@SubtypeOf(UnknownLocalizableKey.class)
public @interface LocalizableKey {}
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
@DefaultQualifierInHierarchy
public @interface UnknownLocalizableKey {}
/**
* String that has been localized
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
@SubtypeOf(UnknownLocalized.class)
public @interface Localized {}
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
@DefaultQualifierInHierarchy
public @interface UnknownLocalized {}Usage Examples:
import org.checkerframework.checker.i18n.qual.*;
import org.checkerframework.checker.i18nformatter.qual.*;
import java.util.ResourceBundle;
import java.text.MessageFormat;
public class I18nExample {
private ResourceBundle bundle = ResourceBundle.getBundle("messages");
// Method that requires a valid localizable key
public @Localized String getMessage(@LocalizableKey String key) {
return bundle.getString(key); // Safe - key is valid
}
// Method that formats i18n messages
public @Localized String formatMessage(@I18nFormat({I18nConversionCategory.GENERAL, I18nConversionCategory.NUMBER}) String pattern,
Object name, Number count) {
return MessageFormat.format(pattern, name, count); // Safe - format matches arguments
}
public void example() {
// Valid localizable keys (would be validated against resource bundle)
@LocalizableKey String welcomeKey = "welcome.message";
@LocalizableKey String errorKey = "error.file_not_found";
// Get localized strings
@Localized String welcome = getMessage(welcomeKey);
@Localized String error = getMessage(errorKey);
// Valid i18n format strings
@I18nFormat({I18nConversionCategory.GENERAL}) String nameFormat = "Hello, {0}!";
@I18nFormat({I18nConversionCategory.GENERAL, I18nConversionCategory.NUMBER})
String countFormat = "Processing {0}: {1} items";
// Safe formatting
@Localized String greeting = formatMessage(nameFormat, "Alice");
@Localized String status = formatMessage(countFormat, "data.txt", 150);
System.out.println(greeting);
System.out.println(status);
}
}Annotations for validating property keys in configuration files.
/**
* Valid property key for configuration files
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
@SubtypeOf(UnknownPropertyKey.class)
public @interface PropertyKey {}
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
@DefaultQualifierInHierarchy
public @interface UnknownPropertyKey {}
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
@SubtypeOf(PropertyKey.class)
public @interface PropertyKeyBottom {}Usage Examples:
import org.checkerframework.checker.propkey.qual.*;
import java.util.Properties;
public class PropertyExample {
private Properties config = new Properties();
// Method that requires a valid property key
public String getProperty(@PropertyKey String key) {
return config.getProperty(key); // Safe - key is validated
}
public void configure() {
// Valid property keys (would be validated against properties file)
@PropertyKey String dbUrlKey = "database.url";
@PropertyKey String dbUserKey = "database.username";
@PropertyKey String dbPassKey = "database.password";
// Safe property access
String dbUrl = getProperty(dbUrlKey);
String dbUser = getProperty(dbUserKey);
String dbPass = getProperty(dbPassKey);
// Use configuration values
connectToDatabase(dbUrl, dbUser, dbPass);
}
private void connectToDatabase(String url, String user, String password) {
// Database connection logic
}
}Install with Tessl CLI
npx tessl i tessl/maven-org-checkerframework--checker-qual