Repository: langchain4j/langchain4j (2026-07-28T14:57:30Z)

Issue:
[BUG] VertexAiAnthropicClient.createClient() does not handle global region
<!-- Please provide as many details as possible, this will help us to deliver a fix as soon as possible. Thank you! -->

**Describe the bug**

VertexAiAnthropicClient.createClient() always builds the Prediction Service transport endpoint as String.format("%s-aiplatform.googleapis.com:443", location), regardless of what
location is. This template is only correct for a single-region location (e.g. us-east5 → us-east5-aiplatform.googleapis.com). Per Google/Anthropic's own documentation, the other
supported location values use different hostnames entirely:

- global → aiplatform.googleapis.com (no location prefix at all)
- us / eu (multi-region) → aiplatform.us.rep.googleapis.com / aiplatform.eu.rep.googleapis.com

Passing location("global") therefore makes the client dial the non-existent host global-aiplatform.googleapis.com. That hostname still resolves (as a wildcard *.googleapis.com
frontend) and completes TLS, but has no backend route, so the gRPC call fails with a generic Google-branded HTML 404 instead of a normal API error — confusing to debug since it
looks like a routing/auth problem rather than a client-side host-string bug.

**Log and Stack trace**
```bash
[INFO] Running io.onering.app.llm.VertexProviderIT
2026-07-27 23:30:10,695 DEBUG [io.onering.app.llm.ConfiguredChatModelResolver] (main) pingReturnsARealResponseFromVertex
2026-07-27 23:30:11,278 DEBUG [dev.langchain4j.model.vertexai.anthropic.VertexAiAnthropicChatModel] (main) Base URL: global-aiplatform.googleapis.com:443
2026-07-27 23:30:11,278 DEBUG [dev.langchain4j.model.vertexai.anthropic.VertexAiAnthropicChatModel] (main) Using model name: claude-opus-4-8 (from parameters: null, default: claude-opus-4-8)
2026-07-27 23:30:11,279 DEBUG [dev.langchain4j.model.vertexai.anthropic.VertexAiAnthropicChatModel] (main) Anthropic request: AnthropicRequest [anthropicVersion=vertex-2023-10-16, messages=1, maxTokens=32, stream=false, temperature=null, topP=null, topK=null, tools=null, toolsChoice=null]
[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 1.521 s <<< FAILURE! -- in io.onering.app.llm.VertexProviderIT
[ERROR] io.onering.app.llm.VertexProviderIT.pingReturnsARealResponseFromVertex -- Time elapsed: 1.509 s <<< ERROR!
java.lang.RuntimeException: Failed to generate response
        at dev.langchain4j.model.vertexai.anthropic.VertexAiAnthropicChatModel.doChat(VertexAiAnthropicChatModel.java:144)
        at dev.langchain4j.model.chat.ChatModel.chat(ChatModel.java:61)
        at dev.langchain4j.model.chat.ChatModel.chat(ChatModel.java:36)
        at dev.langchain4j.model.chat.ChatModel.chat(ChatModel.java:91)
        at io.onering.app.llm.VertexProviderIT.pingReturnsARealResponseFromVertex(VertexProviderIT.java:50)
Caused by: java.io.IOException: Failed to generate content using Vertex AI rawPredict
        at dev.langchain4j.model.vertexai.anthropic.internal.client.VertexAiAnthropicClient.generateContentWithRetry(VertexAiAnthropicClient.java:169)
        at dev.langchain4j.model.vertexai.anthropic.internal.client.VertexAiAnthropicClient.generateContent(VertexAiAnthropicClient.java:74)
        at dev.langchain4j.model.vertexai.anthropic.VertexAiAnthropicChatModel.doChat(VertexAiAnthropicChatModel.java:136)
        ... 4 more
Caused by: com.google.api.gax.rpc.UnimplementedException: io.grpc.StatusRuntimeException: UNIMPLEMENTED: HTTP status code 404
invalid content-type: text/html; charset=UTF-8
headers: Metadata(:status=404,content-type=text/html; charset=UTF-8,referrer-policy=no-referrer,date=Tue, 28 Jul 2026 05:30:12 GMT,alt-svc=h3=":443"; ma=2592000,h3-29=":443"; ma=2592000,content-length=1616)
DATA-----------------------------
<!DOCTYPE html>
<html lang=en>
  <meta charset=utf-8>
  <meta name=viewport content="initial-scale=1, minimum-scale=1, width=device-width">
  <title>Error 404 (Not Found)!!1</title>
  <style>
    *{margin:0;padding:0}html,code{font:15px/22px arial,sans-serif}html{background:#fff;color:#222;padding:15px}body{margin:7% auto 0;max-width:390px;min-height:180px;padding:30px 0 15px}* > body{background:url(//www.google.com/images/errors/robot.png) 100% 5px no-repeat;padding-right:205px}p{margin:11px 0 22px;overflow:hidden}ins{color:#777;text-decoration:none}a img{border:0}@media screen and (max-width:772px){body{background:none;margin-top:0;max-width:none;padding-right:0}}#logo{background:url(//www.google.com/images/branding/googlelogo/1x/googlelogo_color_150x54dp.png) no-repeat;margin-left:-5px}@media only screen and (min-resolution:192dpi){#logo{background:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) no-repeat 0% 0%/100% 100%;-moz-border-image:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) 0}}@media only screen and (-webkit-min-device-pixel-ratio:2){#logo{background:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) no-repeat;-webkit-background-size:100% 100%}}#logo{display:inline-block;height:54px;width:150px}
  </style>
  <a href=//www.google.com/><span id=logo aria-label=Google></span></a>
  <p><b>404.</b> <ins>That’s an error.</ins>
  <p>The requested URL <code>/google.cloud.aiplatform.v1.PredictionService/RawPredict</code> was not found on this server.  <ins>That’s all we know.</ins>

        at com.google.api.gax.rpc.ApiExceptionFactory.createException(ApiExceptionFactory.java:108)
        at com.google.api.gax.rpc.ApiExceptionFactory.createException(ApiExceptionFactory.java:41)
        at com.google.api.gax.grpc.GrpcApiExceptionFactory.create(GrpcApiExceptionFactory.java:86)
        at com.google.api.gax.grpc.GrpcApiExceptionFactory.create(GrpcApiExceptionFactory.java:66)
        at com.google.api.gax.grpc.GrpcExceptionCallable$ExceptionTransformingFuture.onFailure(GrpcExceptionCallable.java:97)
        at com.google.api.core.ApiFutures$1.onFailure(ApiFutures.java:84)
        at com.google.common.util.concurrent.Futures$CallbackListener.run(Futures.java:1125)
        at com.google.common.util.concurrent.DirectExecutor.execute(DirectExecutor.java:30)
        at com.google.common.util.concurrent.AbstractFuture.executeListener(AbstractFuture.java:1004)
        at com.google.common.util.concurrent.AbstractFuture.complete(AbstractFuture.java:767)
        at com.google.common.util.concurrent.AbstractFuture.setException(AbstractFuture.java:516)
        at io.grpc.stub.ClientCalls$GrpcFuture.setException(ClientCalls.java:668)
        at io.grpc.stub.ClientCalls$UnaryStreamToFuture.onClose(ClientCalls.java:638)
        at io.grpc.PartialForwardingClientCallListener.onClose(PartialForwardingClientCallListener.java:39)
        at io.grpc.ForwardingClientCallListener.onClose(ForwardingClientCallListener.java:23)
        at io.grpc.ForwardingClientCallListener$SimpleForwardingClientCallListener.onClose(ForwardingClientCallListener.java:40)
        at com.google.api.gax.grpc.ChannelPool$ReleasingClientCall$1.onClose(ChannelPool.java:569)
        at io.grpc.PartialForwardingClientCallListener.onClose(PartialForwardingClientCallListener.java:39)
        at io.grpc.ForwardingClientCallListener.onClose(ForwardingClientCallListener.java:23)
        at io.grpc.ForwardingClientCallListener$SimpleForwardingClientCallListener.onClose(ForwardingClientCallListener.java:40)
        at com.google.api.gax.grpc.GrpcLoggingInterceptor$1$1.onClose(GrpcLoggingInterceptor.java:98)
        at io.grpc.internal.DelayedClientCall$DelayedListener$3.run(DelayedClientCall.java:500)
        at io.grpc.internal.DelayedClientCall$DelayedListener.delayOrExecute(DelayedClientCall.java:464)
        at io.grpc.internal.DelayedClientCall$DelayedListener.onClose(DelayedClientCall.java:497)
        at io.grpc.internal.ClientCallImpl.closeObserver(ClientCallImpl.java:566)
        at io.grpc.internal.ClientCallImpl.access$100(ClientCallImpl.java:72)
        at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1StreamClosed.runInternal(ClientCallImpl.java:734)
        at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1StreamClosed.runInContext(ClientCallImpl.java:715)
        at io.grpc.internal.ContextRunnable.run(ContextRunnable.java:37)
        at io.grpc.internal.SerializingExecutor.run(SerializingExecutor.java:133)
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1090)
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:614)
        at java.base/java.lang.Thread.run(Thread.java:1474)
        Suppressed: com.google.api.gax.rpc.AsyncTaskException: Asynchronous task failed
                at com.google.api.gax.rpc.ApiExceptions.callAndTranslateApiException(ApiExceptions.java:57)
                at com.google.api.gax.rpc.UnaryCallable.call(UnaryCallable.java:112)
                at com.google.cloud.aiplatform.v1.PredictionServiceClient.rawPredict(PredictionServiceClient.java:743)
                at dev.langchain4j.model.vertexai.anthropic.internal.client.VertexAiAnthropicClient.generateContentWithRetry(VertexAiAnthropicClient.java:161)
                at dev.langchain4j.model.vertexai.anthropic.internal.client.VertexAiAnthropicClient.generateContent(VertexAiAnthropicClient.java:74)
                at dev.langchain4j.model.vertexai.anthropic.VertexAiAnthropicChatModel.doChat(VertexAiAnthropicChatModel.java:136)
                at dev.langchain4j.model.chat.ChatModel.chat(ChatModel.java:61)
                at dev.langchain4j.model.chat.ChatModel.chat(ChatModel.java:36)
                at dev.langchain4j.model.chat.ChatModel.chat(ChatModel.java:91)
                at io.onering.app.llm.VertexProviderIT.pingReturnsARealResponseFromVertex(VertexProviderIT.java:50)
Caused by: io.grpc.StatusRuntimeException: UNIMPLEMENTED: HTTP status code 404
invalid content-type: text/html; charset=UTF-8
headers: Metadata(:status=404,content-type=text/html; charset=UTF-8,referrer-policy=no-referrer,date=Tue, 28 Jul 2026 05:30:12 GMT,alt-svc=h3=":443"; ma=2592000,h3-29=":443"; ma=2592000,content-length=1616)
DATA-----------------------------
<!DOCTYPE html>
<html lang=en>
  <meta charset=utf-8>
  <meta name=viewport content="initial-scale=1, minimum-scale=1, width=device-width">
  <title>Error 404 (Not Found)!!1</title>
  <style>
    *{margin:0;padding:0}html,code{font:15px/22px arial,sans-serif}html{background:#fff;color:#222;padding:15px}body{margin:7% auto 0;max-width:390px;min-height:180px;padding:30px 0 15px}* > body{background:url(//www.google.com/images/errors/robot.png) 100% 5px no-repeat;padding-right:205px}p{margin:11px 0 22px;overflow:hidden}ins{color:#777;text-decoration:none}a img{border:0}@media screen and (max-width:772px){body{background:none;margin-top:0;max-width:none;padding-right:0}}#logo{background:url(//www.google.com/images/branding/googlelogo/1x/googlelogo_color_150x54dp.png) no-repeat;margin-left:-5px}@media only screen and (min-resolution:192dpi){#logo{background:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) no-repeat 0% 0%/100% 100%;-moz-border-image:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) 0}}@media only screen and (-webkit-min-device-pixel-ratio:2){#logo{background:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) no-repeat;-webkit-background-size:100% 100%}}#logo{display:inline-block;height:54px;width:150px}
  </style>
  <a href=//www.google.com/><span id=logo aria-label=Google></span></a>
  <p><b>404.</b> <ins>That’s an error.</ins>
  <p>The requested URL <code>/google.cloud.aiplatform.v1.PredictionService/RawPredict</code> was not found on this server.  <ins>That’s all we know.</ins>

        at io.grpc.Status.asRuntimeException(Status.java:532)
        ... 21 more
```

