Callbacks
Callbacks
A callback receives the result of a request. Every request method in the Java Delivery SDK takes one, and calls it with the data on success or with an Error on failure. Use the callback classes to
- receive entries, assets, content types, global fields, sync results, and taxonomy results
- handle a failed request
- observe every request and response through a plugin
The table below maps each request method to its callback and the method you implement:
| Request method | Callback | Method you implement |
|---|---|---|
| fetch on Entry | EntryResultCallBack | onCompletion(ResponseType responseType, Error error) |
| find on Query | QueryResultsCallBack | onCompletion(ResponseType responseType, QueryResult queryresult, Error error) |
| findOne on Query | SingleQueryResultCallback | onCompletion(ResponseType responseType, Entry entry, Error error) |
| fetch on Asset | FetchResultCallback | onCompletion(ResponseType responseType, Error error) |
| fetchAll on AssetLibrary | FetchAssetsCallback | onCompletion(ResponseType responseType, List<Asset> assets, Error error) |
| fetch on ContentType, getContentTypes on Stack | ContentTypesCallback | onCompletion(ContentTypesModel contentTypesModel, Error error) |
| fetch and findAll on GlobalField | GlobalFieldsCallback | onCompletion(GlobalFieldsModel globalFieldsModel, Error error) |
| sync and the other sync methods on Stack | SyncResultCallBack | onCompletion(SyncStack syncStack, Error error) |
| find on Taxonomy | TaxonomyCallback | onResponse(JSONObject response, Error error) |
| Every request, through setPlugins on Config | ContentstackPlugin | onRequest and onResponse, both optional |
onCompletion
onCompletion receives the result or the error of a request. You implement it on each of the eight result callback classes, and each class gives it a different signature. The sections below list each one.
Returns: void. This method returns nothing.
EntryResultCallBack and FetchResultCallback
public abstract void onCompletion(ResponseType responseType, Error error)
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| responseType | ResponseType | Yes | Not applicable | Source of the result. NETWORK on every request result. |
| error | Error | Yes | Not applicable | The failure, or null on success. |
QueryResultsCallBack
public abstract void onCompletion(ResponseType responseType, QueryResult queryresult, Error error)
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| responseType | ResponseType | Yes | Not applicable | Source of the result. NETWORK on every request result. |
| queryresult | QueryResult | Yes | Not applicable | The matching entries, or null on failure. |
| error | Error | Yes | Not applicable | The failure, or null on success. |
SingleQueryResultCallback
public abstract void onCompletion(ResponseType responseType, Entry entry, Error error)
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| responseType | ResponseType | Yes | Not applicable | Source of the result. NETWORK on every request result. |
| entry | Entry | Yes | Not applicable | The first matching entry, or null on failure. |
| error | Error | Yes | Not applicable | The failure, or null on success. |
FetchAssetsCallback
public abstract void onCompletion(ResponseType responseType, List<Asset> assets, Error error)
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| responseType | ResponseType | Yes | Not applicable | Source of the result. NETWORK on every request result. |
| assets | List<Asset> | Yes | Not applicable | The matching assets, or null on failure. |
| error | Error | Yes | Not applicable | The failure, or null on success. |
ContentTypesCallback
public abstract void onCompletion(ContentTypesModel contentTypesModel, Error error)
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| contentTypesModel | ContentTypesModel | Yes | Not applicable | The content type data, or null on failure. |
| error | Error | Yes | Not applicable | The failure, or null on success. |
GlobalFieldsCallback
public abstract void onCompletion(GlobalFieldsModel globalFieldsModel, Error error)
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| globalFieldsModel | GlobalFieldsModel | Yes | Not applicable | The global field data, or null on failure. |
| error | Error | Yes | Not applicable | The failure, or null on success. |
SyncResultCallBack
public abstract void onCompletion(SyncStack syncStack, Error error)
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| syncStack | SyncStack | Yes | Not applicable | One page of sync results, or null on failure. |
| error | Error | Yes | Not applicable | The failure, or null on success. |
Validation
- There is no client-side validation. The SDK calls onCompletion, and your code implements it.
- The compiler rejects a throws clause that names a checked exception, because the abstract method declares none. Handle checked exceptions inside onCompletion.
- The request method does not throw on an API rejection or a network failure. It passes an Error to onCompletion instead, so check error for null before you read the data.
Additional Resource Refer to Content Delivery API Errors for the full error code list.
Behavior
- On success, error is null and the data argument holds the result. On failure, every data argument is null.
- EntryResultCallBack and FetchResultCallback have no data argument. Read the result from the entry or asset that you called fetch on.
- For SingleQueryResultCallback, a query that matches nothing still succeeds. entry is then an empty Entry whose getUid() returns null, not a null reference.
- For ContentTypesCallback, the fetch method of ContentType fills the content type object's getters before it calls onCompletion.
- By default, the SDK reaches onCompletion through onRequestFail for every failure. An onRequestFail override that does not call super stops failures from reaching onCompletion.
- Known issue: When the fetchAll method of AssetLibrary matches no assets, the SDK never calls onCompletion on the FetchAssetsCallback. The request method still returns. Set a flag inside onCompletion and check it after fetchAll returns, as the second example below does.
- Known issue: A JSONException thrown from onCompletion on a successful result makes the SDK call onCompletion again, with an Error for an invalid JSON response. Catch JSONException inside onCompletion, or read fields with the opt methods of JSONObject, as the third example below does. The issue does not apply to TaxonomyCallback.
Example
The Callbacks class page above has one example for each of the eight callback classes.
Basic usage: handle the result of an entry fetch
import com.contentstack.sdk.Contentstack;
import com.contentstack.sdk.Entry;
import com.contentstack.sdk.EntryResultCallBack;
import com.contentstack.sdk.Error;
import com.contentstack.sdk.ResponseType;
import com.contentstack.sdk.Stack;
public class FetchEntry {
public static void main(String[] args) throws IllegalAccessException {
Stack stack = Contentstack.stack("<API_KEY>", "<DELIVERY_TOKEN>", "<ENVIRONMENT>");
Entry entry = stack.contentType("<CONTENT_TYPE_UID>").entry("<ENTRY_UID>");
entry.fetch(new EntryResultCallBack() {
@Override
public void onCompletion(ResponseType responseType, Error error) {
if (error != null) {
// The API rejected the request, or it did not complete
System.out.println("Request failed: " + error.getErrorCode() + " " + error.getErrorMessage());
return;
}
System.out.println(entry.getTitle());
}
});
}
}Edge case: detect an asset list with no assets
import com.contentstack.sdk.Asset;
import com.contentstack.sdk.Contentstack;
import com.contentstack.sdk.Error;
import com.contentstack.sdk.FetchAssetsCallback;
import com.contentstack.sdk.ResponseType;
import com.contentstack.sdk.Stack;
import java.util.List;
public class DetectNoAssets {
public static void main(String[] args) throws IllegalAccessException {
Stack stack = Contentstack.stack("<API_KEY>", "<DELIVERY_TOKEN>", "<ENVIRONMENT>");
final boolean[] called = {false};
stack.assetLibrary().fetchAll(new FetchAssetsCallback() {
@Override
public void onCompletion(ResponseType responseType, List<Asset> assets, Error error) {
called[0] = true;
if (error != null) { System.out.println(error.getErrorMessage()); return; }
System.out.println(assets.size() + " assets");
}
});
// fetchAll returns only after the request finishes, so the flag is up to date here
if (!called[0]) {
System.out.println("0 assets");
}
}
}Edge case: catch a JSONException inside the callback
import com.contentstack.sdk.Contentstack;
import com.contentstack.sdk.Entry;
import com.contentstack.sdk.EntryResultCallBack;
import com.contentstack.sdk.Error;
import com.contentstack.sdk.ResponseType;
import com.contentstack.sdk.Stack;
import org.json.JSONException;
public class ReadAuthorSafely {
public static void main(String[] args) throws IllegalAccessException {
Stack stack = Contentstack.stack("<API_KEY>", "<DELIVERY_TOKEN>", "<ENVIRONMENT>");
Entry entry = stack.contentType("<CONTENT_TYPE_UID>").entry("<ENTRY_UID>");
entry.fetch(new EntryResultCallBack() {
@Override
public void onCompletion(ResponseType responseType, Error error) {
if (error != null) { System.out.println(error.getErrorMessage()); return; }
try {
// getJSONObject throws JSONException when the entry has no author object
System.out.println(entry.toJSON().getJSONObject("author").getString("title"));
} catch (JSONException e) {
System.out.println("No author: " + e.getMessage());
}
}
});
}
}onRequest
onRequest observes each request on a ContentstackPlugin before the SDK sends it.
| Name | Type | Description |
|---|---|---|
| stack (required) | Stack | The stack that sends the request. |
| request (required) | Request | The okhttp3.Request that the SDK is about to send. |
Returns: Request. The request, which the SDK ignores.
Validation
- There is no client-side validation. The SDK calls onRequest, and your code implements it.
- An exception that onRequest throws stops the request before the SDK sends it. The SDK then does not call the request's callback.
Additional Resource Refer to Content Delivery API Errors for the full error code list.
Behavior
- Signature: default Request onRequest(Stack stack, Request request).
- The SDK calls onRequest once per request method call, before the first attempt. Retries do not call it again.
- request holds the full URL with its query parameters, and every header the SDK sets on the request.
- Known issue: The SDK ignores the Request that onRequest returns. An OkHttp Request cannot change in place, so a plugin cannot add a header or change the URL. To add a header to every request, call setHeader on the stack.
- The SDK calls onRequest only when the Config holds a plugin list, and runs each plugin in list order. Register plugins with the setPlugins method of Config.
- Taxonomy.find and Stack.livePreviewQuery send their own requests and do not call plugins.
- The default body returns request unchanged, so a plugin that needs only onResponse does not override onRequest.
Warning The headers include api_key and access_token, which hold your stack API key and delivery token. Do not log the headers in full.
Example
Basic usage: log the URL of every request
import com.contentstack.sdk.Config;
import com.contentstack.sdk.Contentstack;
import com.contentstack.sdk.ContentstackPlugin;
import com.contentstack.sdk.Entry;
import com.contentstack.sdk.EntryResultCallBack;
import com.contentstack.sdk.Error;
import com.contentstack.sdk.ResponseType;
import com.contentstack.sdk.Stack;
import okhttp3.Request;
import java.util.ArrayList;
import java.util.List;
public class LogRequestUrls {
public static void main(String[] args) throws IllegalAccessException {
List<ContentstackPlugin> plugins = new ArrayList<>();
plugins.add(new ContentstackPlugin() {
@Override
public Request onRequest(Stack stack, Request request) {
// The URL carries the query parameters, not the tokens
System.out.println(request.method() + " " + request.url());
return request;
}
});
// Register the plugins before creating the stack
Config config = new Config();
config.setPlugins(plugins);
Stack stack = Contentstack.stack("<API_KEY>", "<DELIVERY_TOKEN>", "<ENVIRONMENT>", config);
Entry entry = stack.contentType("<CONTENT_TYPE_UID>").entry("<ENTRY_UID>");
entry.fetch(new EntryResultCallBack() {
@Override
public void onCompletion(ResponseType responseType, Error error) {
if (error != null) {
// The API rejected the request, or it did not complete
System.out.println("Request failed: " + error.getErrorCode() + " " + error.getErrorMessage());
return;
}
System.out.println(entry.getTitle());
}
});
}
}Edge case: add a header with setHeader instead of the plugin
import com.contentstack.sdk.Contentstack;
import com.contentstack.sdk.Error;
import com.contentstack.sdk.QueryResult;
import com.contentstack.sdk.QueryResultsCallBack;
import com.contentstack.sdk.ResponseType;
import com.contentstack.sdk.Stack;
public class AddHeaderToEveryRequest {
public static void main(String[] args) throws IllegalAccessException {
Stack stack = Contentstack.stack("<API_KEY>", "<DELIVERY_TOKEN>", "<ENVIRONMENT>");
// A plugin cannot add this header, because the SDK ignores the Request that onRequest returns
stack.setHeader("branch", "<BRANCH>");
stack.contentType("<CONTENT_TYPE_UID>").query().find(new QueryResultsCallBack() {
@Override
public void onCompletion(ResponseType responseType, QueryResult queryResult, Error error) {
if (error != null) { System.out.println(error.getErrorMessage()); return; }
System.out.println(queryResult.getResultObjects().size() + " entries");
}
});
}
}onRequestFail
onRequestFail receives a failed request on any of the eight result callback classes. Its default body forwards the failure to onCompletion, so overriding it is optional.
| Name | Type | Description |
|---|---|---|
| responseType (required) | ResponseType | Source of the result. NETWORK on every request result. |
| error (required) | Error | The failure. Never null here. |
Returns: void. This method returns nothing.
Validation
- There is no client-side validation. The SDK calls onRequestFail, and your code can override it.
- The compiler rejects a throws clause that names a checked exception, because the interface method declares none.
Additional Resource Refer to Content Delivery API Errors for the full error code list.
Behavior
- Signature: public void onRequestFail(ResponseType responseType, Error error).
- The SDK calls onRequestFail for every failed request: an API rejection, a timeout, a network failure, or a response body it cannot read. The Error class page lists what the Error holds in each case.
- onRequestFail comes from the ResultCallBack interface, which all eight result callback classes implement. Each class gives it a default body that calls onCompletion with null for every data argument and the error.
- Override it to keep failure handling out of onCompletion. An override that does not call super.onRequestFail(responseType, error) stops failures from reaching onCompletion, so onCompletion then runs on success only.
- The fetch method of ContentType wraps your ContentTypesCallback in one of its own. It never calls your onRequestFail override, and passes the failure to your onCompletion instead. The getContentTypes method of Stack calls your override.
- TaxonomyCallback and ContentstackPlugin have no onRequestFail. Taxonomy.find passes its failure to onResponse.
Example
Basic usage: handle failures in their own method
import com.contentstack.sdk.Contentstack;
import com.contentstack.sdk.Entry;
import com.contentstack.sdk.Error;
import com.contentstack.sdk.QueryResult;
import com.contentstack.sdk.QueryResultsCallBack;
import com.contentstack.sdk.ResponseType;
import com.contentstack.sdk.Stack;
public class SeparateFailureHandling {
public static void main(String[] args) throws IllegalAccessException {
Stack stack = Contentstack.stack("<API_KEY>", "<DELIVERY_TOKEN>", "<ENVIRONMENT>");
stack.contentType("<CONTENT_TYPE_UID>").query().find(new QueryResultsCallBack() {
@Override
public void onCompletion(ResponseType responseType, QueryResult queryResult, Error error) {
if (error != null) {
// Not reached: onRequestFail below does not forward failures
return;
}
for (Entry entry : queryResult.getResultObjects()) {
System.out.println(entry.getTitle());
}
}
@Override
public void onRequestFail(ResponseType responseType, Error error) {
// The API rejected the request, or it did not complete
System.out.println("Request failed: " + error.getErrorCode() + " " + error.getErrorMessage());
}
});
}
}Edge case: log the failure and still forward it to onCompletion
import com.contentstack.sdk.Asset;
import com.contentstack.sdk.Contentstack;
import com.contentstack.sdk.Error;
import com.contentstack.sdk.FetchResultCallback;
import com.contentstack.sdk.ResponseType;
import com.contentstack.sdk.Stack;
public class LogAndForwardFailure {
public static void main(String[] args) throws IllegalAccessException {
Stack stack = Contentstack.stack("<API_KEY>", "<DELIVERY_TOKEN>", "<ENVIRONMENT>");
Asset asset = stack.asset("<ASSET_UID>");
asset.fetch(new FetchResultCallback() {
@Override
public void onCompletion(ResponseType responseType, Error error) {
if (error != null) { System.out.println(error.getErrorMessage()); return; }
System.out.println(asset.getFileName());
}
@Override
public void onRequestFail(ResponseType responseType, Error error) {
System.err.println("Asset fetch failed with code " + error.getErrorCode());
super.onRequestFail(responseType, error); // keeps the default call to onCompletion
}
});
}
}onRequestFinish
onRequestFinish receives the assets of a successful fetchAll request on a FetchAssetsCallback, and forwards them to onCompletion by default.
| Name | Type | Description |
|---|---|---|
| responseType (required) | ResponseType | Source of the result. NETWORK on every request result. |
| assets (required) | List<Asset> | The assets that the request returned. |
Returns: void. This method returns nothing.
Validation
- There is no client-side validation. The SDK calls onRequestFinish on success only. Failures go to onRequestFail.
Additional Resource Refer to Content Delivery API Errors for the full error code list.
Behavior
- Signature: public void onRequestFinish(ResponseType responseType, List<Asset> assets).
- The default body calls onCompletion with the same responseType, the assets, and null for the error.
- Overriding it is optional. An override that does not call super.onRequestFinish(responseType, assets) stops successful results from reaching onCompletion.
- Only FetchAssetsCallback makes onRequestFinish public. The other result callback classes declare it package-private, so your code cannot call or override it there.
- The SDK does not call onRequestFinish when fetchAll matches no assets, so onCompletion does not run either. Set a flag inside onCompletion and check it after fetchAll returns, as the Known issue on the onCompletion method shows.
- Calling onRequestFinish from your own code runs your onCompletion with the list you pass, without a request.
Example
Basic usage: count the assets before onCompletion runs
import com.contentstack.sdk.Asset;
import com.contentstack.sdk.Contentstack;
import com.contentstack.sdk.Error;
import com.contentstack.sdk.FetchAssetsCallback;
import com.contentstack.sdk.ResponseType;
import com.contentstack.sdk.Stack;
import java.util.List;
public class CountFetchedAssets {
public static void main(String[] args) throws IllegalAccessException {
Stack stack = Contentstack.stack("<API_KEY>", "<DELIVERY_TOKEN>", "<ENVIRONMENT>");
stack.assetLibrary().fetchAll(new FetchAssetsCallback() {
@Override
public void onCompletion(ResponseType responseType, List<Asset> assets, Error error) {
if (error != null) {
// The API rejected the request, or it did not complete
System.out.println("Request failed: " + error.getErrorCode() + " " + error.getErrorMessage());
return;
}
for (Asset asset : assets) {
System.out.println(asset.getFileName());
}
}
@Override
public void onRequestFinish(ResponseType responseType, List<Asset> assets) {
System.out.println("Received " + assets.size() + " assets");
super.onRequestFinish(responseType, assets); // keeps the default call to onCompletion
}
});
}
}onResponse
onResponse receives the result of Taxonomy.find on a TaxonomyCallback, and observes each successful response on a ContentstackPlugin. The two interfaces give it different signatures. The sections below list each one.
Returns: void or Response<ResponseBody>. Nothing for TaxonomyCallback, and the response for ContentstackPlugin.
TaxonomyCallback
void onResponse(JSONObject response, Error error)
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| response | JSONObject | Yes | Not applicable | The parsed response body, or null on failure. |
| error | Error | Yes | Not applicable | The failure, or null on success. |
ContentstackPlugin
default retrofit2.Response<okhttp3.ResponseBody> onResponse(Stack stack, Request request, retrofit2.Response<okhttp3.ResponseBody> response)
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| stack | Stack | Yes | Not applicable | The stack that sent the request. |
| request | Request | Yes | Not applicable | The okhttp3.Request that the SDK sent. |
| response | Response<ResponseBody> | Yes | Not applicable | The successful retrofit2.Response, body not yet read. |
Validation
- There is no client-side validation. The SDK calls onResponse, and your code implements it.
- On TaxonomyCallback, the find method of Taxonomy throws instead of calling onResponse in two cases. Wrap find in a try block that catches RuntimeException for both:
- A network failure, such as a timeout, throws RuntimeException.
- A response body that is not JSON throws JSONException.
- On ContentstackPlugin, an exception that onResponse throws stops the SDK from processing the response. The SDK then does not call the request's callback.
Additional Resource Refer to Content Delivery API Errors for the full error code list.
Behavior
TaxonomyCallback.
- On success, response holds the parsed body of the Get all entries with defined taxonomies request, and error is null. Read the entries with response.optJSONArray("entries").
- On an API rejection, response is null and error holds the body's values. Taxonomy.find uses its own fallbacks: a missing error_message or errors becomes an empty string, and a missing error_code becomes 0.
- TaxonomyCallback is an interface with one method, so a lambda works.
ContentstackPlugin.
- The SDK calls onResponse once per request, after its retries finish and before it parses the body. Each plugin runs in list order.
- The SDK calls onResponse only for a success status. An error status, a timeout, or a network failure goes to the request's callback as an Error, and plugins do not see it.
- Known issue: The SDK ignores the Response that onResponse returns, so a plugin cannot replace the response. Use onResponse to observe the status and headers only.
- A plugin that reads response.body() in onResponse leaves the SDK nothing to parse, and the request's callback receives an Error. OkHttp allows one read of a response body, and the SDK reads it after the plugins return. Do not read the body in onResponse.
- Taxonomy.find and Stack.livePreviewQuery send their own requests and do not call plugins.
- The default body returns response unchanged, so a plugin that needs only onRequest does not override onResponse.
Example
Basic usage: find taxonomy entries with a TaxonomyCallback
import com.contentstack.sdk.Contentstack;
import com.contentstack.sdk.Error;
import com.contentstack.sdk.Stack;
import com.contentstack.sdk.TaxonomyCallback;
import org.json.JSONArray;
import org.json.JSONObject;
import java.util.Arrays;
public class FindTaggedEntries {
public static void main(String[] args) throws IllegalAccessException {
Stack stack = Contentstack.stack("<API_KEY>", "<DELIVERY_TOKEN>", "<ENVIRONMENT>");
try {
stack.taxonomy()
.in("taxonomies.<TAXONOMY_UID>", Arrays.asList("<TERM_UID>"))
.find(new TaxonomyCallback() {
@Override
public void onResponse(JSONObject response, Error error) {
if (error != null) {
// The API rejected the request
System.out.println("Request failed: " + error.getErrorCode() + " " + error.getErrorMessage());
return;
}
JSONArray entries = response.optJSONArray("entries");
System.out.println((entries == null ? 0 : entries.length()) + " entries");
}
});
} catch (RuntimeException e) {
// Thrown by find for a network failure or a body that is not JSON
System.out.println("Request did not complete: " + e.getMessage());
}
}
}Plugin usage: log the status of every successful response
import com.contentstack.sdk.Config;
import com.contentstack.sdk.Contentstack;
import com.contentstack.sdk.ContentstackPlugin;
import com.contentstack.sdk.Error;
import com.contentstack.sdk.QueryResult;
import com.contentstack.sdk.QueryResultsCallBack;
import com.contentstack.sdk.ResponseType;
import com.contentstack.sdk.Stack;
import okhttp3.Request;
import okhttp3.ResponseBody;
import retrofit2.Response;
import java.util.ArrayList;
import java.util.List;
public class LogResponses {
public static void main(String[] args) throws IllegalAccessException {
List<ContentstackPlugin> plugins = new ArrayList<>();
plugins.add(new ContentstackPlugin() {
@Override
public Response<ResponseBody> onResponse(Stack stack, Request request, Response<ResponseBody> response) {
// Read the status only. The SDK reads the body after this method returns.
System.out.println(response.code() + " " + request.url());
return response;
}
});
// Register the plugins before creating the stack
Config config = new Config();
config.setPlugins(plugins);
Stack stack = Contentstack.stack("<API_KEY>", "<DELIVERY_TOKEN>", "<ENVIRONMENT>", config);
stack.contentType("<CONTENT_TYPE_UID>").query().find(new QueryResultsCallBack() {
@Override
public void onCompletion(ResponseType responseType, QueryResult queryResult, Error error) {
if (error != null) { System.out.println(error.getErrorMessage()); return; }
System.out.println(queryResult.getResultObjects().size() + " entries");
}
});
}
}