chinabugotech/hutool · error · IllegalArgumentException

attribute [{}] cannot mirror for [{}], because [{}] already

Error message

attribute [{}] cannot mirror for [{}], because [{}] already mirrored for [{}] and  [{}] already mirrored for [{}]

What it means

The other branch of the same check: both mirrored attributes already point at different targets, so neither can additionally mirror the other. The processor treats this as an unrecoverable inconsistent state and throws IllegalArgumentException with both link targets enumerated. The comment notes this case is theoretically unreachable but is guarded defensively.

Source

Thrown at hutool-core/src/main/java/cn/hutool/core/annotation/MirrorLinkAnnotationPostProcessor.java:111

		}
		// 镜像字段已经跟其他字段形成镜像
		else if (!originalAttributeMirrored && mirrorAttributeMirrored) {
			errorMsg = CharSequenceUtil.format(
				"attribute [{}] cannot mirror for [{}], because it's already mirrored for [{}]",
				mirror.getAttribute(), original.getAttribute(), ((MirroredAnnotationAttribute)mirror).getLinked()
			);
		}
		// 两者都形成了镜像,但是都未指向对方,理论上不会存在该情况
		else {
			errorMsg = CharSequenceUtil.format(
				"attribute [{}] cannot mirror for [{}], because [{}] already mirrored for [{}] and  [{}] already mirrored for [{}]",
				mirror.getAttribute(), original.getAttribute(),
				mirror.getAttribute(), ((MirroredAnnotationAttribute)mirror).getLinked(),
				original.getAttribute(), ((MirroredAnnotationAttribute)original).getLinked()
			);
		}

		throw new IllegalArgumentException(errorMsg);
	}

	/**
	 * 基本校验
	 */
	private void checkMirrorRelation(Link annotation, AnnotationAttribute original, AnnotationAttribute mirror) {
		// 镜像属性必须存在
		checkLinkedAttributeNotNull(original, mirror, annotation);
		// 镜像属性返回值必须一致
		checkAttributeType(original, mirror);
		// 镜像属性上必须存在对应的注解
		final Link mirrorAttributeAnnotation = getLinkAnnotation(mirror, RelationType.MIRROR_FOR);
		Assert.isTrue(
			ObjectUtil.isNotNull(mirrorAttributeAnnotation) && RelationType.MIRROR_FOR.equals(mirrorAttributeAnnotation.type()),
			"mirror attribute [{}] of original attribute [{}] must marked by @Link, and also @LinkType.type() must is [{}]",
			mirror.getAttribute(), original.getAttribute(), RelationType.MIRROR_FOR
		);
		checkLinkedSelf(original, mirror);

View on GitHub (pinned to 8870454b2a)

Solutions

  1. Audit custom AnnotationPostProcessor implementations for link mutation; register them after the mirror processor.
  2. Eliminate duplicate/overlapping @Link MIRROR_FOR declarations across the annotation hierarchy.
  3. Simplify the annotation composition to a single explicit mirror pair and rebuild.
Defensive patterns

Strategy: try-catch

Try / catch

try { synth.setAttribute(n, v); } catch (UnsupportedOperationException e) { if (e.getMessage().contains("proxied")) { /* mutate underlying */ } else throw e; }

Prevention

When it happens

Trigger: An annotation-processing or synthesis bug where both A and B have already been wrapped as MirroredAnnotationAttribute linked to third parties; can appear when multiple AnnotationPostProcessors run and one has already rewired links before MirrorLinkAnnotationPostProcessor validates.

Common situations: Custom AnnotationPostProcessor registered ahead of MirrorLinkAnnotationPostProcessor that mutates link relationships; duplicate mirror declarations across a meta-annotation chain.

Related errors


AI-assisted analysis of chinabugotech/hutool@8870454b2a (2026-08-14). Data as JSON: /api/errors/d9e4bffd14996ff7. Report an issue: GitHub.