@SuppressWarnings("unchecked")
    private List<FieldAndEntity> searchRecursively(Object root) throws IllegalAccessException {
        List<FieldAndEntity> results = new ArrayList<>();
        Set<Object> objectsSeen = new HashSet<>();
        Deque<Object> nodesToSearch = new ArrayDeque<>();
        resolveValue(root).forEach(nodesToSearch::push);
        while (!nodesToSearch.isEmpty()) {
            Object currentNode = nodesToSearch.pop();

            // If is collection-like, then don't scan fields. Just get the values and continue.
            if (isCollectionLike(currentNode)) {
                resolveValue(currentNode).forEach(nodesToSearch::add);
                continue;
            }

            // If not collection-like, then we'll inspect the fields and see whether there's anything worth looking at.
            // Must not have seen this before (i.e. simple cycle detection).
            if (!currentNode.getClass().isEnum() && !objectsSeen.contains(currentNode)) {
                for (Field f : getAllFields(currentNode.getClass())) {
                    boolean isAccessible = f.trySetAccessible();
                    if (f.isAnnotationPresent(BlobReference.class) && f.getType().equals(String.class)) {
                        results.add(new FieldAndEntity(f, currentNode));
                    }
                    if (isAccessible && !shouldBeIgnored(f, currentNode)) {
                        Object value = f.get(currentNode);
                        if (value != null) {
                            resolveValue(value).filter(Objects::nonNull).forEach(nodesToSearch::push);
                        }
                    }
                }
                objectsSeen.add(currentNode);
            }
        }
        return results;
    }
Chase object graph for all @BlobReference strings.
<ul>
  <li>Starting with root and until no entries remain: pop entry off stack, this becomes <code>currentNode</code></li>
  <li>Iterate through every field in <code>currentNode</code>.</li>
  <li>If a field is a <code>String</code> and annotated with <code>BlobReference</code>, it is captured.</li>
  <li>Any non-String type that begins with <code>io.apiman</code> is pushed onto the stack. All other types are ignored.</li>
</ul>
TODO(msavy): Add cache or use Jandex to avoid repeated expensive reflection and iteration.
Simple!
manager/api/rest-impl/src/main/java/io/apiman/manager/api/rest/interceptors/BlobResourceInterceptorProvider.java