projectlombok/lombok · error · DelegateRecursion

@Delegate does not support recursion (delegating to a type…

Error message

@Delegate does not support recursion (delegating to a type that itself has @Delegate members). Member "%s" is @Delegate in type "%s"

What it means

PatchDelegate refuses to compile code where a type annotated with @Delegate delegates to another type that itself contains @Delegate members. Recursive delegation cannot be resolved, so Eclipse's patched compiler throws DelegateRecursion with the member and type names.

Solutions

  1. Remove @Delegate from one level of the delegation chain
  2. Replace @Delegate with explicit handwritten forwarding methods on the middle type
  3. Delegate only to the leaf type directly instead of stacking delegates
  4. Upgrade Lombok — newer versions may handle some recursion cases

Example fix

// before
class A { @Delegate B b; }
class B { @Delegate C c; }
// after
class A { @Delegate B b; }
class B { private C c; C getC() { return c; } } // explicit method instead of @Delegate
Defensive patterns

Strategy: validation

Validate before calling

function hasRecursiveDelegate(chain) {
  for (const t of chain) {
    if (chain.some(o => o !== t && t.delegatesTo.includes(o.name) && o.hasDelegateMembers)) return true;
  }
  return false;
}

Prevention

When it happens

Trigger: Class A has @Delegate on a field of type B, and B (or a supertype in B's hierarchy being scanned) also has @Delegate members — the recursion guard in canDelegate throws.

Common situations: Chained delegation patterns (A delegates to B, B delegates to C), DTO wrappers wrapping other @Delegate wrappers, copying delegation-heavy code between classes.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of projectlombok/lombok@6d6a3e9fec (2026-09-07). Data as JSON: /api/errors/bfbd3d0e18f29f0d. Report an issue: GitHub.

Appendix: source

Thrown at src/eclipseAgent/lombok/eclipse/agent/PatchDelegate.java:1022

		
		for (Binding b : bindings) {
			AnnotationBinding[] anns = null;
			if (b instanceof MethodBinding) anns = ((MethodBinding) b).getAnnotations();
			if (b instanceof FieldBinding) anns = ((FieldBinding) b).getAnnotations();
			// anns = b.getAnnotations() would make a heck of a lot more sense, but that is a late addition to ecj, so would cause NoSuchMethodErrors! Don't use that!
			if (anns == null) continue;
			for (AnnotationBinding ann : anns) {
				char[][] name = null;
				try {
					name = ann.getAnnotationType().compoundName;
				} catch (Exception ignore) {}
				
				if (name == null || name.length < 2 || name.length > 3) continue;
				if (!Arrays.equals(STRING_LOMBOK, name[0])) continue;
				if (!Arrays.equals(STRING_DELEGATE, name[name.length - 1])) continue;
				if (name.length == 3 && !Arrays.equals(STRING_EXPERIMENTAL, name[1])) continue;
				
				throw new DelegateRecursion(parent.readableName(), b.readableName());
			}
		}
	}
	
	private static final class BindingTuple {
		BindingTuple(MethodBinding parameterized, MethodBinding base, char[] fieldName, ASTNode responsible) {
			this.parameterized = parameterized;
			this.base = base;
			this.fieldName = fieldName;
			this.responsible = responsible;
		}
		
		final MethodBinding parameterized, base;
		final char[] fieldName;
		final ASTNode responsible;
		
		@Override public String toString() {
			return String.format("{param: %s, base: %s, fieldName: %s}", parameterized == null ? "(null)" : printSig(parameterized), base == null ? "(null)" : printSig(base), new String(fieldName));

View on GitHub (pinned to 6d6a3e9fec)