spring-projects/spring-ai · error · IllegalStateException

Could not access method:

Error message

Could not access method: 

What it means

MethodToolCallback.callMethod() invokes the @Tool method reflectively via Method.invoke. If the method is not accessible from the caller (non-public method on a non-public class without setAccessible success), IllegalAccessException is converted into an IllegalStateException with this message.

Source

Thrown at spring-ai-model/src/main/java/org/springframework/ai/tool/method/MethodToolCallback.java:189

		catch (Exception ex) {
			logger.warn("Conversion from JSON failed", ex);
			Throwable cause = (ex.getCause() instanceof JacksonException) ? ex.getCause() : ex;
			throw new ToolExecutionException(this.getToolDefinition(), cause);
		}
	}

	@SuppressWarnings("NullAway") // ex.getCause() is guaranteed to be non-null
	private @Nullable Object callMethod(Object[] methodArguments) {
		if (isObjectNotPublic() || isMethodNotPublic()) {
			this.toolMethod.setAccessible(true);
		}

		Object result;
		try {
			result = this.toolMethod.invoke(this.toolObject, methodArguments);
		}
		catch (IllegalAccessException ex) {
			throw new IllegalStateException("Could not access method: " + ex.getMessage(), ex);
		}
		catch (InvocationTargetException ex) {
			throw new ToolExecutionException(this.toolDefinition, ex.getCause());
		}
		return result;
	}

	private boolean isObjectNotPublic() {
		return this.toolObject != null && !Modifier.isPublic(this.toolObject.getClass().getModifiers());
	}

	private boolean isMethodNotPublic() {
		return !Modifier.isPublic(this.toolMethod.getModifiers());
	}

	@Override
	public String toString() {
		return "MethodToolCallback{" + "toolDefinition=" + this.toolDefinition + ", toolMetadata=" + this.toolMetadata

View on GitHub (pinned to 98a7beda4f)

Solutions

  1. Make the @Tool method public on a public class.
  2. If the class is in a named module, add `opens <package>` to module-info.java so reflection can access it.
  3. Check the wrapped exception message for IllegalAccess details; verify no proxy hides a non-public target method.

Example fix

// before
class SecretTools { @Tool(name="x") private String run() {...} }
// after
public class Tools { @Tool(name="x") public String run() {...} }
Defensive patterns

Strategy: validation

Validate before calling

if (!java.lang.reflect.Modifier.isPublic(toolMethod.getModifiers()) || !Modifier.isPublic(toolMethod.getDeclaringClass().getModifiers())) {
    throw new IllegalArgumentException("@Tool methods must be public on a public class");
}

Type guard

static boolean isReflectivelyAccessible(Method m) { return Modifier.isPublic(m.getModifiers()) && Modifier.isPublic(m.getDeclaringClass().getModifiers()); }

Try / catch

try { result = toolCallback.call(input); } catch (IllegalStateException e) { if (e.getMessage().startsWith("Could not access method")) throw new ToolAccessException(e); throw e; }

Prevention

When it happens

Trigger: Registering an object whose @Tool method is private/protected and inaccessible via reflection (e.g. defined in a package-private class, module restrictions, or a security manager/JPMS strong encapsulation).

Common situations: @Tool methods on inner or package-private classes; JDK 16+ module strong encapsulation blocking setAccessible; AOP proxies where the target class differs; loading tools from another module without opens.

Related errors


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