Apollo Kotlin Normalized Cache Help

Partial cache reads

The cache supports partial cache reads, in a similar way to how GraphQL supports partial responses.

This means that if some fields are missing from the cache or represent an error from the server, the cache can return the available data along with the errors.

This is opt-in: by default, executing operations or calling ApolloStore.readOperation() returns responses that expose cache misses and cached server errors with a non-null exception and a null data.

To enable partial cache reads:

Doing this, the returned responses can contain partial data and non-empty errors for any missing (or stale) fields in the cache and cached server errors.

Server errors stored in the cache

Errors from the server are stored in the cache, and can be returned when reading it, as seen above.

By default, errors don't replace existing data in the cache. You can change this behavior with errorsReplaceCachedValues(true).

Strategies

Using the above options and (optionally) a custom fetch policy, you can implement different strategies for your application.

👉 Go to cache first, don't emit partial data, and go to the network if there are cache misses or cached server errors

That is the default behavior.

👉 Go to cache first, emit partial data with cached server errors / don't emit partial data when there are cache misses, and go to the network if there are cache misses

Use serverErrorsAsException(false).

👉 Go to cache first, emit partial data with cache miss errors and cached server errors, and go to the network if there are any errors

Use serverErrorsAsException(false), cacheMissesAsException(false), and a custom fetch policy interceptor like so:

val apolloClient = ApolloClient.Builder() /*...*/ .serverErrorsAsException(false) .cacheMissesAsException(false) .fetchPolicyInterceptor(MyCacheInterceptor) .build()
object MyCacheInterceptor : ApolloInterceptor { override fun <D : Operation.Data> intercept(request: ApolloRequest<D>, chain: ApolloInterceptorChain): Flow<ApolloResponse<D>> { return flow { val cacheResponse = chain.proceed( request = request .newBuilder() // Controls where to read the data from (cache or network) .fetchFromCache(true) .build(), ).single() val fetchFromNetwork = cacheResponse.exception != null || cacheResponse.errors != null && cacheResponse.errors!!.isNotEmpty() emit(cacheResponse.newBuilder().isLast(!fetchFromNetwork).build()) if (fetchFromNetwork) { emitAll(chain.proceed(request = request)) } } } }
Last modified: 05 February 2026