**To Reproduce**
```java
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assumptions.assumeTrue;

import com.google.auth.oauth2.GoogleCredentials;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.vertexai.anthropic.VertexAiAnthropicChatModel;
import io.onering.app.config.OneRingEnv;
import io.onering.core.config.Agent;
import java.io.IOException;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import org.junit.jupiter.api.Test;

class VertexProviderIT {

    private static final Function<Agent.Model, ChatModel> NO_MOCK = m -> null;

    @Test
    void pingReturnsARealResponseFromVertex() {
        System.Logger LOG = System.getLogger(ConfiguredChatModelResolver.class.getName());
        LOG.log(System.Logger.Level.DEBUG, "pingReturnsARealResponseFromVertex");

        Function<String, String> env = OneRingEnv.defaultSource();
        assumeTrue(nonBlank(env.apply("ANTHROPIC_VERTEX_PROJECT_ID")),
                "ANTHROPIC_VERTEX_PROJECT_ID not set — skipping live Vertex ping");
        assumeTrue(nonBlank(env.apply("CLOUD_ML_REGION")),
                "CLOUD_ML_REGION not set — skipping live Vertex ping");
        assumeTrue(applicationDefaultCredentialsAvailable(),
                "No Google Application Default Credentials available — skipping live Vertex ping");

        ConfiguredChatModelResolver resolver = new ConfiguredChatModelResolver(NO_MOCK, env, new TlsOptions());
        String modelName = Optional.ofNullable(env.apply("VERTEX_TEST_MODEL"))
                .filter(v -> !v.isBlank())
                .orElse("claude-opus-4-8");

        ChatModel model = resolver.resolve(
                new Agent.Model(Optional.of("vertex"), modelName, Map.of("maxTokens", 32)));

        String response = model.chat("Reply with exactly one word: ping");

        assertFalse(response == null || response.isBlank(), "expected a non-empty response from Vertex");
    }

    private static boolean nonBlank(String value) {
        return value != null && !value.isBlank();
    }

    /** Cheap pre-flight so a missing/expired ADC skips this test instead of failing it. */
    private static boolean applicationDefaultCredentialsAvailable() {
        try {
            GoogleCredentials.getApplicationDefault();
            return true;
        } catch (IOException e) {
            return false;
        }
    }
}
```


