alibaba/spring-ai-alibaba · error · WebFetchException
Failed after attempts:
Error message
Failed after attempts:
What it means
Final branch of fetchHtmlWithRetry: when the last exception is not a WebFetchException (e.g. a raw IOException or other RuntimeException from the fetch), it is wrapped in WebFetchException with 'Failed after N attempts: <cause message>'. This preserves the original error text in the message and the exception in the cause.
Source
Thrown at spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/tools/WebFetchTool.java:276
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")
.GET()
.build();
try {
HttpResponse<byte[]> byteResponse = this.httpClient.send(request,
HttpResponse.BodyHandlers.ofByteArray());View on GitHub (pinned to f82da0b50f)
Solutions
- Parse the ': <message>' suffix or getCause() to find the concrete root error.
- Validate/normalize the URL before calling the tool (proper scheme, no illegal characters).
- Check TLS/SSL truststore configuration if the cause mentions certificates.
- Enable logging around the tool call to capture the full stack of the wrapped cause.
Example fix
// before
String html = tool.fetch(userUrl);
// after
URI uri = URI.create(userUrl);
if (!"http".equals(uri.getScheme()) && !"https".equals(uri.getScheme())) {
throw new IllegalArgumentException("URL must be http(s): " + userUrl);
}
String html = tool.fetch(uri.toString()); Defensive patterns
Strategy: try-catch
Validate before calling
URI uri = URI.create(url);
if (!"https".equals(uri.getScheme()) && !"http".equals(uri.getScheme())) {
throw new IllegalArgumentException("URL must use http/https: " + url);
} Try / catch
try {
String html = webFetchTool.fetch(url);
} catch (WebFetchException e) {
String rootMessage = e.getCause() != null ? e.getCause().getMessage() : e.getMessage();
log.error("fetch failed after retries: {}", rootMessage, e);
return null;
} Prevention
- Sanitize/normalize URLs (scheme, encoding) before fetching
- Keep JVM CA certificates updated to avoid TLS-related wrapped failures
- Enable debug logging on java.net.http to see raw client errors
When it happens
Trigger: Retry loop exhausted with a non-WebFetchException as lastException — for example an unchecked exception thrown inside fetchHtml or an IOException path not converted earlier.
Common situations: Unexpected runtime errors during fetch (malformed URL passed to HttpClient, SSL handshake exceptions surfaced by the underlying client); target host rejecting TLS.
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
- Failed after attempts
- Fetch attempt {} failed for URL: {}: {}
- Oauth2CallError
- failed to create index
- Retry interrupted
AI-assisted analysis of alibaba/spring-ai-alibaba@f82da0b50f (2026-09-09).
Data as JSON: /api/errors/a492cb125c4b9ac6.
Report an issue: GitHub.