spring-projects/spring-ai · warning

Query expansion result is null. Returning the input query…

Error message

Query expansion result is null. Returning the input query unchanged.

What it means

MultiQueryExpander.expand calls the chat model to generate query variants; when the model returns null content the expander logs this warning and falls back to returning the original query unchanged. Expansion is thus silently skipped rather than failing the retrieval pipeline.

Solutions

  1. Increase maxTokens / adjust the expansion prompt so the model returns text.
  2. Log the full ChatResponse to see why content is null (finish reason, filter).
  3. Treat the fallback as expected: ensure downstream retrieval handles a single-query list.
  4. Use a different or stronger model for expansion if nulls are frequent.

Example fix

// before
MultiQueryExpander expander = MultiQueryExpander.builder()
    .chatClient(chatClient)
    .numberOfQueries(3)
    .build();

// after — set explicit options so the model always returns text
MultiQueryExpander expander = MultiQueryExpander.builder()
    .chatClient(chatClient)
    .numberOfQueries(3)
    .prompt(UPDATE_PROMPT) // tuned prompt
    .build();
// and configure model options: maxTokens(512) on the underlying chat model
Defensive patterns

Strategy: fallback

Validate before calling

// pre-check the chat model config used for expansion
if (options.getMaxTokens() != null && options.getMaxTokens() < 256) {
    logger.warn("maxTokens too low for query expansion; null content likely");
}

Try / catch

List<Query> queries = expander.expand(new Query("original"));
if (queries.size() == 1 && queries.get(0).text().equals("original")) {
    logger.info("Expansion fell back to original query; check model output");
}

Prevention

When it happens

Trigger: expand() invokes the prompt with number/query params and .call().content() returns null (model produced no content, e.g. content filter, empty completion, or a reasoning-only response).

Common situations: ChatModel returning null content due to safety filters; maxTokens too small so no content is generated; misconfigured model returning tool calls instead of text; provider outage producing empty completions.

Understand the failure class

Background: "empty response", "returned no data", "empty embeddings": what HTTP 200-with-empty-body errors mean across libraries — this error's family across 36 libraries.

Related errors


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

Appendix: source

Thrown at spring-ai-rag/src/main/java/org/springframework/ai/rag/preretrieval/query/expansion/MultiQueryExpander.java:112

	}

	@Override
	public List<Query> expand(Query query) {
		Assert.notNull(query, "query cannot be null");

		if (logger.isDebugEnabled()) {
			logger.debug("Generating " + this.numberOfQueries + " query variants");
		}

		var response = this.chatClient.prompt()
			.user(user -> user.text(this.promptTemplate.getTemplate())
				.param("number", this.numberOfQueries)
				.param("query", query.text()))
			.call()
			.content();

		if (response == null) {
			logger.warn("Query expansion result is null. Returning the input query unchanged.");
			return List.of(query);
		}

		var queryVariants = Arrays.asList(response.split("\n"));

		if (CollectionUtils.isEmpty(queryVariants) || this.numberOfQueries != queryVariants.size()) {
			if (logger.isWarnEnabled()) {
				logger.warn("Query expansion result does not contain the requested " + this.numberOfQueries
						+ " variants. Returning the input query unchanged.");
			}
			return List.of(query);
		}

		var queries = queryVariants.stream()
			.filter(StringUtils::hasText)
			.map(queryText -> query.mutate().text(queryText).build())
			.collect(Collectors.toList());

View on GitHub (pinned to 98a7beda4f)