**Expected behavior**
A CLOUD_ML_REGION=global is routed correctly.

**Please complete the following information:**
- LangChain4j version:1.16.3-beta26 - current main
- LLM(s) used: anthropic claude-opus-4-8
- Java version: OpenJDK 64-Bit Server VM Temurin-25+36 (build 25+36-LTS, mixed mode, sharing)
- Spring Boot version (if applicable): n/a

**Additional context**
If I patch the langchain4j-vertex-ai-anthropic/src/main/java/dev/langchain4j/model/vertexai/anthropic/internal/client/VertexAiAnthropicClient.java as indicated in the attached patch file,

```bash
[INFO] Running io.onering.app.llm.VertexProviderIT
2026-07-27 23:31:54,272 DEBUG [io.onering.app.llm.ConfiguredChatModelResolver] (main) pingReturnsARealResponseFromVertex
2026-07-27 23:31:54,857 DEBUG [dev.langchain4j.model.vertexai.anthropic.VertexAiAnthropicChatModel] (main) Base URL: global-aiplatform.googleapis.com:443
2026-07-27 23:31:54,857 DEBUG [dev.langchain4j.model.vertexai.anthropic.VertexAiAnthropicChatModel] (main) Using model name: claude-opus-4-8 (from parameters: null, default: claude-opus-4-8)
2026-07-27 23:31:54,859 DEBUG [dev.langchain4j.model.vertexai.anthropic.VertexAiAnthropicChatModel] (main) Anthropic request: AnthropicRequest [anthropicVersion=vertex-2023-10-16, messages=1, maxTokens=32, stream=false, temperature=null, topP=null, topK=null, tools=null, toolsChoice=null]
2026-07-27 23:31:56,841 DEBUG [dev.langchain4j.model.vertexai.anthropic.VertexAiAnthropicChatModel] (main) Anthropic response: AnthropicResponse [id=msg_vrtx_011CdTucity4DEHth31yeNwT, type=message, role=assistant, model=claude-opus-4-8, content=1, stopReason=end_turn, stopSequence=null, usage=AnthropicUsage [inputTokens18, outputTokens4, cacheCreationInputTokens0, cacheReadInputTokens0]]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.591 s -- in io.onering.app.llm.VertexProviderIT
[INFO] 
[INFO] Results:
[INFO] 
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
```


