Generated immutable value classes for Java 8+ using annotation processing
—
AutoAnnotation generates proper implementations of annotation interfaces with correct equals(), hashCode(), and toString() methods that conform to the Java annotation specification.
// Annotation interface
public @interface Named {
String value();
}
// Factory method with @AutoAnnotation
public class Annotations {
@AutoAnnotation
public static Named named(String value) {
return new AutoAnnotation_Annotations_named(value);
}
}Named annotation = Annotations.named("example");
System.out.println(annotation); // @Named(value="example")
System.out.println(annotation.value()); // "example"
// Proper equals() behavior
Named same = Annotations.named("example");
Named different = Annotations.named("other");
System.out.println(annotation.equals(same)); // true
System.out.println(annotation.equals(different)); // false// Complex annotation
public @interface ApiEndpoint {
String path();
String method() default "GET";
boolean authenticated() default true;
String[] produces() default {};
}
// Factory method
public class Annotations {
@AutoAnnotation
public static ApiEndpoint apiEndpoint(
String path,
String method,
boolean authenticated,
String[] produces) {
return new AutoAnnotation_Annotations_apiEndpoint(path, method, authenticated, produces);
}
}Usage:
ApiEndpoint endpoint = Annotations.apiEndpoint(
"/api/users",
"POST",
true,
new String[]{"application/json"});
System.out.println(endpoint.path()); // "/api/users"
System.out.println(endpoint.method()); // "POST"
System.out.println(endpoint.authenticated()); // true
System.out.println(Arrays.toString(endpoint.produces())); // ["application/json"]Factory methods can omit parameters that have default values:
public @interface RequestMapping {
String path();
String method() default "GET";
int timeout() default 5000;
}
public class Annotations {
// Full parameter version
@AutoAnnotation
public static RequestMapping requestMapping(String path, String method, int timeout) {
return new AutoAnnotation_Annotations_requestMapping(path, method, timeout);
}
// Convenience method using defaults
@AutoAnnotation
public static RequestMapping requestMapping(String path) {
return new AutoAnnotation_Annotations_requestMapping(path, "GET", 5000);
}
// Partial defaults
@AutoAnnotation
public static RequestMapping requestMapping(String path, String method) {
return new AutoAnnotation_Annotations_requestMapping(path, method, 5000);
}
}Usage:
RequestMapping simple = Annotations.requestMapping("/api/data");
RequestMapping withMethod = Annotations.requestMapping("/api/data", "POST");
RequestMapping full = Annotations.requestMapping("/api/data", "POST", 10000);AutoAnnotation properly handles array-valued annotation elements:
public @interface Tags {
String[] value();
int[] priorities() default {};
}
public class Annotations {
@AutoAnnotation
public static Tags tags(String[] value, int[] priorities) {
return new AutoAnnotation_Annotations_tags(value, priorities);
}
// Convenience method for single tag
public static Tags tag(String tag) {
return tags(new String[]{tag}, new int[]{});
}
}Usage:
Tags multipleTags = Annotations.tags(
new String[]{"web", "api", "rest"},
new int[]{1, 2, 3});
Tags singleTag = Annotations.tag("important");
// Arrays are properly cloned for immutability
String[] originalArray = {"test"};
Tags tagsFromArray = Annotations.tags(originalArray, new int[]{});
originalArray[0] = "modified"; // Doesn't affect the annotation
System.out.println(tagsFromArray.value()[0]); // Still "test"AutoAnnotation works with annotations that contain other annotations:
public @interface Validation {
String message();
int code();
}
public @interface Field {
String name();
Validation[] validations() default {};
}
public class Annotations {
@AutoAnnotation
public static Validation validation(String message, int code) {
return new AutoAnnotation_Annotations_validation(message, code);
}
@AutoAnnotation
public static Field field(String name, Validation[] validations) {
return new AutoAnnotation_Annotations_field(name, validations);
}
}Usage:
Validation required = Annotations.validation("Field is required", 1001);
Validation minLength = Annotations.validation("Minimum length is 3", 1002);
Field nameField = Annotations.field("username", new Validation[]{required, minLength});AutoAnnotation handles enum values correctly:
public enum Priority {
LOW, MEDIUM, HIGH, CRITICAL
}
public @interface Task {
String description();
Priority priority() default Priority.MEDIUM;
}
public class Annotations {
@AutoAnnotation
public static Task task(String description, Priority priority) {
return new AutoAnnotation_Annotations_task(description, priority);
}
// Convenience method with default priority
public static Task task(String description) {
return task(description, Priority.MEDIUM);
}
}Usage:
Task normalTask = Annotations.task("Implement feature");
Task urgentTask = Annotations.task("Fix critical bug", Priority.CRITICAL);AutoAnnotation supports Class<?> annotation elements:
public @interface Converter {
Class<?> from();
Class<?> to();
Class<? extends TypeConverter> converter();
}
public class Annotations {
@AutoAnnotation
public static Converter converter(Class<?> from, Class<?> to, Class<? extends TypeConverter> converter) {
return new AutoAnnotation_Annotations_converter(from, to, converter);
}
}Usage:
Converter stringToInt = Annotations.converter(String.class, Integer.class, StringToIntConverter.class);Factory methods can be generic for flexibility:
public @interface TypedAnnotation {
Class<?> value();
}
public class Annotations {
@AutoAnnotation
public static TypedAnnotation typedAnnotation(Class<?> value) {
return new AutoAnnotation_Annotations_typedAnnotation(value);
}
// Generic convenience method
public static <T> TypedAnnotation typedAnnotation(Class<T> type) {
return typedAnnotation((Class<?>) type);
}
}AutoAnnotation methods can have any visibility:
public class InternalAnnotations {
@AutoAnnotation
private static Internal internal(String value) {
return new AutoAnnotation_InternalAnnotations_internal(value);
}
// Public wrapper method
public static Internal createInternal(String value) {
validateInternalValue(value);
return internal(value);
}
private static void validateInternalValue(String value) {
if (value.isEmpty()) {
throw new IllegalArgumentException("Internal value cannot be empty");
}
}
}Combine AutoAnnotation with AutoBuilder for fluent annotation creation:
public @interface Configuration {
String name();
String value();
boolean required() default false;
String description() default "";
}
public class Annotations {
@AutoAnnotation
public static Configuration configuration(String name, String value, boolean required, String description) {
return new AutoAnnotation_Annotations_configuration(name, value, required, description);
}
}
@AutoBuilder(callMethod = "configuration", ofClass = Annotations.class)
public abstract class ConfigurationBuilder {
public static ConfigurationBuilder builder() {
return new AutoBuilder_ConfigurationBuilder()
.setRequired(false)
.setDescription("");
}
public abstract ConfigurationBuilder setName(String name);
public abstract ConfigurationBuilder setValue(String value);
public abstract ConfigurationBuilder setRequired(boolean required);
public abstract ConfigurationBuilder setDescription(String description);
public abstract Configuration build();
}Usage:
Configuration config = ConfigurationBuilder.builder()
.setName("database.url")
.setValue("jdbc:postgresql://localhost:5432/mydb")
.setRequired(true)
.setDescription("Database connection URL")
.build();AutoAnnotation validates non-null parameters:
public @interface Description {
String value();
String author();
}
public class Annotations {
@AutoAnnotation
public static Description description(String value, String author) {
return new AutoAnnotation_Annotations_description(value, author);
}
}// This will throw NullPointerException
Description desc = Annotations.description(null, "Alice"); // NPE: value cannot be null
Description desc2 = Annotations.description("text", null); // NPE: author cannot be nullInstall with Tessl CLI
npx tessl i tessl/maven-com-google-auto-value--auto-value