alibaba/spring-ai-alibaba · error · WebFetchException

Failed after attempts

Error message

Failed after  attempts

What it means

After exhausting all fetch attempts in fetchHtmlWithRetry, WebFetchTool throws WebFetchException('Failed after N attempts'). The no-cause branch (line 270) fires when the loop ended with lastException == null — every attempt returned normally per the catch structure but no usable response was produced — or conceptually marks the generic exhaustion message. It means the URL could not be fetched within the configured retry budget.

Source

Thrown at spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/tools/WebFetchTool.java:270

				return response;
			}
			catch (WebFetchException e) {
				lastException = e;
				if (e.getCause() instanceof InterruptedException) {
					throw e;
				}
				logger.warn("Fetch attempt {} failed for URL: {}: {}", attempt + 1, url, e.getMessage());
				attempt++;
			}
			catch (InterruptedException e) {
				Thread.currentThread().interrupt();
				throw new WebFetchException("Retry interrupted", e);
			}
		}

		if (lastException == null) {
			throw new WebFetchException("Failed after " + (this.maxRetries + 1) + " attempts", null);
		}
		else if (lastException instanceof WebFetchException) {
			throw new WebFetchException("Failed after " + (this.maxRetries + 1) + " attempts", lastException);
		}
		else {
			throw new WebFetchException(
					"Failed after " + (this.maxRetries + 1) + " attempts: " + lastException.getMessage(),
					lastException);
		}
	}

	private HttpResponse<String> fetchHtml(String url) {
		HttpRequest request = HttpRequest.newBuilder()
			.uri(URI.create(url))
			.timeout(DEFAULT_REQUEST_TIMEOUT)
			.header("User-Agent", USER_AGENT)
			.header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
			.header("Accept-Language", "en-US,en;q=0.5")

View on GitHub (pinned to f82da0b50f)

Solutions

  1. Verify the URL is reachable from the host with curl or a browser.
  2. Increase maxRetries via the Builder for flaky targets.
  3. Inspect the chained cause (getCause()) to find the real network failure.
  4. Add connectivity/proxy configuration (HTTP_PROXY, VPN, firewall rules) for the runtime.

Example fix

// before
WebFetchTool tool = WebFetchTool.builder(chatClient).build();
// after
WebFetchTool tool = WebFetchTool.builder(chatClient)
    .maxRetries(3)
    .build();
Defensive patterns

Strategy: retry

Validate before calling

URI uri = URI.create(url);
if (uri.getScheme() == null ||
    !(uri.getScheme().equals("http") || uri.getScheme().equals("https"))) {
    throw new IllegalArgumentException("invalid fetch URL: " + url);
}

Try / catch

try {
    String html = webFetchTool.fetch(url);
} catch (WebFetchException e) {
    log.warn("fetch exhausted: {} cause={}", e.getMessage(), e.getCause());
    return fallbackContent(url);
}

Prevention

When it happens

Trigger: Calling WebFetchTool on a URL where all maxRetries+1 attempts fail, and the final recorded exception is null or a WebFetchException already carrying its own cause.

Common situations: Fetching an unreachable or DNS-failing host; misconfigured maxRetries=0 with a flaky network; a proxy or firewall blocking egress from the runtime environment; URL temporarily returning errors until retries run out.

Understand the failure class

Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.

Related errors


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