spring-projects/spring-ai · error · RuntimeException

Failed to search index

Error message

Failed to search index

What it means

Thrown by LuceneToolIndex.doSearch() (called from search()) when the Lucene search pipeline fails with an IOException: opening/reusing the IndexReader (ensureAndGetReader), executing searcher.search(), or reading stored fields in extractToolReferences. The library wraps it as a RuntimeException("Failed to search index") because IOException is checked but the search API is not declared to throw.

Source

Thrown at spring-ai-tool-search-tool/src/main/java/org/springframework/ai/tool/toolsearch/index/lucene/LuceneToolIndex.java:253

	 * @param queryString the search query
	 * @param maxResults maximum number of results to return
	 * @param minScore minimum score threshold for results
	 * @return list of matching documents
	 */
	private ToolSearchResponse doSearch(SessionIndex sessionIndex, String queryString, int maxResults, float minScore) {
		try {
			IndexSearcher searcher = new IndexSearcher(sessionIndex.ensureAndGetReader());

			Query query = buildQuery(queryString);
			if (query == null) {
				return ToolSearchResponse.builder().build();
			}

			TopDocs results = searcher.search(query, maxResults);
			return this.extractToolReferences(queryString, searcher, results, minScore);
		}
		catch (IOException e) {
			throw new RuntimeException("Failed to search index", e);
		}
	}

	private ToolSearchResponse extractToolReferences(String query, IndexSearcher searcher, TopDocs results,
			float minScore) throws IOException {

		List<ToolReference> foundToolReferences = new ArrayList<>(results.scoreDocs.length);
		StoredFields storedFields = searcher.storedFields();
		for (ScoreDoc scoreDoc : results.scoreDocs) {
			if (logger.isInfoEnabled()) {
				logger.info("Score: " + scoreDoc.score + ", name: "
						+ storedFields.document(scoreDoc.doc).get(FIELD_TOOL_NAME));
			}
			if (scoreDoc.score >= minScore) {
				var doc = storedFields.document(scoreDoc.doc);
				foundToolReferences.add(ToolReference.builder()
					.relevanceScore(scoreDoc.score)
					.toolName(doc.get(FIELD_TOOL_NAME))

View on GitHub (pinned to 98a7beda4f)

Solutions

  1. Inspect the wrapped IOException cause to see which step failed (reader open, search, stored-fields fetch)
  2. Verify the index directory exists and is readable/writable by the process
  3. Ensure no clearIndex() or close() runs concurrently with search() for the same session
  4. If reader/index is stale or corrupt, re-initialize the session (re-add documents) and search again
  5. Catch the RuntimeException and return an empty ToolSearchResponse as a graceful fallback in the advisor flow

Example fix

// before
ToolSearchResponse resp = index.search(sessionId, "sql query", 5, 0.5f); // throws RuntimeException on IO failure
// after
ToolSearchResponse resp;
try {
    resp = index.search(sessionId, "sql query", 5, 0.5f);
}
catch (RuntimeException e) {
    logger.warn("tool search failed, returning empty result", e);
    resp = ToolSearchResponse.builder().toolReferences(List.of()).totalMatches(0).build();
}
Defensive patterns

Strategy: fallback

Validate before calling

File idxDir = new File(indexPath);
if (!idxDir.isDirectory() || !Files.isReadable(idxDir.toPath())) {
    throw new IllegalStateException("Index directory missing or unreadable: " + indexPath);
}

Try / catch

ToolSearchResponse resp;
try {
    resp = index.search(sessionId, query, maxResults, minScore);
}
catch (RuntimeException e) {
    if (e.getCause() instanceof java.io.IOException) {
        logger.warn("Lucene search IO failure for session " + sessionId, e);
    }
    resp = ToolSearchResponse.builder().toolReferences(List.of()).totalMatches(0).build();
}

Prevention

When it happens

Trigger: Calling search() when the session's index directory is unreadable or deleted, the IndexReader was closed by a concurrent clearIndex(), the underlying index files are corrupt, or an I/O error occurs while fetching stored documents for the matched score docs.

Common situations: Index directory removed or remounted read-only between add and search; reader closed by concurrent clearIndex()/close(); corrupted segment files after a crash mid-commit; NFS/network storage dropping the index directory; searching after the index was rebuilt with an incompatible Lucene version.

Understand the failure class

Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.

Related errors


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