# Collections

## Overview

Collections provide a way to attach data to activities that is shared between these activities and can be updated in a single place.
A typical use case is to track the status of an activity such as `started`, `completed`, or `failed`. Instead of having to update multiple activities when the status changes you can create a collection and add it to the activities that needs this data. When the status changes you only need to update the collection.

A collection consists of a `name`, an `id` and some data. You can think of the `name` as a namespace for the collection. A **collection reference** is a string that looks like `<name>:<id>` and is the unique identifier for the collection.

<Admonition type="info">

Data stored in collections can not be used in custom ranking or aggregation.

</Admonition>

<Admonition type="info">

The batch endpoints supports up to 100 collections in a single request.
Automatic deduplication will be applied, keeping the last occurrence only.

</Admonition>

## Creating Collections

The example below shows how to create a collection.

```java label="Java"
Map<String, Object> customData = new HashMap<>();
customData.put("title", "Lord of the Rings");
customData.put("genre", "fantasy");
customData.put("rating", 9);

CollectionRequest collection = CollectionRequest.builder()
    .name("movies")
    .id("lord_of_the_rings")
    .custom(customData)
    .build();

CreateCollectionsRequest request = CreateCollectionsRequest.builder()
    .collections(List.of(collection))
    .build();

CreateCollectionsResponse response = feeds.createCollections(request).execute().getData();
```

### Overview of the collection model

<h4 id="CollectionResponse"><a href="#CollectionResponse">CollectionResponse</a></h4><table><thead><tr><th>Name</th><th>Type</th><th>Description</th><th>Constraints</th></tr></thead><tbody><tr><td><code>created_at</code></td><td><code>number</code></td><td>When the collection was created</td><td>-</td></tr><tr><td><code>custom</code></td><td><code>object</code></td><td>Custom data for the collection</td><td>-</td></tr><tr><td><code>id</code></td><td><code>string</code></td><td>Unique identifier for the collection within its name</td><td>Required</td></tr><tr><td><code>name</code></td><td><code>string</code></td><td>Name/type of the collection</td><td>Required</td></tr><tr><td><code>updated_at</code></td><td><code>number</code></td><td>When the collection was last updated</td><td>-</td></tr><tr><td><code>user_id</code></td><td><code>string</code></td><td>ID of the user who owns this collection</td><td>-</td></tr></tbody></table>

## Updating Collections

The example below shows how to update a collection.

```java label="Java"
Map<String, Object> customData = new HashMap<>();
customData.put("title", "Lord of the Rings");
customData.put("genre", "fantasy");
customData.put("rating", 9.5);

CollectionRequest collection = CollectionRequest.builder()
    .name("movies")
    .id("lord_of_the_rings")
    .custom(customData)
    .build();

UpdateCollectionsRequest request = UpdateCollectionsRequest.builder()
    .collections(List.of(collection))
    .build();

UpdateCollectionsResponse response = feeds.updateCollections(request).execute().getData();
```

## Upserting Collections

The upsert endpoint creates a collection if it does not exist, or updates it if it already exists. This is useful when you want to ensure a collection is in a specific state without checking for its existence first.

<Admonition type="info">

The upsert collections endpoint is server-side only.

</Admonition>

```java label="Java"
Map<String, Object> customData = new HashMap<>();
customData.put("title", "Lord of the Rings");
customData.put("genre", "fantasy");
customData.put("rating", 9.5);

CollectionRequest collection = CollectionRequest.builder()
    .name("movies")
    .id("lord_of_the_rings")
    .custom(customData)
    .build();

UpsertCollectionsRequest request = UpsertCollectionsRequest.builder()
    .collections(List.of(collection))
    .build();

UpsertCollectionsResponse response = feeds.upsertCollections(request).execute().getData();
```

## Deleting Collections

The example below shows how to delete a collection.

```java label="Java"
DeleteCollectionsRequest request = DeleteCollectionsRequest.builder()
    .collectionRefs(List.of("movies:lord_of_the_rings"))
    .build();

DeleteCollectionsResponse response = feeds.deleteCollections(request).execute().getData();
```

## Reading Collections

The example below shows how to read a collection.

```java label="Java"
ReadCollectionsRequest request = ReadCollectionsRequest.builder()
    .collectionRefs(List.of("movies:lord_of_the_rings"))
    .build();

ReadCollectionsResponse response = feeds.readCollections(request).execute().getData();
```

## Query Collections

Retrieve collection entries that match a filter, with sorting and cursor-based pagination. This is useful when you don't know the exact IDs of the entries you want (unlike `readCollections`, which fetches by reference). For example, listing every entry in a `movies` collection, or all entries owned by a given user.

`queryCollections` requires the **read collections** permission. When called client-side, results are automatically scoped to the calling user: only the user's own entries and global (no-owner) entries are returned, and any `user_id` filter is ignored.

```java label="Java"
Map<String, Object> filter = new HashMap<>();
filter.put("name", "movies");

QueryCollectionsRequest request = QueryCollectionsRequest.builder()
    .filter(filter)
    .sort(List.of(
        SortParamRequest.builder()
            .field("created_at")
            .direction(1)
            .build()
    ))
    .limit(25)
    .build();

QueryCollectionsResponse response = feeds.queryCollections(request).execute().getData();
```

### Parameters

