{"record":{"id":"0e07622cfa84a502","repo":"chinabugotech/hutool","slug":"failed-to-send-post-request","errorCode":null,"errorMessage":"Failed to send POST request：","messagePattern":"Failed to send POST request：","errorType":"exception","errorClass":"AIException","httpStatus":null,"severity":"error","filePath":"hutool-ai/src/main/java/cn/hutool/ai/core/BaseAIService.java","lineNumber":94,"sourceCode":"\t * @param endpoint 请求节点\n\t * @param paramJson 请求参数json\n\t * @return 请求响应\n\t */\n\tprotected HttpResponse sendPost(final String endpoint, final String paramJson) {\n\t\t//链式构建请求\n\t\ttry {\n\t\t\tHttpRequest httpRequest = HttpRequest.post(config.getApiUrl() + endpoint)\n\t\t\t\t.header(Header.CONTENT_TYPE, \"application/json\")\n\t\t\t\t.header(Header.ACCEPT, \"application/json\")\n\t\t\t\t.header(Header.AUTHORIZATION, \"Bearer \" + config.getApiKey())\n\t\t\t\t.body(paramJson)\n\t\t\t\t.timeout(config.getTimeout());\n\t\t\tif (config.getHasProxy()) {\n\t\t\t\thttpRequest.setProxy(config.getProxy());\n\t\t\t}\n\t\t\treturn httpRequest.execute();\n\t\t} catch (final Exception e) {\n\t\t\tthrow new AIException(\"Failed to send POST request：\" + e.getMessage(), e);\n\t\t}\n\n\t}\n\n\t/**\n\t * 发送表单请求\n\t * @param endpoint 请求节点\n\t * @param paramMap 请求参数map\n\t * @return 请求响应\n\t */\n\tprotected HttpResponse sendFormData(final String endpoint, final Map<String, Object> paramMap) {\n\t\t//链式构建请求\n\t\ttry {\n\t\t\t//设置超时3分钟\n\t\t\tHttpRequest httpRequest = HttpRequest.post(config.getApiUrl() + endpoint)\n\t\t\t\t.header(Header.CONTENT_TYPE, \"multipart/form-data\")\n\t\t\t\t.header(Header.ACCEPT, \"application/json\")\n\t\t\t\t.header(Header.AUTHORIZATION, \"Bearer \" + config.getApiKey())","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/chinabugotech/hutool/blob/8870454b2a0c29cc6ffd31dcf5667c8ceb2fc442/hutool-ai/src/main/java/cn/hutool/ai/core/BaseAIService.java#L76-L112","documentation":"Thrown by BaseAIService.sendPost on a transport-level failure of HttpRequest.execute(). Hutool-http returns HTTP error statuses as a normal HttpResponse, so 4xx/5xx do NOT raise this -- only connection/socket/URL/SSL/proxy errors do. Note the message uses a full-width colon character (：, U+FF1A), not an ASCII colon, which matters if you filter or match the string. The cause is preserved.","triggerScenarios":"POST to an unreachable/malformed apiUrl, connect/read timeout, SSL handshake failure, or proxy failure during chat/embeddings calls on models inheriting BaseAIService.sendPost (deepseek, openai, doubao, grok, hutool). A large or malformed paramJson causes a different error path, not this one.","commonSituations":"Wrong base URL; network outage; timeout too low; reverse-proxy rejecting the path; self-signed cert. Invalid apiKey or rate-limit returns an HTTP status (handled by the caller), not this exception.","solutions":["Confirm config.getApiUrl() + endpoint resolves and accepts POST.","Raise setTimeout/setReadTimeout for slow generation.","Inspect ex.getCause() for the underlying IOException.","If matching this message in logs, use the full-width colon （：） literally.","For HTTP business errors, read response.getStatus()/body() on the success path instead."],"exampleFix":"// before\nHttpResponse resp = service.sendPost(\"/v1/chat/completions\", json);\n// transport fails -> Failed to send POST request：...\n\n// after\nAIConfig cfg = new AIConfigBuilder(ModelName.OPENAI.getValue())\n    .setApiKey(k).setApiUrl(\"https://api.openai.com\")\n    .setTimeout(60_000).setReadTimeout(120_000).build();\nHttpResponse resp = service.sendPost(\"/v1/chat/completions\", json);\nif (!resp.isOk()) { /* handle 4xx/5xx body */ }","handlingStrategy":"try-catch","validationCode":"// Validate URL + timeout before the POST\nString base = config.getApiUrl();\nif (StrUtil.isBlank(base)) throw new IllegalStateException(\"apiUrl blank\");\nif (config.getTimeout() < 10_000) config.setTimeout(30_000);","typeGuard":null,"tryCatchPattern":"try {\n    return service.sendPost(endpoint, json);\n} catch (AIException e) {\n    // NOTE: message uses full-width colon （：）\n    if (e.getMessage().contains(\"Failed to send POST request\")) {\n        Throwable root = e.getCause(); // real IOException\n        // transport failure only; HTTP 4xx/5xx are returned, not thrown\n    }\n    throw e;\n}","preventionTips":["Match the message with the full-width colon if filtering logs.","Set generous read timeouts for generation endpoints.","Check response.getStatus() separately for business errors."],"tags":["network","http","transport","timeout","i18n-string"],"backgroundTag":null,"analyzedSha":"8870454b2a0c29cc6ffd31dcf5667c8ceb2fc442","analyzedAt":"2026-08-14T04:01:12.892Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}