[vertex-client.patch](https://github.com/user-attachments/files/30445092/vertex-client.patch)



Discussion:
@starksm64 thanks! Could you please open an a ctual PR with this patch?
PR created as #5869



Patch:
diff --git a/docs/docs/integrations/language-models/google-vertex-ai-anthropic.md b/docs/docs/integrations/language-models/google-vertex-ai-anthropic.md
index 53e23d66116..bffde03b419 100644
--- a/docs/docs/integrations/language-models/google-vertex-ai-anthropic.md
+++ b/docs/docs/integrations/language-models/google-vertex-ai-anthropic.md
@@ -186,7 +186,7 @@ You can learn about the models in the [Claude model documentation](https://docs.
 ```java
 ChatModel model = VertexAiAnthropicChatModel.builder()
     .project(PROJECT_ID)            // your Google Cloud project ID
-    .location(LOCATION)             // the region where AI inference should take place
+    .location(LOCATION)             // where inference takes place, see "Locations" below
     .modelName(MODEL_NAME)          // the Claude model used
     .maxTokens(4096)               // the maximum number of tokens to generate
     .temperature(0.7)              // temperature (between 0 and 1)
@@ -202,6 +202,30 @@ ChatModel model = VertexAiAnthropicChatModel.builder()
 
 The same parameters are also available on the streaming chat model.
 
+### Locations
+
+The `location` decides where your requests are processed. Vertex AI offers three kinds of locations,
+and the value you pass to `.location(...)` determines which one is used:
+
+| Location value | Kind | What it does |
+|---|---|---|
+| `"global"` | Global | Routes each request to any region with available capacity. Best availability, no pricing premium. Recommended unless you have data residency requirements. |
+| `"us"`, `"eu"` | Multi-region | Routes each request to a region within that geography, for data residency with high availability. |
+| `"us-east5"`, `"europe-west1"`, ... | Regional | Routes every request through one specific region. Required for single-region data residency and for provisioned throughput. |
+
+```java
+ChatModel model = VertexAiAnthropicChatModel.builder()
+    .project(PROJECT_ID)
+    .location("global")
+    .modelName(MODEL_NAME)
+    .build();
+```
+
+Note that model availability differs per location, and that multi-region and regional locations carry
+a pricing premium over the global one. See
+[Global, multi-region, and regional endpoints](https://platform.claude.com/docs/en/build-with-claude/claude-on-vertex-ai#global-multi-region-and-regional-endpoints)
+for details.
+
 ## More examples
 
 Claude is a powerful multimodal model that accepts both text and images as input.
diff --git a/langchain4j-vertex-ai-anthropic/src/main/java/dev/langchain4j/model/vertexai/anthropic/VertexAiAnthropicChatModel.java b/langchain4j-vertex-ai-anthropic/src/main/java/dev/langchain4j/model/vertexai/anthropic/VertexAiAnthropicChatModel.java
index 55654c41ffd..d7d5da8ab26 100644
--- a/langchain4j-vertex-ai-anthropic/src/main/java/dev/langchain4j/model/vertexai/anthropic/VertexAiAnthropicChatModel.java
+++ b/langchain4j-vertex-ai-anthropic/src/main/java/dev/langchain4j/model/vertexai/anthropic/VertexAiAnthropicChatModel.java
@@ -106,7 +106,7 @@ public ChatResponse doChat(ChatRequest chatRequest) {
             String requestModelName = getOrDefault(parameters.modelName(), modelName);
 
             if (logRequests) {
-                logger.debug("Base URL: {}-aiplatform.googleapis.com:443", location);
+                logger.debug("Base URL: {}", VertexAiAnthropicClient.resolveEndpoint(location));
                 logger.debug(
                         "Using model name: {} (from parameters: {}, default: {})",
                         requestModelName,
diff --git a/langchain4j-vertex-ai-anthropic/src/main/java/dev/langchain4j/model/vertexai/anthropic/internal/client/VertexAiAnthropicClient.java b/langchain4j-vertex-ai-anthropic/src/main/java/dev/langchain4j/model/vertexai/anthropic/internal/client/VertexAiAnthropicClient.java
index 48a6d4f7550..fce41f6b5a6 100644
--- a/langchain4j-vertex-ai-anthropic/src/main/java/dev/langchain4j/model/vertexai/anthropic/internal/client/VertexAiAnthropicClient.java
+++ b/langchain4j-vertex-ai-anthropic/src/main/java/dev/langchain4j/model/vertexai/anthropic/internal/client/VertexAiAnthropicClient.java
@@ -17,13 +17,16 @@
 import io.grpc.StatusRuntimeException;
 import java.io.IOException;
 import java.util.HashMap;
+import java.util.Locale;
 import java.util.Map;
 
 public class VertexAiAnthropicClient {
 
     private static final String PUBLISHER = "anthropic";
 
-    private static final String CREDENTIALS_ENDPOINT_TEMPLATE = "%s-aiplatform.googleapis.com:443";
+    private static final String GLOBAL_ENDPOINT = "aiplatform.googleapis.com:443";
+    private static final String MULTI_REGION_ENDPOINT_TEMPLATE = "aiplatform.%s.rep.googleapis.com:443";
+    private static final String REGIONAL_ENDPOINT_TEMPLATE = "%s-aiplatform.googleapis.com:443";
 
     private volatile PredictionServiceClient predictionServiceClient;
     private final String project;
@@ -49,7 +52,8 @@ public VertexAiAnthropicClient(String project, String location, String model, Go
         String ignoredModel = model;
 
         this.project = project;
-        this.location = location;
+        // Vertex AI locations are lowercase, both in the endpoint host and in the resource name
+        this.location = location.toLowerCase(Locale.ROOT);
         this.credentials = credentials;
         this.objectMapper = new ObjectMapper().disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
         this.predictionServiceClient = createClient();
@@ -58,7 +62,7 @@ public VertexAiAnthropicClient(String project, String location, String model, Go
     private PredictionServiceClient createClient() {
         try {
             PredictionServiceSettings.Builder settingsBuilder = PredictionServiceSettings.newBuilder();
-            settingsBuilder.setEndpoint(String.format(CREDENTIALS_ENDPOINT_TEMPLATE, location.toLowerCase()));
+            settingsBuilder.setEndpoint(resolveEndpoint(location));
             if (credentials != null) {
                 GoogleCredentials scopedCredentials =
                         credentials.createScoped("https://www.googleapis.com/auth/cloud-platform");
@@ -70,6 +74,30 @@ private PredictionServiceClient createClient() {
         }
     }
 
+    /**
+     * Maps a Vertex AI location to the API endpoint host serving it. Vertex AI uses a different host
+     * format for each of its three location types:
+     * <ul>
+     *     <li>{@code global} &rarr; {@code aiplatform.googleapis.com}</li>
+     *     <li>multi-region ({@code us}, {@code eu}) &rarr; {@code aiplatform.<location>.rep.googleapis.com}</li>
+     *     <li>region (e.g. {@code us-east5}) &rarr; {@code <location>-aiplatform.googleapis.com}</li>
+     * </ul>
+     * See <a href="https://platform.claude.com/docs/en/build-with-claude/claude-on-vertex-ai">Claude on Vertex AI</a>.
+     *
+     * @param location the Vertex AI location, case-insensitive
+     * @return the {@code host:port} endpoint to use for {@link PredictionServiceSettings}
+     */
+    public static String resolveEndpoint(String location) {
+        String normalized = location.toLowerCase(Locale.ROOT);
+        if ("global".equals(normalized)) {
+            return GLOBAL_ENDPOINT;
+        }
+        if ("us".equals(normalized) || "eu".equals(normalized)) {
+            return String.format(MULTI_REGION_ENDPOINT_TEMPLATE, normalized);
+        }
+        return String.format(REGIONAL_ENDPOINT_TEMPLATE, normalized);
+    }
+
     public AnthropicResponse generateContent(AnthropicRequest request, String modelName) throws IOException {
         return generateContentWithRetry(request, modelName, 1);
     }


Test patch:
diff --git a/langchain4j-vertex-ai-anthropic/src/test/java/dev/langchain4j/model/vertexai/anthropic/internal/client/VertexAiAnthropicClientTest.java b/langchain4j-vertex-ai-anthropic/src/test/java/dev/langchain4j/model/vertexai/anthropic/internal/client/VertexAiAnthropicClientTest.java
new file mode 100644
index 00000000000..33bde849018
--- /dev/null
+++ b/langchain4j-vertex-ai-anthropic/src/test/java/dev/langchain4j/model/vertexai/anthropic/internal/client/VertexAiAnthropicClientTest.java
@@ -0,0 +1,46 @@
+package dev.langchain4j.model.vertexai.anthropic.internal.client;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Basic validation of the {@link VertexAiAnthropicClient#resolveEndpoint(String)} behavior
+ */
+class VertexAiAnthropicClientTest {
+
+    @Test
+    void should_resolve_global_endpoint_without_location_prefix() {
+        assertThat(VertexAiAnthropicClient.resolveEndpoint("global")).isEqualTo("aiplatform.googleapis.com:443");
+    }
+
+    @Test
+    void should_resolve_multi_region_endpoints() {
+        assertThat(VertexAiAnthropicClient.resolveEndpoint("us")).isEqualTo("aiplatform.us.rep.googleapis.com:443");
+        assertThat(VertexAiAnthropicClient.resolveEndpoint("eu")).isEqualTo("aiplatform.eu.rep.googleapis.com:443");
+    }
+
+    @Test
+    void should_resolve_regional_endpoint() {
+        assertThat(VertexAiAnthropicClient.resolveEndpoint("us-east5"))
+                .isEqualTo("us-east5-aiplatform.googleapis.com:443");
+        assertThat(VertexAiAnthropicClient.resolveEndpoint("europe-west1"))
+                .isEqualTo("europe-west1-aiplatform.googleapis.com:443");
+    }
+
+    @Test
+    void should_not_treat_a_region_starting_with_a_multi_region_name_as_multi_region() {
+        assertThat(VertexAiAnthropicClient.resolveEndpoint("us-central1"))
+                .isEqualTo("us-central1-aiplatform.googleapis.com:443");
+        assertThat(VertexAiAnthropicClient.resolveEndpoint("europe-west4"))
+                .isEqualTo("europe-west4-aiplatform.googleapis.com:443");
+    }
+
+    @Test
+    void should_be_case_insensitive() {
+        assertThat(VertexAiAnthropicClient.resolveEndpoint("GLOBAL")).isEqualTo("aiplatform.googleapis.com:443");
+        assertThat(VertexAiAnthropicClient.resolveEndpoint("US")).isEqualTo("aiplatform.us.rep.googleapis.com:443");
+        assertThat(VertexAiAnthropicClient.resolveEndpoint("US-East5"))
+                .isEqualTo("us-east5-aiplatform.googleapis.com:443");
+    }
+}