| Name     | Type   | Description                                                                               |
| -------- | ------ | ----------------------------------------------------------------------------------------- |
| `filter` | object | Query filter. See the filterable fields below. Omit or pass `{}` to match all entries.    |
| `sort`   | array  | Sort clauses, applied in order. Each is `{ field, direction }`. Defaults to newest first. |
| `limit`  | int    | Maximum number of entries to return per page. Defaults to `10`. Maximum is `100`.         |
| `next`   | string | Pagination cursor pointing to the next page (from a previous response's `next`).          |
| `prev`   | string | Pagination cursor pointing to the previous page. Cannot be combined with `next`.          |

### Filterable fields

Filters use Stream's standard [query syntax](https://getstream.io/docs/platform/query-syntax-operators/). A bare value is an equality match (`{ "name": "movies" }`); operators are expressed as nested objects (`{ "created_at": { "$gte": "2024-01-01T00:00:00Z" } }`).

| Field        | Type   | Supported operators                        |
| ------------ | ------ | ------------------------------------------ |
| `id`         | string | `$eq`, `$in`                               |
| `name`       | string | `$eq`, `$in`                               |
| `user_id`    | string | `$eq`, `$in` (ignored on client-side auth) |
| `created_at` | date   | `$eq`, `$gt`, `$gte`, `$lt`, `$lte`        |
| `updated_at` | date   | `$eq`, `$gt`, `$gte`, `$lt`, `$lte`        |

### Sorting

`sort` accepts one or more clauses of the form `{ field, direction }`, where `direction` is `1` for ascending and `-1` for descending. Sortable fields are `created_at`, `updated_at`, and `id`. When `sort` is omitted, entries are returned by `created_at` descending (newest first).

### Pagination

`queryCollections` is cursor-paginated. Each response includes a `next` cursor when more entries are available; pass it back as `next` to fetch the following page. `next` is absent on the last page. Keep the `filter` and `sort` identical across page requests.

```javascript
let next;
const all = [];
do {
  const page = await client.queryCollections({
    filter: { name: "movies" },
    limit: 100,
    next,
  });
  all.push(...page.collections);
  next = page.next;
} while (next);
```

### Response

| Field         | Type   | Description                           |
| ------------- | ------ | ------------------------------------- |
| `collections` | array  | Matching collection entries.          |
| `next`        | string | Cursor for the next page, if any.     |
| `prev`        | string | Cursor for the previous page, if any. |
| `duration`    | string | Server-side processing time.          |

Each entry in `collections` has the following shape:

| Field        | Type   | Description                                    |
| ------------ | ------ | ---------------------------------------------- |
| `name`       | string | Name/type of the collection.                   |
| `id`         | string | Unique identifier within the collection name.  |
| `custom`     | object | Custom data attached to the entry.             |
| `user_id`    | string | Owner of the entry (empty for global entries). |
| `created_at` | date   | When the entry was created.                    |
| `updated_at` | date   | When the entry was last updated.               |

## Activities and Enrichment

Collections are added to activities in the form of **collection references**. An activity can reference up to 10 collections.

<Admonition type="info">

Support for adding collections directly when creating activities is coming soon.

</Admonition>

### Adding Collections to an Activity

```java label="Java"
AddActivityRequest activity =
    AddActivityRequest.builder()
        .type("post")
        .feeds(List.of("user:jack"))
        .text("I love this movie!")
        .userID("jack")
        .collectionRefs(List.of("movies:lord_of_the_rings"))
        .build();

AddActivityResponse response = feeds.addActivity(activity).execute().getData();
```

### Enrichment

When you have added collection references to your activities these will automatically be enriched with the collection data when reading feeds.

```java label="Java"
// Create a collection
Map<String, Object> customData = new HashMap<>();
customData.put("title", "Lord of the Rings");
customData.put("genre", "fantasy");
customData.put("rating", 9);

CollectionRequest collection = CollectionRequest.builder()
    .name("movies")
    .id("lord_of_the_rings")
    .custom(customData)
    .build();

CreateCollectionsRequest createRequest = CreateCollectionsRequest.builder()
    .collections(List.of(collection))
    .build();

feeds.createCollections(createRequest).execute().getData();

// Add the reference to an activity
AddActivityRequest activity =
    AddActivityRequest.builder()
        .type("post")
        .feeds(List.of("user:jack"))
        .text("I love this movie!")
        .userID("jack")
        .collectionRefs(List.of("movies:lord_of_the_rings"))
        .build();

feeds.addActivity(activity).execute().getData();

// Read the feed and see the enriched collection data
Feed feed = new Feed("user", "jack", feeds);
GetOrCreateFeedRequest feedRequest = GetOrCreateFeedRequest.builder()
    .userID("jack")
    .build();
GetOrCreateFeedResponse feedResponse = feed.getOrCreate(feedRequest).getData();
List<Activity> activities = feedResponse.getActivities();
// The activities will contain enriched collection data in the collections field
// Example response structure:
// {
//     "activities": [
//         {
//             "type": "post",
//             "text": "I love this movie!",
//             "collections": {
//                 "movies:lord_of_the_rings": {
//                     "name": "movies",
//                     "id": "lord_of_the_rings",
//                     "custom": {
//                         "title": "Lord of the Rings",
//                         "genre": "fantasy",
//                         "rating": 9
//                     },
//                     "user_id": "jack",
//                     "created_at": "2025-01-01T00:00:00.000Z",
//                     "updated_at": "2025-01-01T00:00:00.000Z",
//                     "status": "ok"
//                 }
//             }
//         }
//     ],
//     ...
// }
```

<Admonition type="info">

The enrichment is a best effort process. Missing or deleted collections will not be enriched but will be retured with a status of `notfound`

</Admonition>


---

This page was last updated at 2026-08-11T17:18:54.263Z.

For the most recent version of this documentation, visit [https://getstream.io/activity-feeds/docs/java/collections/](https://getstream.io/activity-feeds/docs/java/collections/).