Java library for the Contentful Content Delivery API and Content Preview API. It helps you to easily access your content stored in Contentful with your Java applications.
What is Contentful?
Contentful provides content infrastructure for digital teams to power websites, apps, and devices. Unlike a CMS, Contentful was built to integrate with the modern software stack. It offers a central hub for structured content, powerful management and delivery APIs, and a customizable web app that enable developers and content creators to ship their products faster.
Table of contents
- Content retrieval through the Content Delivery API and Content Preview API.
- Synchronization with delta updates on subsequent calls.
- Localization support with locale fallback chains.
- Automatic link resolution, configurable up to 10 levels deep.
- Cross-space reference resolution, automatically linking entries and assets across multiple Contentful spaces.
- Support for Environments.
- Synchronous, callback-based, and reactive (RxJava 3) methods of fetching content.
- Unwrapping of
CDAEntryresponses into your own custom Java types via simple annotations. - Rich Text field decoding into a strongly typed node tree.
- A companion Rich Text renderer library for turning rich text into HTML or native Android output.
In order to get started with the Contentful Java library you'll need not only to install it, but also to get credentials which will allow you to have access to your content in Contentful.
| Requirement | Version |
|---|---|
| Java | 8 or higher |
| Android | API 21+ |
The SDK depends on OkHttp, Retrofit, Gson, and RxJava 3 for its networking, serialization, and reactive layers.
- Maven
<dependency>
<groupId>com.contentful.java</groupId>
<artifactId>java-sdk</artifactId>
<version>10.6.1</version>
</dependency>- Gradle
implementation 'com.contentful.java:java-sdk:10.6.1'The CDAClient manages all interactions with the Content Delivery API:
CDAClient client = CDAClient.builder()
.setSpace("{space-key-goes-here}")
.setToken("{access-token-goes-here}")
.build();Fetching content is achieved by calling the .fetch() method. It fetches all Resources from a Space. The following code fetches all Entries:
CDAArray array =
client
.fetch(CDAEntry.class)
.all();Grab credentials for your Contentful space by navigating to the "APIs" section of the Contentful Web App. The Space ID and Access Token are retrieved from there.
Delivery tokens only return published content, while preview tokens return the latest draft of your content. Never hard-code tokens into a shipping app; inject them from your build configuration or a secure store instead.
The Content Delivery API only returns published Entries. The Content Preview API returns all Entries, even ones that aren't published yet:
CDAClient client =
CDAClient.builder()
.setSpace("space-key-goes-here")
.setToken("access-token-goes-here")
.preview()
.build();The Preview Access Token is exposed on the Contentful Web App.
Note: In Preview, Resources can be invalid since no validation is performed prior to publishing.
Filtering of Resources can be done by chaining method calls after .fetch(). Using .one() and a Resource id retrieves only the specified Resource:
CDAEntry entry =
client
.fetch(CDAEntry.class)
.one("{entry-id-goes-here}");Fetching only Entries of a specific Content Type is done by adding the .withContentType({id}) call to the chain:
CDAArray result =
client
.fetch(CDAEntry.class)
.withContentType("{content-type-id-goes-here}")
.orderBy("{some-field-id-to-order-by-goes-here}")
.all();Fetching Assets follows the same principles:
// Fetch an Asset with a specific id
CDAAsset asset =
client
.fetch(CDAAsset.class)
.one("{asset-id-goes-here}");All of the above examples are executed synchronously. To request content asynchronously, provide a callback to .all(…) or .one(…):
client
.fetch(CDAAsset.class)
.all(new CDACallback<CDAArray>() {
@Override protected void onSuccess(CDAArray result) {
// ...
}
});Note: The return value for any asynchronous method is the callback itself. Keeping a reference to it and clearing it according to its host's lifecycle events is advised.
If RxJava is preferred instead, use .observe() to get an Observable instance:
client
.observe(CDAAsset.class)
.one("jake")
.subscribe(System.out::println);If more than 100 Resources are in the Space, .all() only returns the first 100. If more Resources are needed, specify the limit with .limit(X):
CDAArray result =
client
.fetch(CDAEntry.class)
.limit(1000)
.all();The maximum number of Resources requestable in one call is 1000.
For more than 1000 Resources, .skip(N), .limit(L), and .orderBy(F) are needed together. .skip(N) ignores the first N Resources, and L items (from .limit(L)) are returned. To guarantee a stable order across paged requests, use .orderBy(…):
// Get the amount of Entries, without fetching the actual content.
final int amountOfResourcesInContentful =
client
.fetch(CDAEntry.class)
.limit(0)
.all()
.total();
// Create storage for the Entries.
final List<CDAResource> resources = new ArrayList<CDAResource>(amountOfResourcesInContentful);
// Use a page size based on your use case.
final int PAGE_SIZE = 2;
// Loop through all pages and store results.
for (int page = 0; page * PAGE_SIZE < amountOfResourcesInContentful; ++page) {
final CDAArray currentPagedItems = client
.fetch(CDAEntry.class)
.skip(page * PAGE_SIZE)
.limit(PAGE_SIZE)
.orderBy("sys.createdAt")
.all();
resources.addAll(currentPagedItems.items());
}Use .reverseOrderBy() to reverse the order:
CDAArray result =
client
.fetch(CDAEntry.class)
.limit(23)
.reverseOrderBy("sys.createdAt")
.all();The above snippet fetches the first 23 Entries, sorted by creation date with the latest ones on top.
Sync is the recommended approach for fetching all entries in a single initial call and getting only changed Resources on subsequent calls.
The library resolves links automatically: a simple .getField(…) retrieves a linked entry directly, without needing to look up the entry by id manually.
For link resolution to work, the linked entry needs to be published (see Preview), and the include level needs to be set to include it. A level of 2 means links-of-links are also resolved. Entries beyond the requested depth contain an empty field where the link could not be resolved; compare .rawFields with .fields to find the id of an unresolved field.
CDAArray found = client.fetch(CDAEntry.class)
.include(1) // Maximum is 10.
.all();10 is the maximum number of levels to include, and should be used sparingly since it can bloat the response significantly.
The library supports resolving cross-space references, letting you link content across multiple Contentful spaces. When cross-space tokens are configured, entries and assets from other spaces are automatically included in the response's includes section and resolved by the library's link resolution.
To enable cross-space reference resolution, provide access tokens for the additional spaces:
Map<String, String> crossSpaceTokens = new HashMap<>();
crossSpaceTokens.put("space-id-1", "cda-token-for-space-1");
crossSpaceTokens.put("space-id-2", "cda-token-for-space-2");
CDAClient client = CDAClient.builder()
.setSpace("main-space-id")
.setToken("main-space-token")
.setCrossSpaceTokens(crossSpaceTokens)
.build();
// Cross-space references will now be automatically resolved.
CDAArray entries = client.fetch(CDAEntry.class)
.include(2)
.all();A few limits apply:
- Maximum 20 extra spaces can be configured (21 total including the main space).
- Only the first level of cross-space references is resolved (similar to
include=1for cross-space). - The main space can still resolve up to 10 levels of includes.
- Cross-space errors are returned via
CDAArray.getErrors().
For more information, see the Contentful Resource Links documentation.
Unwrapping is the process of taking a CDAEntry and transforming it into your own custom types:
import com.contentful.java.cda.TransformQuery.ContentfulEntryModel;
import com.contentful.java.cda.TransformQuery.ContentfulField;
@ContentfulEntryModel("cat")
public static class Cat {
@ContentfulField
String name;
@ContentfulField("bestFriend")
Cat mate;
@ContentfulField
FavoriteFood favoriteFood;
@ContentfulSystemField("id")
String contentfulId;
@ContentfulField(value = "likes", locale = "de-DE")
List<String> germanFavorites;
}To have the library return your custom type instead of a CDAEntry:
Cat happycat = client
.observeAndTransform(Cat.class)
.one("happycat")
.blockingFirst();Unwrapping also uses the select filter under the hood to only return the fields required, making the response smaller and more focused.
Notes:
- Specifying a
valuefor@ContentfulFielduses the value as the field id instead of the name of the annotated field.- A
localecan be specified for a given field. If omitted, the default locale is used.@ContentfulSystemFieldis used to populateCDAEntryattributes (sys.id, etc).- Any nested type must also be annotated with
@ContentfulEntryModel, similar toCatabove.- Limitation: Unwrapping does not currently allow direct access to the raw JSON for rich text fields, since the library automatically transforms fields into the custom model structure. Use the
rawFieldsmap onCDAEntryto access the unprocessed JSON of any field, including rich text, or make a direct HTTP request to the Contentful API for the full raw JSON response.
The amount of data returned by the API can be reduced with .select(). The library always requests the sys fields (.getAttribute() on an Entry), since they're required for the library to function correctly:
CDAArray found = client.fetch(CDAEntry.class)
.withContentType("cat")
.select("fields.name");This ensures entries of type cat only contain their name field — all other fields are null or their default value.
Note: The content type must be added through
.withContentType(…), otherwise an error is thrown.
Fetching all Resources initially, and only changes on subsequent calls, is accomplished with the .sync() methods:
SynchronizedSpace space = client.sync().fetch();The SynchronizedSpace contains all published Resources. If .preview() (see Preview) is used, it also contains unpublished Resources.
To fetch changes later, call .sync() again, passing the previous SynchronizedSpace as a parameter:
SynchronizedSpace later = client.sync(space).fetch();If an Entry is deleted, its id is returned in SynchronizedSpace.deletedEntries(). The same is true for deleted Assets via SynchronizedSpace.deletedAssets().
Rich text fields decode into a CDARichDocument, the base of all rich text nodes in the SDK:
final CDARichDocument node = entry.getField(FIELD_ID);If your data comes from an external tool (for example a JavaScript library), you can build a CDARichDocument from plain JSON — useful when the content wasn't fetched directly through this library. Using GSON for JSON processing:
private final Gson gson = new Gson();
Type type = new TypeToken<Map<String, Object>>(){}.getType();
Map<String, Object> jsonMap = gson.fromJson(json, type);
final CDARichDocument node = RichTextFactory.resolveRichNode(jsonMap);To turn a rich text node tree into HTML or native Android output, use the companion rich-text-renderer-java library — see Rich Text renderer library.
Changing the settings of the HTTP client, without losing the information set up during the client build process, is achieved by requesting the .defaultCallFactoryBuilder() from the CDAClient.Builder, changing it, then reapplying it:
// Create a client builder as usual.
CDAClient.Builder clientBuilder = CDAClient.builder()
.setSpace("space-id-goes-here")
.setEnvironment("environment-id-goes-here") // Optional.
.setToken("cda-token-goes-here");
// Request the http client with the settings from above (token, error interceptor, etc).
OkHttpClient httpClient = clientBuilder.defaultCallFactoryBuilder()
.addInterceptor(interceptor) // Adding a custom interceptor.
.connectTimeout(5, TimeUnit.SECONDS) // Adding a timeout.
.cache(new Cache(new File("/tmp"), CACHE_SIZE_BYTES)) // Adding a simple HTTP cache.
.build();
// Reapply the http changes and build a Contentful client.
CDAClient cdaClient = clientBuilder.setCallFactory(httpClient).build();OkHttp 5 splits platform artifacts: okhttp-jvm for the JVM and okhttp-android for Android. No configuration is needed from 10.7.0:
- Gradle (Android and JVM): the SDK publishes Gradle module metadata, and Gradle resolves
okhttptookhttp-androidin Android apps and tookhttp-jvmon the JVM. - Maven (JVM): the POM includes
okhttp-jvm, as before.
For 10.6.1 and earlier on Android, exclude it:
implementation('com.contentful.java:java-sdk:10.6.1') {
exclude group: 'com.squareup.okhttp3', module: 'okhttp-jvm'
}The ProGuard configuration file is used to minify Android apps that use this library.
For further information about the underlying REST API, check out the Content Delivery API Reference Documentation. Browse the JavaDoc for the full API reference of this library.
Every released change is recorded in the CHANGELOG.md.
There is a Java library for the Rich Text API. It helps you easily render rich text stored in Contentful into HTML or native Android views.
Development versions of this library are available through:
maven { url 'https://oss.sonatype.org/content/repositories/snapshots' }
implementation 'com.contentful.java:java-sdk:10.4.1-SNAPSHOT'maven { url 'https://jitpack.io' }
implementation 'com.github.contentful:contentful.java:java-sdk-10.4.1-SNAPSHOT'- File an issue here on GitHub:
. Make sure to remove any credential from your code before sharing it.
We appreciate any help on our repositories. For more details about how to contribute, see CONTRIBUTING.md.
For a reproducible local setup, open this repository in its included dev container. The container installs the project dependencies automatically when it is created.
After the container is ready, run:
./mvnw -B testSee CONTRIBUTING.md for the full contributor workflow, including commit conventions and the release process.
This repository is published under the Apache 2.0 license.
We want to provide a safe, inclusive, welcoming, and harassment-free space and experience for all participants, regardless of gender identity and expression, sexual orientation, disability, physical appearance, socioeconomic status, body size, ethnicity, nationality, level of experience, age, religion (or lack thereof), or other identity markers.
Read our full Code of Conduct.
| Document | What it covers |
|---|---|
| AGENTS.md | Agent-first context directory — read this first |
| ARCHITECTURE.md | Internal structure, data flows, component map, integration points |
| CONTRIBUTING.md | Development setup, workflow, release process, CI |
| docs/ADRs/ | Why things look the way they do — architecture decisions |
| docs/specs/ | Active and recent implementation specs |
| .bito/guidelines/ | PR review posture and domain invariants |
