Using nested annotations for key-value pairs in a custom annotation

java, annotation

Introduction

In my article "Using a hashmap in a custom annotation" I explained how to use a HashMap in an annotation using an Enum Constant.

Annotations are allowed as type in an annotation. This means that nested annotations can be used to define key-value pairs in a custom annotation.

List of supported Types in Annotations

Annotations

Two customs annotations are required. The first annotation (e.g. MapItem) containing a key-value pair and the second annotation (e.g. MapItems) containing a list of MapItem annotations.

Custom Annotation @MapItem

The annotation @MapItem represents a single key-value pair.

@Target(ElementType.FIELD)
public @interface MapItem {
    
    String key();
    String value();
}

Custom Annotation @MapItems

The annotation @MapItems defines a list of MapItem.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface MapItems {
    
    MapItem[] items();
}

Functional Test

The list of @MapItem annotations are set in the @MapItems annotation.

class ExampleDto {
	
    @MapItems(items = {
        @MapItem(key = "1", value = "MALE"),
        @MapItem(key = "2", value = "FEMALE"),
        @MapItem(key = "6", value = "DIVERS")
    })
    public String salutation;
}

The MapItemsTest tests the MapItems annotation. The test is done on the salutation field.

To demonstrate how the list of @MapItem can be used, I create a HashMap from @MapItem and compare it to an expected HashMap.

class MapItemsTest {

    @Test
    void testMapItems() throws NoSuchFieldException {
		
        Field field = ExampleDto.class.getDeclaredField("salutation");
        field.setAccessible(true);
    
        MapItems annotation = field.getAnnotation(MapItems.class);
    
        Map<String, String> mappingItems = Arrays
                .stream(annotation.items())
                .collect(
                    Collectors.toMap(
                        MapItem::key,
                        MapItem::value
                    )
        );
    
        assertEquals(
            new HashMap<>() {{
                put("1", "MALE");
                put("2", "FEMALE");
                put("6", "DIVERS");
            }},
            mappingItems
        );
    }
}

Conclusion

Pro

It is a smart solution and easy to implement.

Contra

If the key-value pairs are to be used in a validator, for example, they must be fetched indirectly.

Full example

https://github.com/alaugks/article-annotation-key-value-pairs

Related article