spring-projects/spring-ai · error · IllegalArgumentException

The specified advisor is not part of the chain:

Error message

The specified advisor is not part of the chain: 

What it means

copyAdvisorsAfter(advisor, advisors) splits an advisor list at a given 'after' advisor to build a new chain; if the advisor isn't in the list, indexOf returns -1 and an IllegalArgumentException is thrown naming the missing advisor. It's triggered from copy() when copying a chain using an advisor that isn't part of it.

Source

Thrown at spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/DefaultAroundAdvisorChain.java:186

	@Override
	public CallAdvisorChain copy(CallAdvisor after) {
		return this.copyAdvisorsAfter(this.getCallAdvisors(), after);
	}

	@Override
	public StreamAdvisorChain copy(StreamAdvisor after) {
		return this.copyAdvisorsAfter(this.getStreamAdvisors(), after);
	}

	private DefaultAroundAdvisorChain copyAdvisorsAfter(List<? extends Advisor> advisors, Advisor after) {

		Assert.notNull(after, "The after advisor must not be null");
		Assert.notNull(advisors, "The advisors must not be null");

		int afterAdvisorIndex = advisors.indexOf(after);

		if (afterAdvisorIndex < 0) {
			throw new IllegalArgumentException("The specified advisor is not part of the chain: " + after.getName());
		}

		var remainingStreamAdvisors = advisors.subList(afterAdvisorIndex + 1, advisors.size());

		return DefaultAroundAdvisorChain.builder(this.getObservationRegistry())
			.observationConvention(this.observationConvention)
			.pushAll(remainingStreamAdvisors)
			.build();
	}

	@Override
	public List<CallAdvisor> getCallAdvisors() {
		return this.originalCallAdvisors;
	}

	@Override
	public List<StreamAdvisor> getStreamAdvisors() {
		return this.originalStreamAdvisors;

View on GitHub (pinned to 98a7beda4f)

Solutions

  1. Use the exact advisor instance (or a correctly equal one) that was added to the chain when calling copy().
  2. Verify with advisors.contains(advisor) before copying.
  3. Add the advisor to the chain first if it's supposed to be part of it.
  4. Prefer copying by index or rebuilding the chain explicitly if identity matching is the issue.

Example fix

// before
chain.copy(myNewToolAdvisorInstance); // not the registered one
// after
Advisor registered = /* the same instance passed to .advisors(...) */;
chain.copy(registered);
Defensive patterns

Strategy: validation

Validate before calling

if (!chain.getAdvisors().contains(afterAdvisor)) {
  throw new IllegalArgumentException("advisor not in chain: " + afterAdvisor.getName());
}
chain.copy(afterAdvisor);

Try / catch

try { chain.copy(after); } catch (IllegalArgumentException e) { if (e.getMessage().startsWith("The specified advisor is not part of the chain")) { /* use the registered instance */ } throw e; }

Prevention

When it happens

Trigger: Calling copy() on an advisor chain passing an 'after' advisor instance that is not contained in the chain — typically a different instance than the one registered (equal contents but distinct object), or an advisor that was never added.

Common situations: Rebuilding a chain from a cached/ad-hoc advisor instance instead of the one originally registered; advisor registered by class but copy called with a freshly constructed instance without proper equals/hashCode; typo in advisor wiring.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11). Data as JSON: /api/errors/dfa53a4ef608e5b8. Report an issue: GitHub.