Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## v1.12.0

### Jun 15, 2026

- Feature: Dynamic endpoint resolution via `Endpoint.getContentstackEndpoint()` and `Builder.setRegion()` backed by the Contentstack Regions Registry.

## v1.11.2

### Jun 01, 2026
Expand Down
22 changes: 21 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<artifactId>cms</artifactId>
<packaging>jar</packaging>
<name>contentstack-management-java</name>
<version>1.11.2</version>
<version>1.12.0</version>
<description>Contentstack Java Management SDK for Content Management API, Contentstack is a headless CMS with an
API-first approach
</description>
Expand Down Expand Up @@ -278,6 +278,26 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>download-regions</id>
<phase>generate-resources</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>bash</executable>
<arguments>
<argument>${project.basedir}/scripts/download-regions.sh</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
Expand Down
48 changes: 48 additions & 0 deletions scripts/download-regions.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#!/usr/bin/env bash
#
# Downloads the Contentstack regions registry from the official source and
# saves it to src/main/resources/regions.json.
#
# Invoked automatically by Maven on the generate-resources phase, and
# manually via: bash scripts/download-regions.sh
#
# Requires: curl (preferred) or wget as fallback

set -euo pipefail

URL="https://artifacts.contentstack.com/regions.json"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DEST="${SCRIPT_DIR}/../src/main/resources/regions.json"
DIR="$(dirname "$DEST")"

mkdir -p "$DIR"

data=""

# --- Attempt 1: curl (preferred) --------------------------------------------
if command -v curl &>/dev/null; then
data=$(curl --silent --fail --location --max-time 30 "$URL") || data=""
fi

# --- Attempt 2: wget fallback -----------------------------------------------
if [[ -z "$data" ]] && command -v wget &>/dev/null; then
data=$(wget --quiet --timeout=30 -O - "$URL") || data=""
fi

# --- Validate and write ------------------------------------------------------
if [[ -z "$data" ]]; then
echo "contentstack/cms: Warning — could not download regions.json." >&2
echo " The SDK will attempt to download it at runtime on first use." >&2
exit 0 # non-fatal: runtime fallback in Endpoint.java handles it
fi

# Basic validation: must contain a "regions" key
if ! echo "$data" | grep -q '"regions"'; then
echo "contentstack/cms: Warning — downloaded data is not valid regions.json." >&2
exit 0
fi

echo "$data" > "$DEST"

region_count=$(echo "$data" | grep -o '"id"' | wc -l | tr -d ' ')
echo "contentstack/cms: regions.json downloaded (${region_count} regions)."
110 changes: 110 additions & 0 deletions src/main/java/com/contentstack/cms/Contentstack.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.jetbrains.annotations.NotNull;

import com.contentstack.cms.core.AuthInterceptor;
import com.contentstack.cms.core.Endpoint;
import com.contentstack.cms.core.Util;
import static com.contentstack.cms.core.Util.API_KEY;
import static com.contentstack.cms.core.Util.AUTHORIZATION;
Expand Down Expand Up @@ -561,6 +562,81 @@ public CompletableFuture<Void> oauthLogout() {
return oauthLogout(false);
}

/**
* Forces a live download of the Contentstack regions registry and replaces
* the in-memory cache. Useful when new regions or service URLs have been
* published since the SDK JAR was built.
*
* <pre>{@code
* int count = Contentstack.refreshRegions();
* // now getContentstackEndpoint() resolves from the freshly downloaded registry
* }</pre>
*
* @return the number of regions loaded from the live registry
* @throws RuntimeException if the download fails
*/
public static int refreshRegions() {
return Endpoint.refresh();
}

/**
* Resolves a Contentstack service endpoint URL for the given region.
*
* <pre>{@code
* // Full URL
* String url = Contentstack.getContentstackEndpoint("eu", "contentManagement");
* // → "https://eu-api.contentstack.com"
*
* // Host only (for Builder.setHost)
* String host = Contentstack.getContentstackEndpoint("eu", "contentManagement", true);
* // → "eu-api.contentstack.com"
* }</pre>
*
* @param region region ID or alias (e.g. {@code na}, {@code eu}, {@code azure-na})
* @param service service key (e.g. {@code contentManagement}, {@code contentDelivery})
* @param omitHttps when {@code true}, strips {@code https://} from the returned URL
* @return the resolved URL string
* @throws IllegalArgumentException if region or service is unknown
*/
public static String getContentstackEndpoint(String region, String service, boolean omitHttps) {
return Endpoint.getContentstackEndpoint(region, service, omitHttps);
}

/**
* Resolves a Contentstack service endpoint URL for the given region (with scheme).
*
* @param region region ID or alias
* @param service service key
* @return the resolved URL including {@code https://}
* @throws IllegalArgumentException if region or service is unknown
*/
public static String getContentstackEndpoint(String region, String service) {
return Endpoint.getContentstackEndpoint(region, service);
}

/**
* Returns all endpoint URLs for the given region as an ordered map.
*
* @param region region ID or alias
* @return map of service name → URL (includes {@code https://})
* @throws IllegalArgumentException if region is unknown or empty
*/
public static java.util.Map<String, String> getContentstackEndpoints(String region) {
return Endpoint.getContentstackEndpoints(region);
}

/**
* Returns all endpoint URLs for the given region as an ordered map.
*
* @param region region ID or alias
* @param omitHttps when {@code true}, strips {@code https://} from every URL
* @return map of service name → URL
* @throws IllegalArgumentException if region is unknown or empty
*/
public static java.util.Map<String, String> getContentstackEndpoints(String region, boolean omitHttps) {
return Endpoint.getContentstackEndpoints(region, omitHttps);
}

public Contentstack(Builder builder) {
this.host = builder.hostname;
this.port = builder.port;
Expand All @@ -577,6 +653,16 @@ public Contentstack(Builder builder) {
this.retryConfig = builder.retryConfig;
}

/** Returns the API hostname this client is configured to target. */
public String getHost() {
return host;
}

/** Returns the full Retrofit base URL (e.g. {@code https://eu-api.contentstack.com/v3/}). */
public String getBaseUrl() {
return instance.baseUrl().toString();
}

public RetryConfig getRetryConfig() {
return retryConfig;
}
Expand Down Expand Up @@ -665,6 +751,30 @@ public Builder setHost(@NotNull String hostname) {
return this;
}

/**
* Configures the client to target a specific Contentstack region by resolving
* the correct Content Management API host from the bundled regions registry.
*
* <p>This is a convenience alternative to calling {@link #setHost(String)} with
* a manually constructed hostname.
*
* <pre>{@code
* Contentstack client = new Contentstack.Builder()
* .setAuthtoken("authtoken")
* .setRegion("eu")
* .build();
* }</pre>
*
* @param region region ID or alias (e.g. {@code "na"}, {@code "eu"}, {@code "azure-na"},
* {@code "azure-eu"}, {@code "gcp-na"}, {@code "gcp-eu"}, {@code "au"}).
* @return this Builder
* @throws IllegalArgumentException if the region is unknown or empty
*/
public Builder setRegion(@NotNull String region) {
this.hostname = Endpoint.getContentstackEndpoint(region, "contentManagement", true);
return this;
}

/**
* Set port for client instance
*
Expand Down
Loading
Loading