# UNDF: UNDF-2026-000000462
# micronaut-0002: MutableAnnotationMetadata — O(P×L) annotationList.contains in loop

## CWE-407 — Algorithmic Complexity: Linear Membership Test in Loop

| Field        | Value |
|--------------|-------|
| ID           | micronaut-0002 |
| Severity     | MEDIUM |
| Ecosystem    | micronaut-core |
| Package      | micronaut-inject |
| File         | `inject/src/main/java/io/micronaut/inject/annotation/MutableAnnotationMetadata.java` |
| Lines        | 332, 350, 428, 491 |
| Complexity   | O(P × L) where P = |parents| list, L = current annotationList size |
| Fix          | Change backing store from `ArrayList<String>` to `LinkedHashSet<String>` |

## Description

`getAnnotationsByStereotypeInternal()` returns a `List<String>` backed by `new ArrayList<>()`.
Both `addRepeatableStereotype` and `addDeclaredRepeatableStereotype` iterate over `parents` (a
`List<String>`) and for each call `annotationList.contains(parentAnnotation)` — O(L) per parent.
This pattern appears at lines 332, 350, 428, and 491, so it affects all four stereotype-addition
paths.

```java
List<String> annotationList = getAnnotationsByStereotypeInternal(stereotype); // ArrayList
for (String parentAnnotation : parents) {             // O(P)
    if (!annotationList.contains(parentAnnotation)) { // O(L) ArrayList scan
        annotationList.add(parentAnnotation);
    }
}
```

Since `annotationList` grows as parents are added, and this runs for every stereotype registration,
the total cost per stereotype with P parents is O(P × L) where L can approach P in the worst case
→ O(P²).

### Impact

Annotation metadata is built at startup for every bean, every method, every field that carries
annotations. In Micronaut applications with extensive annotation-based configuration (validation,
security, caching, AOP), the startup time is directly impacted.

## Fix

Change the backing store in `getAnnotationsByStereotypeInternal`:

```java
// Before
return getAnnotationsByStereotypeInternal().computeIfAbsent(stereotype, s -> new ArrayList<>());

// After
return getAnnotationsByStereotypeInternal().computeIfAbsent(stereotype, s -> new ArrayList<>());
// (keep the List<String> API but use a separate Set for dedup tracking)
```

Better: change the map value type to `Set<String>` (LinkedHashSet for order preservation):

```java
// In getAnnotationsByStereotypeInternal():
private Map<String, List<String>> getAnnotationsByStereotypeInternal()
// → change to Map<String, Set<String>> or use LinkedHashSet and adapt callers
```

Simplest compatible fix: use `LinkedHashSet<String>` via the existing `List` interface —
change the `computeIfAbsent` lambda to `new LinkedHashSet<>()` and cast where needed.

## Speedup Estimate

For P=15 parents per stereotype, L≈P: 15² = 225 ops → 15 ops.
**15x speedup** per stereotype registration. Multiplied across all beans at startup.
