{"record":{"id":"631760a97ac4d351","repo":"languagetool-org/languagetool","slug":"you-have-v2-in-your-path-but-not-at-the-root","errorCode":null,"errorMessage":"You have '/v2/' in your path, but not at the root. Try an URL like 'http://server/v2/...' ","messagePattern":"You have '/v2/' in your path, but not at the root\\. Try an URL like 'http://server/v2/\\.\\.\\.' ","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"warning","filePath":"languagetool-server/src/main/java/org/languagetool/server/LanguageToolHttpHandler.java","lineNumber":219,"sourceCode":"        return;\n      }\n      if (workQueueFull(httpExchange, parameters, \"Error: There are currently too many parallel requests. Please try again later.\")) {\n        ServerMetricsCollector.getInstance().logRequestError(ServerMetricsCollector.RequestErrorType.QUEUE_FULL);\n        return;\n      }\n      if (allowedIps == null || allowedIps.contains(origAddress)) {\n        if (path.startsWith(\"/v2/\")) {\n          ApiV2 apiV2 = new ApiV2(textCheckerV2, config.getAllowOriginUrl());\n          String pathWithoutVersion = path.substring(\"/v2/\".length());\n          final Map<String, String> finalParameters = parameters;\n          final String finalRemoteAddress = remoteAddress;\n          TelemetryProvider.INSTANCE.createSpan(\"/v2\", Attributes.empty(), () -> apiV2.handleRequest(pathWithoutVersion, httpExchange, finalParameters, errorRequestLimiter, finalRemoteAddress, config));\n        } else if (path.endsWith(\"/Languages\")) {\n          throw new BadRequestException(\"You're using an old version of our API that's not supported anymore. Please see \" + API_DOC_URL);\n        } else if (path.equals(\"/\")) {\n          throw new BadRequestException(\"Missing arguments for LanguageTool API. Please see \" + API_DOC_URL);\n        } else if (path.contains(\"/v2/\")) {\n          throw new BadRequestException(\"You have '/v2/' in your path, but not at the root. Try an URL like 'http://server/v2/...' \");\n        } else if (path.equals(\"/favicon.ico\")) {\n          sendError(httpExchange, HttpURLConnection.HTTP_NOT_FOUND, \"Not found\");\n        } else {\n          throw new BadRequestException(\"This is the LanguageTool API. You have not specified any parameters. Please see \" + API_DOC_URL);\n        }\n      } else {\n        String errorMessage = \"Error: Access from \" + StringTools.escapeXML(origAddress) + \" denied\";\n        sendError(httpExchange, HttpURLConnection.HTTP_FORBIDDEN, errorMessage);\n        throw new RuntimeException(errorMessage);\n      }\n    } catch (Exception e) {\n      String response;\n      int errorCode;\n      boolean textLoggingAllowed = false;\n      boolean logStacktrace = true;\n      Throwable rootCause = ExceptionUtils.getRootCause(e);\n      if (e instanceof TextTooLongException || rootCause instanceof TextTooLongException) {\n        errorCode = HttpURLConnection.HTTP_ENTITY_TOO_LARGE;","sourceCodeStart":201,"sourceCodeEnd":237,"githubUrl":"https://github.com/languagetool-org/languagetool/blob/2e990059ce67d5e2a0f7f7ca5d31160c6709df4b/languagetool-server/src/main/java/org/languagetool/server/LanguageToolHttpHandler.java#L201-L237","documentation":"Thrown when the request path contains '/v2/' but not at the start (root) of the path, e.g. '/api/v2/check'. The server only recognizes the version prefix when it directly follows the host, so embedded version segments are treated as unknown paths.","triggerScenarios":"Requests where '/v2/' appears mid-path, such as mounting LanguageTool under a base path like '/languagetool/v2/check' without rewriting the URL before it reaches the server.","commonSituations":"Reverse-proxy setups that preserve a context root instead of stripping it, Docker/nginx configs forwarding the full original path, or clients hardcoding a deployment base path into the endpoint URL.","solutions":["Request the endpoint with /v2 at the path root, e.g. http://server/v2/check","Configure the reverse proxy to strip the outer base path before forwarding to LanguageTool","If a base path is required, use a proxy rewrite rule (e.g. nginx proxy_pass http://backend/v2/check)"],"exampleFix":"// before\ncurl http://myhost/languagetool/v2/check?text=hi\n// after\ncurl -X POST http://myhost/v2/check -d 'text=hi&language=en-US'","handlingStrategy":"validation","validationCode":"const p = new URL(endpoint).pathname;\nif (!/^\\/v2(\\/|$)/.test(p)) throw new Error(`'/v2/' must be at path root, got '${p}'`);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Strip deployment base paths in the reverse proxy before forwarding","Never hardcode a context root into the LanguageTool endpoint URL","Test proxy configs with curl against a known-good /v2/check call"],"tags":["http","languagetool","path","proxy"],"backgroundTag":"invalid-url-format","analyzedSha":"2e990059ce67d5e2a0f7f7ca5d31160c6709df4b","analyzedAt":"2026-09-06T09:20:17.015Z","contentChangedAt":"2026-09-06T09:20:17.015Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}