---
source_url: https://www.pubnub.com/docs/sdks/android/api-reference/storage-and-playback
title: Message Persistence API for Android SDK
updated_at: 2026-05-25T11:27:01.203Z
---

> Documentation Index
> For a curated overview of PubNub documentation, see: https://www.pubnub.com/docs/llms.txt
> For the full list of all documentation pages, see: https://www.pubnub.com/docs/llms-full.txt


# Message Persistence API for Android SDK

:::warning Unsupported docs
PubNub no longer maintains Android SDK docs, but our [Java SDK](https://www.pubnub.com/docs/sdks/java) or [Kotlin SDK](https://www.pubnub.com/docs/sdks/kotlin) are fully compatible with the Android platform and you can use them to build mobile apps, ensuring stable software development.
:::

Message Persistence gives you real-time access to the history of messages published to PubNub. Each message is timestamped to the nearest 10 nanoseconds and stored across multiple availability zones in several geographic locations. You can encrypt stored messages with AES-256 so they are not readable on PubNub’s network. For details, see [Message Persistence](https://www.pubnub.com/docs/general/storage).

You control how long messages are stored through your account’s retention policy. Options include: 1 day, 7 days, 30 days, 3 months, 6 months, 1 year, or Unlimited.

You can retrieve the following:

* Messages
* Message reactions
* Files (using the File Sharing API)

## Fetch history

:::warning Requires Message Persistence
Enable Message Persistence for your key in the [Admin Portal](https://admin.pubnub.com/). See how to [enable add-on features](https://support.pubnub.com/hc/en-us/articles/360051974791-How-do-I-enable-add-on-features-for-my-keys-).
:::

Fetch historical messages from one or multiple channels. Use `includeMessageActions` to include message reactions along with the messages.

You can control how messages are returned and in what order:

* If you specify only the `start` parameter (without `end`), you receive messages older than the `start` timetoken.
* If you specify only the `end` parameter (without `start`), you receive messages from that `end` timetoken and newer.
* If you specify both `start` and `end`, you retrieve messages between those timetokens (inclusive of the `end` value).

You can receive up to 100 messages for a single channel, or 25 messages per channel when querying multiple channels (up to 500 channels). If more messages match the time range, make iterative calls and adjust the `start` timetoken to page through the results.

### Method(s)

To run `Fetch History`, you can use the following method(s) in the Android SDK:

```java
this.pubnub.fetchMessages()
    .channels(List<String>)
    .start(Long)
    .end(Long)
    .maximumPerChannel(Integer)
    .includeMeta(Boolean)
    .includeMessageActions(Boolean)
    .includeMessageType(Boolean)
    .includeUUID(Boolean)
```

| Parameter | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| channels | List<String> | Yes |  | Channels to fetch history messages from (up to 500). |
| start | Long | Optional |  | Timetoken delimiting the start of the time slice (exclusive) to pull messages from. |
| end | Long | Optional |  | Timetoken delimiting the end of the time slice (inclusive) to pull messages from. |
| maximumPerChannel | Integer | Optional | `100 or 25` | Number of historical messages to return per channel. Maximum is 100 for a single channel or 25 for multiple channels. |
| includeMeta | Boolean | Optional | `false` | Whether to include message metadata in the response. |
| includeMessageActions | Boolean | Optional | `false` | Retrieve history messages with message reactions. If `true`, limited to one channel only. |
| includeMessageType | Boolean | Optional | `true` | Whether to include message type. If `includeMessageActions` is `true`, then `25` is the default and maximum. |
| includeUUID | Boolean | Optional | `true` | Whether to include the publisher UUID. |
| async | PNCallback | Yes |  | `PNCallback` of type `PNFetchMessagesResult`. |

:::note Truncated response
If you fetch messages with message actions, the response may be truncated when internal limits are reached. If truncated, a `more` property is returned with additional parameters. Make iterative calls to history, adjusting the parameters to fetch more messages.
:::

### Sample code

Retrieve the last message on a channel:

```java
pubnub.fetchMessages()
        .channels(Arrays.asList("my_channel"))
        .maximumPerChannel(25)
        .includeMessageActions(true)
        .includeMeta(true)
        .includeMessageType(true)
        .includeUUID(true)
        .async(new PNCallback<PNFetchMessagesResult>() {
            @Override
            public void onResponse(PNFetchMessagesResult result, PNStatus status) {
                if (!status.isError()) {
                    Map<String, List<PNFetchMessageItem>> channels = result.getChannels();
                    for (PNFetchMessageItem messageItem : channels.get("my_channel")) {
                        System.out.println(messageItem.getMessage());
                        System.out.println(messageItem.getMeta());
                        System.out.println(messageItem.getTimetoken());
                        System.out.println(messageItem.getMessagetType());
                        System.out.println(messageItem.getUuid());
                        HashMap<String, HashMap<String, List<PNFetchMessageItem.Action>>> actions =
                                messageItem.getActions();
                        for (String type : actions.keySet()) {
                            System.out.println("Action type: " + type);
                            for (String value : actions.get(type).keySet()) {
                                System.out.println("Action value: " + value);
                                for (PNFetchMessageItem.Action action : actions.get(type).get(value)) {
                                    System.out.println("Action timetoken: " + action.getActionTimetoken());
                                    System.out.println("Action publisher: " + action.getUuid());
                                }
                            }
                        }
                    }
                } else {
                    status.getErrorData().getThrowable().printStackTrace();
                }
            }
        });
```

### Returns

The `fetchMessages()` operation returns a list of `PNFetchMessagesResult` objects, each containing the following operations:

| Method | Description |
| --- | --- |
| `getMessage()`Type: JsonElement | Message content. |
| `getMeta()`Type: JsonElement | Message metadata if any, and if requested via `includeMeta(true)`. |
| `getTimetoken()`Type: Long | Publish timetoken. |
| `getActionTimetoken()`Type: Long | Timestamp when the message reaction was created. |
| `getActions()`Type: HashMap | Actions data of the message, if any, and if requested via `includeMessageActions(true)`. |
| `getMessageType()`Type: Integer | **Message type** `0` - message, `1` - signal, `2` - object, `3` - message reaction, `4` - files |
| `getUuid()`Type: String | UUID of the publisher |

### Other examples

#### Paging history responses

```java
package com.we.pubnubtest.utils;

import com.pubnub.api.models.consumer.history.PNFetchMessagesResult;

public abstract class CallbackSkeleton {

    public CallbackSkeleton() {
    }

    public abstract void handleResponse(PNFetchMessagesResult result);

    public abstract void finish();

}
```

```java
package com.we.pubnubtest.utils;

import com.pubnub.api.PubNub;
import com.pubnub.api.callbacks.PNCallback;
import com.pubnub.api.models.consumer.PNStatus;
import com.pubnub.api.models.consumer.history.PNFetchMessagesResult;
import com.pubnub.api.models.consumer.pubsub.PNMessageResult;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CountDownLatch;

public class PubnubRecursiveHistoryFetcher {
    private PubNub pubNub;
    private String channels;

    public PubnubRecursiveHistoryFetcher(PubNub pubNub, String channels) {
        this.pubNub = pubNub;
        this.channels = channels;
    }

    public void getAllMessages(CallbackSkeleton callback) {
        getAllMessages(null, callback);
    }

    public void getAllMessages(Long startTimestamp, final CallbackSkeleton callback) {
        final CountDownLatch latch = new CountDownLatch(1);
        final Long timeToken;

        if (startTimestamp == null) {
            startTimestamp = -1L;
        }

        timeToken = startTimestamp;

        pubNub.fetchMessages()
            .channels(Arrays.asList(channels)) // where to fetch history from
            .start(startTimestamp) // first timestamp
            .maximumPerChannel(25)
            .async(new PNCallback<PNFetchMessagesResult>() {
                @Override
                public void onResponse(PNFetchMessagesResult result, PNStatus status) {
                    // handle resposne here
                    if (!status.isError() && result.getChannels().size()!=0) {

                        callback.handleResponse(result);

                        Long timetoken = timeToken;

                        for (List<PNMessageResult> itens: result.getChannels().values()) {

                            for (PNMessageResult item: itens) {
                                if (item.getTimetoken()<timetoken) {
                                    timetoken = item.getTimetoken();
                                }
                            }
                        }

                        getAllMessages(timetoken, callback);
                    } else {
                        callback.finish();
                    }
                }
            });
    }
}
```

#### Fetch messages from multiple channels

```java
pubNub.fetchMessages()
.channels(Arrays.asList("ch1", "ch2", "ch3"))
.async(new PNCallback<PNFetchMessagesResult>() {
    @Override
    public void onResponse(@Nullable final PNFetchMessagesResult result, @NotNull final PNStatus status) {
        if (!status.isError()) {
            final Map<String, List<PNFetchMessageItem>> channelToMessageItemsMap = result.getChannels();
            final Set<String> channels = channelToMessageItemsMap.keySet();
            for (final String channel : channels) {
                List<PNFetchMessageItem> pnFetchMessageItems = channelToMessageItemsMap.get(channel);
                for (final PNFetchMessageItem fetchMessageItem: pnFetchMessageItems) {
                    System.out.println(fetchMessageItem.getMessage());
                    System.out.println(fetchMessageItem.getMeta());
                    System.out.println(fetchMessageItem.getTimetoken());
                }
            }
        }
        else {
            System.err.println("Handling error");
        }
    }
});
```

## Delete messages from history

:::warning Requires Message Persistence
Enable Message Persistence for your key in the [Admin Portal](https://admin.pubnub.com/). See how to [enable add-on features](https://support.pubnub.com/hc/en-us/articles/360051974791-How-do-I-enable-add-on-features-for-my-keys-).
:::

Remove messages from the history of a specific channel.

:::note Required setting
To accept delete-from-history requests, enable the `Delete-From-History` setting for your key in the Admin Portal and initialize the SDK with a secret key.
:::

### Method(s)

To `Delete Messages from History` you can use the following method(s) in the Android SDK.

```java
this.pubnub.deleteMessages()
    .channels(Array)
    .start(Long)
    .end(Long)
```

| Parameter | Description |
| --- | --- |
| `channels` *Type: ArrayDefault: n/a | Channels to delete messages from. |
| `start`Type: LongDefault: n/a | Timetoken delimiting the start of the time slice (inclusive) to delete messages from. |
| `end`Type: LongDefault: n/a | Timetoken delimiting the end of the time slice (exclusive) to delete messages from. |
| `async` *Type: PNCallbackDefault: n/a | `PNCallback` of type `PNDeleteMessagesResult`. |

### Sample code

```java
pubnub.deleteMessages()
    .channels(Arrays.asList("channel_1", "channel_2"))
    .start(1460693607379L)
    .end(1460893617271L)
    .async(new PNCallback<PNDeleteMessagesResult>() {
        @Override
        public void onResponse(PNDeleteMessagesResult result, PNStatus status) {
            // The deleteMessages() method does not return actionable data, be sure to check the status
            // object on the outcome of the operation by checking the status.isError().
        }
    });
```

### Other examples

#### Delete specific message from history

To delete a specific message, pass the `publish timetoken` (received from a successful publish) in the `End` parameter and `timetoken +/- 1` in the `Start` parameter. For example, if `15526611838554310` is the `publish timetoken`, pass `15526611838554309` in `Start` and `15526611838554310` in `End` parameters respectively as shown in the following code snippet.

```java
pubnub.deleteMessages()
    .channels(Arrays.asList("channel_1"))
    .start(15526611838554309L)
    .end(15526611838554310L)
    .async(new PNCallback<PNDeleteMessagesResult>() {
        @Override
        public void onResponse(PNDeleteMessagesResult result, PNStatus status) {
            // The deleteMessages() method does not return actionable data, be sure to check the status
            // object on the outcome of the operation by checking the status.isError().
        }
    });
```

## Message counts

:::warning Requires Message Persistence
Enable Message Persistence for your key in the [Admin Portal](https://admin.pubnub.com/). See how to [enable add-on features](https://support.pubnub.com/hc/en-us/articles/360051974791-How-do-I-enable-add-on-features-for-my-keys-).
:::

Return the number of messages published on one or more channels since a given time. The `count` is the number of messages in history with a timetoken greater than or equal to the value passed in `channelsTimetoken`.

:::note Unlimited message retention
For keys with unlimited message retention enabled, this method considers only messages published in the last 30 days.
:::

### Method(s)

You can use the following method(s) in the Android SDK:

```java
this.pubnub.messageCounts()
    .channels(Array)
    .channelsTimetoken(Array)
```

| Parameter | Description |
| --- | --- |
| `channels` *Type: ArrayDefault: n/a | Channels to fetch the message count for. |
| `channelsTimetoken` *Type: ArrayDefault: n/a | List of `timetokens`, in the same order as the channels list. Specify a single `timetoken` to apply it to all channels. Otherwise, the list of `timetokens` must match the number of channels, or the function returns a `PNStatus` error. |
| `async` *Type: PNCallbackDefault: n/a | PNCallback of type `PNMessageCountResult`. |

### Sample code

```java
Long lastHourTimetoken = (Calendar.getInstance().getTimeInMillis() - TimeUnit.HOURS.toMillis(1)) * 10000L;

pubnub.messageCounts()
    .channels(Arrays.asList("news"))
    .channelsTimetoken(Arrays.asList(lastHourTimetoken))
    .async(new PNCallback<PNMessageCountResult>() {
        @Override
        public void onResponse(PNMessageCountResult result, PNStatus status) {
            if (!status.isError()) {
                for (Map.Entry<String, Long> messageCountEntry : result.getChannels().entrySet()) {
                    messageCountEntry.getKey(); // the channel name
                    messageCountEntry.getValue(); // number of messages for that channel
                }
            } else {
                // Handle error accordingly.
                status.getErrorData().getThrowable().printStackTrace();
            }
        }
    });
```

### Returns

The operation returns a `PNMessageCountResult` which contains the following properties:

| Method | Description |
| --- | --- |
| `getChannels()`Type: Map`<String, Long>` | A map with values of `Long` for each channel. `Channels` without messages have a count of 0. `Channels` with 10,000 messages or more have a count of 10000. |

### Other examples

#### Retrieve count of messages using different timetokens for each channel

```java
Long lastHourTimetoken = (Calendar.getInstance().getTimeInMillis() - TimeUnit.HOURS.toMillis(1)) * 10000L;
Long lastDayTimetoken = (Calendar.getInstance().getTimeInMillis() - TimeUnit.DAYS.toMillis(1)) * 10000L;

pubnub.messageCounts()
    .channels(Arrays.asList("news", "info"))
    .channelsTimetoken(Arrays.asList(lastHourTimetoken, lastDayTimetoken))
    .async(new PNCallback<PNMessageCountResult>() {
        @Override
        public void onResponse(PNMessageCountResult result, PNStatus status) {
            if (!status.isError()) {
                for (Map.Entry<String, Long> messageCountEntry : result.getChannels().entrySet()) {
                    messageCountEntry.getKey(); // the channel name
                    messageCountEntry.getValue(); // number of messages for that channel
                }
            } else {
                // Handle error accordingly.
                status.getErrorData().getThrowable().printStackTrace();
            }
        }
    });
```

## History (deprecated)

:::warning Requires Message Persistence
Enable Message Persistence for your key in the [Admin Portal](https://admin.pubnub.com/). See how to [enable add-on features](https://support.pubnub.com/hc/en-us/articles/360051974791-How-do-I-enable-add-on-features-for-my-keys-).
:::

:::note Alternative method
This method is deprecated. Use [fetch history](#fetch-history) instead.
:::

Fetch historical messages for a channel.

You can control how messages are returned and in what order. For example, you can:

* Search from the newest end of the timeline (default: `reverse` = `false`).
* Search from the oldest end of the timeline by setting `reverse` to `true`.
* Page through results by providing a `start` or `end` timetoken.
* Retrieve a slice of the timeline by providing both a `start` and `end` timetoken.
* Limit the number of messages using the `count` parameter.

:::tip Start & End parameter usage clarity
If you specify only `start` (without `end`), you receive messages older than and up to that `start` timetoken. If you specify only `end` (without `start`), you receive messages that match that `end` timetoken and newer. If you specify both `start` and `end`, you receive messages between those timetokens (inclusive of `end`). You still receive up to 100 messages even if more match. Make iterative calls to history, adjusting `start`, to page through the full result set.
:::

### Method(s)

To run `History` you can use the following method(s) in the Android SDK:

```java
this.pubnub.history()
    .channel(String)
    .reverse(Boolean)
    .includeTimetoken(Boolean)
    .includeMeta(Boolean)
    .start(Long)
    .end(Long)
    .count(Integer);
```

| Parameter | Description |
| --- | --- |
| `channel` *Type: StringDefault: n/a | Channel to return history messages from. |
| `reverse`Type: BooleanDefault: `false` | Whether to traverse the timeline in reverse (oldest to newest) when set to `true`. |
| `includeTimetoken`Type: BooleanDefault: `false` | Whether event dates `timetokens` should be included in the response. |
| `includeMeta`Type: BooleanDefault: `false` | Whether to include message metadata within the response. |
| `start`Type: LongDefault: n/a | Timetoken delimiting the start of time slice (exclusive) to pull messages from. |
| `end`Type: LongDefault: n/a | Timetoken delimiting the end of time slice (inclusive) to pull messages from. |
| `count`Type: IntDefault: `100` | Number of historical messages to return. |
| `async` *Type: PNCallbackDefault: n/a | `PNCallback` of type `PNHistoryResult`. |

:::tip Using the reverse parameter
Messages are always returned sorted in ascending time direction from history regardless of `reverse`. The `reverse` direction matters when you have more than 100 (or `count`, if it's set) messages in the time interval, in which case `reverse` determines the end of the time interval from which it should start retrieving the messages.
:::

### Sample code

Retrieve the last 100 messages on a channel:

```java
pubnub.history()
    .channel("history_channel") // where to fetch history from
    .count(100) // how many items to fetch
    .async(new PNCallback<PNHistoryResult>() {
        @Override
        public void onResponse(PNHistoryResult result, PNStatus status) {

        }
    });
```

### Returns

The `history()` operation returns a `PNHistoryResult` which contains the following properties:

| Method | Description |
| --- | --- |
| `getMessages()`Type: List`<PNHistoryItemResult>` | List of messages. See [PNHistoryItemResult](#pnhistoryitemresult) for more details. |
| `getStartTimetoken()`Type: Long | Start `timetoken`. |
| `getEndTimetoken()`Type: Long | End `timetoken`. |

#### PNHistoryItemResult

| Method | Description |
| --- | --- |
| `getTimetoken()`Type: Long | `Timetoken` of the message. |
| `getEntry()`Type: JsonElement | Message. |

### Other examples

#### Use history() to retrieve the three oldest messages by retrieving from the time line in reverse

```java
pubnub.history()
    .channel("my_channel") // where to fetch history from
    .count(3) // how many items to fetch
    .reverse(true) // should go in reverse?
    .async(new PNCallback<PNHistoryResult>() {
        @Override
        public void onResponse(PNHistoryResult result, PNStatus status) {

        }
    });
```

#### Response

```java
if (!status.isError()) {
    for (PNHistoryItemResult pnHistoryItemResult: result.getMessages()) {
        pnHistoryItemResult.getEntry(); // custom JSON structure for message
    }
}
```

#### Use history() to retrieve messages newer than a given timetoken by paging from oldest message to newest message starting at a single point in time (exclusive)

```java
pubnub.history()
    .channel("my_channel") // where to fetch history from
    .start(13847168620721752L) // first timestamp
    .reverse(true) // should go in reverse?
    .async(new PNCallback<PNHistoryResult>() {
        @Override
        public void onResponse(PNHistoryResult result, PNStatus status) {

        }
    });
```

##### Response

```java
if (!status.isError()) {
    for (PNHistoryItemResult pnHistoryItemResult: result.getMessages()) {
        pnHistoryItemResult.getEntry(); // custom JSON structure for message
    }
}
```

#### Use history() to retrieve messages until a given timetoken by paging from newest message to oldest message until a specific end point in time (inclusive)

```java
pubnub.history()
    .channel("my_channel") // where to fetch history from
    .count(100) // how many items to fetch
    .start(-1) // first timestamp
    .end(13847168819178600L) // last timestamp
    .reverse(true) // should go in reverse?
    .async(new PNCallback<PNHistoryResult>() {
        @Override
        public void onResponse(PNHistoryResult result, PNStatus status) {

        }
    });
```

##### Response

```json
[
    ["Pub3","Pub4","Pub5"],
    13406746780720711,
    13406746845892666
]
```

#### History paging example

:::note Usage
You can call the method by passing 0 or a valid **timetoken** as the argument.
:::

```java
package com.pubnub.api;

import com.pubnub.api.v2.PNConfiguration;
import com.pubnub.api.PubNub;
import com.pubnub.api.callbacks.PNCallback;
import com.pubnub.api.models.consumer.PNStatus;
import com.pubnub.api.models.consumer.history.PNHistoryItemResult;
import com.pubnub.api.models.consumer.history.PNHistoryResult;

public class PubNubRecursiveHistoryFetcher {

    private PubNub pubnub;

    private static abstract class CallbackSkeleton {
        public abstract void handleResponse(PNHistoryResult result);
    }

    private PubNubRecursiveHistoryFetcher() {
        // NOTICE: for demo/demo pub/sub keys Message Persistence is disabled,
        // so use your pub/sub keys instead
        PNConfiguration pnConfiguration = new PNConfiguration();
        pnConfiguration.setSubscribeKey("demo");
        pubnub = new PubNub(pnConfiguration);
    }

    public static void main(String[] args) {
        PubNubRecursiveHistoryFetcher fetcher = new PubNubRecursiveHistoryFetcher();
        fetcher.getAllMessages("my_channel", null, 10, new CallbackSkeleton() {
            @Override
            public void handleResponse(PNHistoryResult result) {
                for (PNHistoryItemResult message : result.getMessages()) {
                    System.out.println(message.getEntry());
                }
            }
        });
    }

    /**
        * Fetches channel history in a recursive manner, in chunks of specified size, starting from the most recent,
        * with every subset (with predefined size) sorted by the timestamp the messages were published.
        *
        * @param channel  The channel where to fetch history from
        * @param start    The timetoken which the fetching starts from
        * @param count    Chunk size
        * @param callback Callback which fires when a chunk is fetched
        */
    private void getAllMessages(final String channel, Long start, final int count, final CallbackSkeleton callback) {
        pubnub.history()
                .channel(channel)
                .count(count)
                .start(start)
                .includeTimetoken(true)
                .async(new PNCallback<PNHistoryResult>() {
                    @Override
                    public void onResponse(PNHistoryResult result, PNStatus status) {
                        if (!status.isError() && !result.getMessages().isEmpty()) {
                            callback.handleResponse(result);
                            getAllMessages(channel, result.getMessages().get(0).getTimetoken(), count, callback);
                        }
                    }
                });
    }
}
```

#### Include timetoken in history response

```java
pubnub.history()
    .channel("history_channel") // where to fetch history from
    .count(100) // how many items to fetch
    .includeTimetoken(true) // include timetoken with each entry
    .async(new PNCallback<PNHistoryResult>() {
        @Override
        public void onResponse(PNHistoryResult result, PNStatus status) {

        }
    });
```

## Terms in this document

* **Message** - A unit of data transmitted between clients or between a client and a server in PubNub, containing information such as text, binary data, or structured data formats like JSON. Messages are sent over channels and can be tracked for delivery and read status.
* **PubNub** - PubNub is a real-time messaging platform that provides APIs and SDKs for building scalable applications. It handles the complex infrastructure of real-time communication, including: Message delivery and persistence, Presence detection, Access control, Push notifications, File sharing, Serverless processing with Functions and Events & Actions, Analytics and monitoring with BizOps Workspace, AI-powered insights with Illuminate.
* **Timetoken** - A unique identifier for each message that represents the number of 100-nanosecond intervals since January 1, 1970, for example, 16200000000000000.
