alibaba/spring-ai-alibaba · error · IllegalStateException

ChatOptions implementation missing copy() method

Error message

ChatOptions implementation missing copy() method

What it means

ChatOptionsProxy locates the copy() method on the ChatOptions implementation via getClass().getMethod("copy") for its deep-copy logic. If the implementation (or any superclass) exposes no public parameterless copy() method, a NoSuchMethodException is wrapped in this IllegalStateException: the proxy cannot proceed without a copyable options object.

Source

Thrown at spring-boot-starters/spring-ai-alibaba-starter-config-nacos/src/main/java/com/alibaba/cloud/ai/agent/nacos/utils/ChatOptionsProxy.java:125

			return "copy".equals(method.getName())
					&& method.getParameterCount() == 0
					&& ChatOptions.class.isAssignableFrom(method.getReturnType());
		}

		private Object createCopiedProxy() {
			ChatOptions copiedChatOptions;
			try {
				Method copyMethod = chatOptions.getClass().getMethod("copy");
				// 如果是 private 或 protected,需要 setAccessible(true)
				copyMethod.setAccessible(true);
				Object result = copyMethod.invoke(chatOptions);
				if (!(result instanceof ChatOptions)) {
					throw new IllegalStateException("copy() method did not return a ChatOptions instance");
				}
				copiedChatOptions = (ChatOptions) result;
			}
			catch (NoSuchMethodException e) {
				throw new IllegalStateException("ChatOptions implementation missing copy() method", e);
			}
			catch (Exception e) {
				throw new RuntimeException("Failed to invoke copy() method", e);
			}

			// 创建新的代理对象(深拷贝 metadata)
			return ChatOptionsProxy.createProxy(
					copiedChatOptions,
					new HashMap<>(this.observationMetadata)
			);
		}

		/**
		 * 处理观察方法 - 基于方法名动态处理
		 */
		private Object handleObservationMethod(String methodName, Object[] args) {
			switch (methodName) {
			case "getObservationMetadata":

View on GitHub (pinned to f82da0b50f)

Solutions

  1. Implement a public parameterless copy() method on the ChatOptions class returning a new instance of itself.
  2. Align the spring-ai-alibaba and Spring AI versions (use the spring-ai-alibaba-bom) so the interface's default copy()/methods match.
  3. As a workaround, wrap the options in a standard implementation (e.g. the framework's own options class) before proxying.

Example fix

// before
class MyOptions implements ChatOptions { /* no copy() */ }
// after
class MyOptions implements ChatOptions {
    @Override
    public MyOptions copy() { return new MyOptions(this); }
}
Defensive patterns

Strategy: type-guard

Validate before calling

try { options.getClass().getMethod("copy"); } catch (NoSuchMethodException e) { throw new IllegalStateException("ChatOptions lacks copy(): " + options.getClass()); }

Type guard

static boolean supportsCopy(Object o) {
    try { o.getClass().getMethod("copy"); return true; } catch (NoSuchMethodException e) { return false; }
}

Try / catch

try { return ChatOptionsProxy.createProxy(options, meta); }
catch (IllegalStateException e) {
    if (e.getMessage().contains("missing copy() method")) { log.error("fix ChatOptions impl: {}", options.getClass().getName()); }
    throw e;
}

Prevention

When it happens

Trigger: Creating a ChatOptionsProxy around a custom ChatOptions class that omits the copy() method required by the Spring AI ChatOptions interface contract, or implements it with a non-public/different-signature variant.

Common situations: Hand-rolled ChatOptions implementations that only partially implement the interface; old Spring AI versions where copy() was named differently or absent; classpath mixing of incompatible spring-ai-core versions so the runtime class lacks copy().

Related errors


AI-assisted analysis of alibaba/spring-ai-alibaba@f82da0b50f (2026-09-09). Data as JSON: /api/errors/a91f70eda3256a86. Report an issue: GitHub.