/stream-chat-java

Stream Chat official Java API Client

Primary LanguageJavaOtherNOASSERTION

Official Java SDK for Stream Chat Maven Central build

The official Java API client for Stream chat a service for building chat applications.

You can sign up for a Stream account at https://getstream.io/chat/get_started/.

You can use this library to access chat API endpoints server-side, for the client-side integrations (web and mobile) have a look at the Javascript, iOS and Android SDK libraries (https://getstream.io/chat/).

Table of Contents

Usage

Requirements

The Stream chat Java SDK requires Java 11+.

It supports latest LTS. If you need support an older Java, please contact at support.

Compatibility

The Stream chat Java SDK is compatible with Groovy, Scala, Kotlin and Clojure.

Installation for Java

With Gradle: Add the library as a dependency in your module level build.gradle file:

See the releases page for the latest version number.

dependencies {
    implementation "io.getstream:stream-chat-java:$stream_version"
}

With Maven: Add the library as a dependency in pom.xml file:

See the releases page for the latest version number.

<dependency>
  <groupId>io.getstream</groupId>
  <artifactId>stream-chat-java</artifactId>
  <version>$stream_version</version>
</dependency>

Installation for Groovy

With Gradle: Add the library as a dependency in your module level build.gradle file:

See the releases page for the latest version number.

dependencies {
    implementation 'io.getstream:stream-chat-java:$stream_version'
}

You can see an example project at GetStream/stream-chat-groovy-example.

Installation for Scala

With Gradle: Add the library as a dependency in your module level build.gradle file:

See the releases page for the latest version number.

dependencies {
    implementation 'io.getstream:stream-chat-java:$stream_version'
}

You can see an example project at GetStream/stream-chat-scala-example.

Installation for Kotlin

With Gradle: Add the library as a dependency in your module level build.gradle.kts file:

See the releases page for the latest version number.

dependencies {
    implementation("io.getstream:stream-chat-java:$stream_version")
}

You can see an example project at GetStream/stream-chat-kotlin-example.

Installation for Clojure

With Leiningen: Add the library as a dependency in your project.clj file:

See the releases page for the latest version number.

:dependencies [[io.getstream/stream-chat-java "$stream_version"]]

You can see an example project at GetStream/stream-chat-clojure-example.

Dependencies

This SDK uses lombok (code generation), retrofit (http client), jackson (json) and jjwt (jwt).

You can find the exact versions in build.gradle.

Configuration

To configure the SDK you need to provide required properties

Property ENV Default Required
io.getstream.chat.apiKey STREAM_KEY - Yes
io.getstream.chat.secretKey STREAM_SECRET - Yes
io.getstream.chat.timeout STREAM_CHAT_TIMEOUT 10000 No
io.getstream.chat.url STREAM_CHAT_URL https://chat.stream-io-api.com No

You can also use your own CDN by creating an implementation of FileHandler and setting it this way

Message.fileHandlerClass = MyFileHandler.class

All setup must be done prior to any request to the API.

JavaDoc

It's automatically built and published at https://getstream.github.io/stream-chat-java/

Simple test

Java
System.out.println(App.get().request());
Groovy
println App.get().request()
Scala
println(App.get.request)
Kotlin
println(App.get().request())
Clojure
println (.request (App/get))

Usage principles

To perform a request on the Stream Chat API, you need to:

Create a StreamRequest

You do so by calling static methods on Stream Model classes.

Set all information you want in the StreamRequest

StreamRequest objects have builder style methods. Some methods require xxxRequestObject instances. All xxxRequestObject classes have builder included, and when there is a corresponding model they have a buildFrom method.

Perform the request

This can be done either synchronously, calling the request() method and handling the StreamException exceptions, or asynchronously, calling the requestAsync(Consumer<Response> onSuccess, Consumer<StreamException> onError)

Simple Example

Synchronous:

try {
    Message message = Message.send("team", "sample_channel")
        .message(MessageRequestObject.builder().text("Sample message").userId("fakeUserId").build())
        .request().getMessage();
} catch (StreamException e) {
    // Handle the exception
}

Asynchronous:

Message.send("team", "sample_channel")
        .message(MessageRequestObject.builder().text("Sample message").userId("fakeUserId").build())
        .requestAsync(
            (sendMessageResponse) -> {
              Message message = sendMessageResponse.getMessage();
            },
            (exception) -> {
              // Handle the exception
            });

Supported features

Channel types

  • Create channel type
  • Delete channel type
  • Get channel type
  • List channel types
  • Update channel type

Channels

  • Delete channel
  • Delete many channels
  • Export channels
  • Export channels status
  • Get or create channel (type,id)
  • Get or create channel (type)
  • Hide channel
  • Mark all read
  • Mark read
  • Mute channel
  • Partially update channel
  • Query channels
  • Query members
  • Search messages
  • Show channel
  • Truncate channel
  • Unmute channel
  • Update channel

Custom commands

  • Create command
  • Delete command
  • Get command
  • List commands
  • Update command

Devices

  • Create device
  • Delete device
  • List devices

Events

  • Send event
  • Send user event

Files

  • Delete file
  • Delete image
  • Upload file
  • Upload image

GDPR

  • Deactivate user
  • Delete user
  • Delete many users
  • Reactivate user

Messages

  • Delete file
  • Delete image
  • Delete message
  • Delete reaction
  • Flag
  • Get many messages
  • Get message
  • Get reactions
  • Get replies
  • Mark all read
  • Mark read
  • Run message command action
  • Search messages
  • Send new message
  • Send reaction
  • Translate message
  • Unflag
  • Update message
  • Upload file
  • Upload image

Moderation

  • Ban user
  • Create block list
  • Deactivate user
  • Delete block list
  • Delete user
  • Flag
  • Get block list
  • List block lists
  • Mute user
  • Query banned users
  • Reactivate user
  • Unban user
  • Unflag
  • Unmute user
  • Update block list

Permissions V2

  • Create custom permission
  • Create custom role
  • Delete custom permission
  • Delete custom role
  • Get custom permission
  • List custom permission
  • List custom roles
  • Update custom permission

Reactions

  • Delete reaction
  • Get reactions
  • Send reaction

Settings

  • Check push
  • Check SQS
  • Create block list
  • Create channel type
  • Delete block list
  • Delete channel type
  • Get App Settings
  • Get block list
  • Get channel type
  • Get rate limits
  • List block lists
  • List channel types
  • Update App Settings
  • Update block list
  • Update channel type

Testing

  • Check push
  • Check SQS

Users

  • Ban user
  • Create guest
  • Deactivate user
  • Delete user
  • Delete many users
  • Export user
  • Flag
  • Mute user
  • Partially update user
  • Query Banned Users
  • Query users
  • Reactivate user
  • Unban user
  • Unflag
  • Unmute user
  • Upsert users

Tasks

  • Get task status

All examples

Upsert users

Single user

User.upsert()
    .user(
        UserRequestObject.builder()
            .id(userId)
            .role("admin")
            .additionalField("book", "dune")
            .build())
    .request();

Batch of users

User.upsert()
    .user(
        UserRequestObject.builder()
            .id(userId1)
            .role("admin")
            .additionalField("book", "dune")
            .build())
    .user(
        UserRequestObject.builder()
            .id(userId2)
            .role("user")
            .additionalField("book", "1984")
            .build())
    .user(
        UserRequestObject.builder()
            .id(userId3)
            .role("admin")
            .additionalField("book", "Fahrenheit 451")
            .build())
    .request();

Set user teams

// creates or updates a user from backend to be part of the "red" and "blue" teams
User.upsert()
    .user(UserRequestObject.builder().id(id).teams(Arrays.asList("red", "blue")).build())
    .request();

Partially update user

Standard

/*
 * make partial update call for userID
 * it set's user.role to "admin", sets  user.field = {'text': 'value'}
 * and user.field2.subfield = 'test'.
 * NOTE:
 * changing role is available only for server-side auth.
 * field name should not contain dots or spaces, as dot is used as path separator.
 */
// response will contain user object with updated users info
User.partialUpdate()
    .user(
        UserPartialUpdateRequestObject.builder()
            .id("userId")
            .setValue("role", "admin")
            .setValue("field", Collections.singletonMap("text", "value"))
            .setValue("field2.subfield", "test")
            .build())
    .request();
// partial update for multiple users
User.partialUpdate()
    .user(
        UserPartialUpdateRequestObject.builder()
            .id("userId")
            .setValue("field", "value")
            .build())
    .user(
        UserPartialUpdateRequestObject.builder()
            .id("userId2")
            .unsetValue("field.value")
            .build())
    .request();

Change a user role

User.partialUpdate()
    .user(
        UserPartialUpdateRequestObject.builder()
            .id("tommaso")
            .setValue("name", "Tommy Doe")
            .setValue("role", "admin")
            .build())

Update App Settings

Standard

// disable auth checks, allows dev token usage
App.update().disableAuthChecks(true).request();
// re-enable auth checks
App.update().disableAuthChecks(false).request();
// Disallow guests from using queryUsers
App.update().userSearchDisallowedRoles(Arrays.asList("guest")).request();

Disable permissions checks

// disable permission checks
App.update().disablePermissionsChecks(true).request();
// re-enable permission checks
App.update().disablePermissionsChecks(false).request();

Enforce unique usernames in app

App.update().enforceUniqueUsernames("app").request();

Enforce unique usernames in team

App.update().enforceUniqueUsernames("team").request();

Enable teams

App.update().multiTenantEnabled(true).request();

Enable image moderation

App.update().imageModerationEnabled(true).request();

Configure webhooks

// update webhook URLs
App.update()
    .webhookURL("https://example.com/webhooks/stream/push") // sets Push webhook address
    .beforeMessageSendHookUrl(
        "https://example.com/webhooks/stream/before-message-send") // sets Before Message Send
                                                                   // webhook address
    .customActionHandlerUrl(
        "https://example.com/webhooks/stream/custom-commands?type={type}") // sets Custom
                                                                           // Commands webhook
                                                                           // address
    .request();

Configure APN

    App.update()
        .aPNConfig(
            APNConfigRequestObject.builder()
                .authKey(Files.readAllBytes(Paths.get("./auth-key.p8")))
                .authType(AuthenticationType.TOKEN)
                .keyId("key_id")
                .bundleId("com.apple.test")
                .teamId("team_id")
                .notificationTemplate(
                    "{\"aps\" :{\"alert\":{\"title\":\"{{ sender.name }}\",\"subtitle\":\"New direct message from {{ sender.name }}\",\"body\":\"{{ message.text }}\"},\"badge\":\"{{ unread_count }}\",\"category\":\"NEW_MESSAGE\"}}")
                .build())
        .request();

Configure APN for development

App.update()
    .aPNConfig(
        APNConfigRequestObject.builder()
            .authKey(Files.readAllBytes(Paths.get("./auth-key.p8")))
            .authType(AuthenticationType.TOKEN)
            .development(true)
            .keyId("key_id")
            .bundleId("com.apple.test")
            .teamId("team_id")
            .notificationTemplate(
                "{\"aps\" :{\"alert\":{\"title\":\"{{ sender.name }}\",\"subtitle\":\"New direct message from {{ sender.name }}\",\"body\":\"{{ message.text }}\"},\"badge\":\"{{ unread_count }}\",\"category\":\"NEW_MESSAGE\"}}")
            .build())
    .request();

Configure Firebase

App.update()
    .firebaseConfig(
        FirebaseConfigRequestObject.builder()
            .serverKey("server_key")
            .notificationTemplate(
                "{\"message\":{\"notification\":{\"title\":\"New messages\",\"body\":\"You have {{ unread_count }} new message(s) from {{ sender.name }}\"},\"android\":{\"ttl\":\"86400s\",\"notification\":{\"click_action\":\"OPEN_ACTIVITY_1\"}}}}")
            .dataTemplate(
                "{\"sender\":\"{{ sender.id }}\",\"channel\":{\"type\": \"{{ channel.type }}\",\"id\":\"{{ channel.id }}\"},\"message\":\"{{ message.id }}\"}")
            .build())
    .request();

Query users

User.list().filterCondition(FilterCondition.in("id", "john", "jack", "jessie")).request();
UserListResponse response =
    User.list()
        .filterCondition(FilterCondition.in("id", "jessica"))
        .filterCondition("last_active", -1)
        .filterCondition("presence", true)
        .request();

Query banned users

List<User> bannedUsers = User.list()
    .filterCondition("banned", true)
    .request()
    .getUsers();

Query users with teams

User.list()
    .filterConditions(
        FilterCondition.and(
            FilterCondition.eq("name", "Jordan")),
            FilterCondition.contains("teams", "red"))));

Get or create channel (type,id)

Standard

Channel.getOrCreate("messaging", "travel")
    .data(
        ChannelRequestObject.builder()
            .additionalField("name", "Awesome channel about traveling")
            .createdBy(UserRequestObject.builder().id("myuserid").build())
            .build())
    .request();

Channel pagination

Channel.getOrCreate(type, id)
    .messages(MessagePaginationParameters.builder().limit(20).idLt(lastMessageId).build())
    .members(PaginationParameters.builder().limit(20).offset(0).build())
    .watchers(PaginationParameters.builder().limit(20).offset(0).build())
    .request();

Create a channel with team

Channel.getOrCreate("messaging", "red-general")
    .data(ChannelRequestObject.builder().team("red").build())
    .request();

Get or create channel (type)

Channel.getOrCreate("messaging")
    .data(
        ChannelRequestObject.builder()
            .member(ChannelMemberRequestObject.builder().userId("thierry").build())
            .member(ChannelMemberRequestObject.builder().userId("tommaso").build())
            .createdBy(UserRequestObject.builder().id("myuserid").build())
            .build())
    .request();

Query channels

Query channels

List<Channel> channels =
    Channel.list().filterCondition("type", "messaging")
        .sort(Sort.builder().field("last_message_at").direction(Direction.DESC).build())
        .request().getChannels().stream()
        .map(channelResponse -> channelResponse.getChannel())
        .collect(Collectors.toList());
for (Channel channel : channels) {
  System.out.println(channel.getAdditionalFields().get("name") + ":" + channel.getCId());
}

Pagination

Channel.list().filterCondition("type", "messaging")
    .sort(Sort.builder().field("last_message_at").direction(Direction.DESC).build())
    .limit(20).offset(10).request();

Query accepted invites

Channel.list().filterCondition("invite", "accepted").userId("u2").request();

Query rejected invites

Channel.list().filterCondition("invite", "rejected").userId("u2").request();

Query muted channels

// retrieve all channels excluding muted ones
Channel.list()
    .filterCondition("muted", false)
    .filterCondition(FilterCondition.in("members", userId))
    .request();

// retrieve all muted channels
Channel.list()
    .filterCondition("muted", true)
    .filterCondition(FilterCondition.in("members", userId))
    .request();

With teams

Channel.list().filterCondition("team", "red-team").request();

Partially update channel

Standard

Map<String, String> channelDetail = new HashMap<>();
channelDetail.put("topic", "Plants and Animals");
channelDetail.put("rating", "pg");
// Here's a channel with some custom field data that might be useful
Channel.getOrCreate(type, id)
    .data(
        ChannelRequestObject.builder()
            .additionalField("source", "user")
            .additionalField("source_detail", Collections.singletonMap("user_id", 123))
            .additionalField("channel_detail", channelDetail)
            .build())
    .request();
// let's change the source of this channel
Channel.partialUpdate(type, id).setValue("source", "system").request();
// since it's system generated we no longer need source_detail
Channel.partialUpdate(type, id).unsetValue("source_detail").request();
// and finally update one of the nested fields in the channel_detail
Channel.partialUpdate(type, id).setValue("channel_detail.topic", "Nature").request();
// and maybe we decide we no longer need a rating
Channel.partialUpdate(type, id).unsetValue("channel_detail.rating").request();

Use a different blocklist

Map<String, Object> configOverrides = new HashMap<>();
configOverrides.put("blocklist", "medical_blocklist");
configOverrides.put("blocklist_behavior", "block");
Channel.partialUpdate(type, id).setValue("config_overrides", configOverrides).request();

Disable replies

Channel.partialUpdate(type, id)
    .setValue("config_overrides", Collections.singletonMap("replies", false))
    .request();

Remove overrides and go back to default settings

Channel.partialUpdate(type, id).setValue("config_overrides", Collections.EMPTY_MAP).request();

Update channel Full update (overwrite)

Channel.update(type, id)
    .data(
        ChannelRequestObject.builder()
            .additionalField("name", "myspecialchannel")
            .additionalField("color", "green")
            .build())
    .message(
        MessageRequestObject.builder()
            .text("Thierry changed the channel color to green")
            .userId("Thierry")
            .build())
    .request();

Add/remove members

Channel.update(type, id).addMember("thierry").addMember("josh").request();
// Or hiding the history of the channel when adding a new member
Channel.update(type, id).addMember("john").hideHistory(true).request();

// Removing a member
Channel.update(type, id).removeMember("tommaso").request();
Channel.update(type, id)
    .addMember("tommaso")
    .message(
        MessageRequestObject.builder()
            .text("Tommaso joined the channel")
            .userId("tommaso")
            .build())
    .request();

Leaving a channel

Channel.update(type, id).removeMember(myUserId).request();

Add/remove moderators

Channel.update(type, id).addModerator("thierry").addModerator("josh").request();
Channel.update(type, id).demoteModerator("tommaso").request();

Inviting users

Channel.update("messaging", "awesome-chat").invite("nick").request();

Accepting an invite

Channel.update("messaging", "awesome-chat")
    .acceptInvite(true)
    .userId("nick")
    .message(MessageRequestObject.builder().text("Nick joined the channel").build())
    .request();

Rejecting an invite

Channel.update("messaging", "awesome-chat")
    .acceptInvite(false)
    .userId("nick")
    .request();

Freeze a channel

ChannelRequestObject channelRequestObject = ChannelRequestObject.buildFrom(channel);
channelRequestObject.setFrozen(true);
Channel.update(channel.getType(), channel.getId())
    .data(channelRequestObject)
    .message(
        MessageRequestObject.builder()
            .text("Thierry has frozen the channel")
            .userId("Thierry")
            .build())
    .request();

Unfreeze a channel

ChannelRequestObject channelRequestObject = ChannelRequestObject.buildFrom(channel);
channelRequestObject.setFrozen(false);
Channel.update(channel.getType(), channel.getId())
    .data(channelRequestObject)
    .message(
        MessageRequestObject.builder()
            .text("Thierry has unfrozen the channel")
            .userId("Thierry")
            .build())
    .request();

Add moderators to a channel

Channel.update("livestream", "fortnite").addModerator("thierry").addModerator("tommaso").request();

Remove moderators from a channel

Channel.update(type, id).demoteModerator("thierry").request();

Enable automatic translation

// enable auto-translation only for this channel
ChannelRequestObject channelRequestObject = ChannelRequestObject.buildFrom(channel);
channelRequestObject.setAutoTranslationEnabled(true);
Channel.update(channel.getType(), channel.getId()).data(channelRequestObject).request();

// ensure all messages are translated in english for this channel
ChannelRequestObject channelRequestObject2 = ChannelRequestObject.buildFrom(channel);
channelRequestObject2.setAutoTranslationEnabled(true);
channelRequestObject2.setAutoTranslationLanguage(Language.EN);
Channel.update(channel.getType(), channel.getId()).data(channelRequestObject2).request();

// auto translate messages for all channels
App.update().autoTranslationEnabled(true).request();

Enable/Disable slow mode

// Enable slow mode and set cooldown to 1s
Channel.update("messaging", "general").cooldown(1).request();

// Increase cooldown to 30s
Channel.update("messaging", "general").cooldown(30).request();

// Disable slow mode
Channel.update("messaging", "general").cooldown(0).request();

Delete channel

Channel.delete(type, id).request();

Delete many channels

var taskId = Channel.deleteMany(List.of("c:1", "c:2"), DeleteStrategy.HARD).request().getTaskId();

Hide channel

// hides the channel until a new message is added there
Channel.hide(type, id).userId(userId).request();

// hides the channel until a new message is added there. This also clears the history for the user
Channel.hide(type, id).clearHistory(true).userId(userId).request();

Show channel

Channel.show(type, id).userId(userId).request();

Truncate channel

Channel.truncate(type, id).request();

Mute channel

// mute channel for a user
Channel.mute().channelCid(cid).userId(userId).request();
// mute a channel for 2 weeks
Channel.mute()
    .channelCid(cid)
    .userId(userId)
    .expiration(TimeUnit.MILLISECONDS.convert(14, TimeUnit.DAYS))
    .request();
// mute a channel for 10 seconds
Channel.mute().channelCid(cid).userId(userId).expiration(10000L).request();
// check if a channel is muted for the user
user.getChannelMutes().stream()
    .anyMatch(channelMute -> channelMute.getChannel().getCId().equals(channel.getCId()));

Unmute channel

Channel.unmute().channelCid(cid).userId(userId).request();

Query members

Pagination and ordering

// returns up to 100 members ordered by created_at ascending
Channel.queryMembers().type(type).id(id).request();
// returns up to 100 members ordered by created_at descending
Channel.queryMembers()
    .type(type)
    .id(id)
    .sort(Sort.builder().field("created_at").direction(Direction.DESC).build())
    .request();
// returns up to 100 members ordered by user_id descending
Channel.queryMembers()
    .type(type)
    .id(id)
    .sort(Sort.builder().field("user_id").direction(Direction.DESC).build())
    .request();
// paginate by user_id in descending order
Channel.queryMembers()
    .type(type)
    .id(id)
    .sort(Sort.builder().field("user_id").direction(Direction.DESC).build())
    .userIdLt(lastMember.getUserId())
    .request();
// paginate by created at in ascending order
Channel.queryMembers()
    .type(type)
    .id(id)
    .sort(Sort.builder().field("created_at").direction(Direction.ASC).build())
    .createdAtAfter(lastMember.getCreatedAt())
    .request();
// paginate using offset
Channel.queryMembers().type(type).id(id).offset(20);

Few examples

// query members by user.name
Channel.queryMembers().filterCondition("name", "tommaso").request();

// autocomplete members by user name
Channel.queryMembers()
    .filterCondition(FilterCondition.autocomplete("name", "tomm"))
    .request();

// query member by id
Channel.queryMembers().filterCondition("user_id", "tommaso").request();

// query multiple members by id
Channel.queryMembers()
    .filterCondition(
        FilterCondition.in("user_id", "tommaso", "thierry"))
    .request();

// query channel moderators
Channel.queryMembers().filterCondition("is_moderator", true).request();

// query for banned members in channel
Channel.queryMembers().filterCondition("banned", true).request();

// query members with pending invites
Channel.queryMembers().filterCondition("invite", "pending").request();

// query members who joined the channel directly or accepted an invite
Channel.queryMembers().filterCondition("joined", true).request();

// query members who have rejected invite or have pending invite
Channel.queryMembers().filterCondition("joined", false).request();

// query all the members
Channel.queryMembers().request();

// order results by member created at descending
Channel.queryMembers()
    .sort(Sort.builder().field("created_at").direction(Direction.DESC).build())
    .request();

// query for user.email (currently the only supported custom field)
Channel.queryMembers().filterCondition("user.email", "awesome@getstream.io").request();

Export channels

String taskId =
    Channel.export()
        .channel(
            ChannelExportRequestObject.builder()
                .type("livestream")
                .id("white_room")
                .messagesSince(
                    new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX")
                        .parse("2020-11-10T09:30:00.000Z"))
                .messagesSince(
                    new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX")
                        .parse("2020-11-10T11:30:00.000Z"))
                .build())
        .request()
        .getTaskId();

Export channels status

ChannelExportStatusResponse response = Channel.exportStatus(taskId).request();
System.out.println(response.getStatus()); // the status for this task
System.out.println(response.getResult()); // the result object, only present if the task is completed
System.out.println(response.getResult().getUrl()); // the link to the JSON export
System.out.println(response.getError()); // if not null the description of the task error

Create channel type

Standard

ChannelType.create()
    .name("public")
    .permission(
        PermissionRequestObject.builder()
            .name("Allow reads for all")
            .priority(999)
            .resources(List.of(ResourceAction.READ_CHANNEL, ResourceAction.CREATE_MESSAGE))
            .action(Action.ALLOW)
            .build())
    .permission(
        PermissionRequestObject.builder()
            .name("Deny all")
            .priority(1)
            .resources(List.of(ResourceAction.ALL))
            .action(Action.DENY)
            .build())
    .mutes(false)
    .reactions(false)
    .request();

With command

ChannelType.create().name("support-channel-type").commands(Arrays.asList("ticket")).request();

List channel types

ChannelType.list().request();

Get channel type

ChannelType.get("public").request();

Update channel type

Update a few elements in channel type

ChannelType.update("public")
    .permission(
        PermissionRequestObject.builder()
            .name("Allow reads for all")
            .priority(999)
            .resources(Arrays.asList(Resource.READ_CHANNEL, Resource.CREATE_MESSAGE))
            .roles(Arrays.asList("*"))
            .action(Action.ALLOW)
            .build())
    .permission(
        PermissionRequestObject.builder()
            .name("Deny all")
            .priority(1)
            .resources(Arrays.asList(Resource.ALL))
            .roles(Arrays.asList("*"))
            .action(Action.DENY)
            .build())
    .replies(false)
    .commands(Arrays.asList("all"))
    .request();

Update channel type features

ChannelType.update("public")
    .typingEvents(false)
    .readEvents(true)
    .connectEvents(true)
    .search(false)
    .reactions(true)
    .replies(false)
    .mutes(true)
    .request();

Update channel type settings

ChannelType.update("public")
    .automod(AutoMod.DISABLED)
    .messageRetention("7")
    .maxMessageLength(140)
    .commands(Arrays.asList("ban", "unban"))
    .request();

Grant the UseFrozenChannel permission

List<PermissionRequestObject> permissions =
    ChannelType.get("messaging").request().getPermissions().stream()
        .map(policy -> PermissionRequestObject.buildFrom(policy))
        .collect(Collectors.toList());
permissions.add(
    PermissionRequestObject.builder()
        .name("Admin users can use frozen channels")
        .priority(600)
        .resources(Arrays.asList(Resource.USE_FROZEN_CHANNEL))
        .roles(Arrays.asList("admin"))
        .owner(false)
        .action(Action.ALLOW)
        .build());
ChannelType.update("messaging").permissions(permissions).request();

Set permissions

ChannelType.update("messaging")
    .permission(
        PermissionRequestObject.builder()
            .name("Admin users can perform any action")
            .priority(600)
            .resources(Arrays.asList(Resource.ALL))
            .roles(Arrays.asList("admin"))
            .owner(false)
            .action(Action.ALLOW)
            .build())
    .permission(
        PermissionRequestObject.builder()
            .name("Anonymous users are not allowed")
            .priority(500)
            .resources(Arrays.asList(Resource.ALL))
            .roles(Arrays.asList("anonymous"))
            .owner(false)
            .action(Action.DENY)
            .build())
    .permission(
        PermissionRequestObject.builder()
            .name("Users can modify their own messages")
            .priority(400)
            .resources(Arrays.asList(Resource.UPDATE_MESSAGE))
            .roles(Arrays.asList("user"))
            .owner(true)
            .action(Action.ALLOW)
            .build())
    .permission(
        PermissionRequestObject.builder()
            .name("Users can create channels")
            .priority(300)
            .resources(Arrays.asList(Resource.CREATE_CHANNEL))
            .roles(Arrays.asList("user"))
            .owner(false)
            .action(Action.ALLOW)
            .build())
    .permission(
        PermissionRequestObject.builder()
            .name("Members of a channel can read and send messages")
            .priority(200)
            .resources(Arrays.asList(Resource.READ_CHANNEL, Resource.CREATE_MESSAGE))
            .roles(Arrays.asList("channel_member"))
            .owner(false)
            .action(Action.ALLOW)
            .build())
    .permission(
        PermissionRequestObject.builder()
            .name("Anything not matching the previous list should not be allowed")
            .priority(100)
            .resources(Arrays.asList(Resource.ALL))
            .roles(Arrays.asList("*"))
            .owner(false)
            .action(Action.DENY)
            .build())
    .request();

Delete channel type

ChannelType.delete("public").request();

Send new message

Simple example

Message.send(type, id)
    .message(
        MessageRequestObject.builder()
            .text(
                "@Josh I told them I was pesca-pescatarian. Which is one who eats solely fish who eat other fish.")
            .userId(userId)
            .build())
    .request();

Complex example

Message.send(type, id)
    .message(
        MessageRequestObject.builder()
            .text(
                "@Josh I told them I was pesca-pescatarian. Which is one who eats solely fish who eat other fish.")
            .attachment(
                AttachmentRequestObject.builder()
                    .type("image")
                    .assetURL("https://bit.ly/2K74TaG")
                    .thumbURL("https://bit.ly/2Uumxti")
                    .additionalField("myCustomField", 123)
                    .build())
            .mentionedUsers(Arrays.asList(josh.getId()))
            .additionalField("anotherCustomField", 234)
            .userId(userId)
            .build())
    .skipPush(true)
    .request();

With url enrichment

Message.send(type, id)
    .message(
        MessageRequestObject.builder()
            .text(
                "Check this bear out https://imgur.com/r/bears/4zmGbMN")
            .userId(userId)
            .build())
    .request();

Create a thread

Message.send(type, id)
    .message(
        MessageRequestObject.builder()
            .text("Hey, I am replying to a message!")
            .parentId(parentId)
            .showInChannel(false)
            .userId(userId)
            .build())
    .request();

Quote a message

// Create the initial message
Message.send(type, id)
    .message(
        MessageRequestObject.builder()
            .id("first_message_id")
            .text("The initial message")
            .userId(userId)
            .build())
    .request();

// Quote the initial message
Message.send(type, id)
    .message(
        MessageRequestObject.builder()
            .text("This is the first message that quotes another message")
            .quotedMessageId("first_message_id")
            .userId(userId)
            .build())
    .request();

Silent message

Message.send(type, id)
    .message(
        MessageRequestObject.builder()
            .text("You completed your trip")
            .userId(systemUserId)
            .silent(true)
            .attachment(
                AttachmentRequestObject.builder()
                    .type("trip")
                    .additionalField("tripData", tripData)
                    .build())
            .build())
    .request();

Get message

Message.get(messageId).request();

Update message

Standard

Message message = Message.get("123").request().getMessage();
MessageRequestObject messageRequestObject = MessageRequestObject.buildFrom(message);
messageRequestObject.setText("the edited version of my text");
Message.update(message.getId()).message(messageRequestObject).request();

Pin and unpin message

// create pinned message
Message message =
    Message.send(channelType, channelId)
        .message(
            MessageRequestObject.builder()
                .text("my message")
                .pinned(true)
                .pinExpires(
                    new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX")
                        .parse("2077-01-01T00:00:00Z"))
                .userId(userId)
                .build())
        .request()
        .getMessage();

// unpin message
MessageRequestObject messageRequestObject = MessageRequestObject.buildFrom(message);
messageRequestObject.setPinned(false);
Message message2 =
    Message.update(message.getId()).message(messageRequestObject).request().getMessage();

// pin message for 120 seconds
MessageRequestObject messageRequestObject2 = MessageRequestObject.buildFrom(message2);
messageRequestObject2.setPinned(true);
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.SECOND, 120);
messageRequestObject2.setPinExpires(calendar.getTime());
Message message3 =
    Message.update(message2.getId()).message(messageRequestObject2).request().getMessage();

// change message expiration to 2077
MessageRequestObject messageRequestObject3 = MessageRequestObject.buildFrom(message3);
messageRequestObject3.setPinExpires(
    new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX").parse("2077-01-01T00:00:00Z"));
Message message4 =
    Message.update(message3.getId()).message(messageRequestObject3).request().getMessage();

// remove expiration date from pinned message
MessageRequestObject messageRequestObject4 = MessageRequestObject.buildFrom(message4);
messageRequestObject4.setPinExpires(null);
Message.update(message4.getId()).message(messageRequestObject4).request();

Partial update message

Message originalMessage =
    Message.send(channelType, channelId)
        .message(
            MessageRequestObject.builder()
                .text("this message is about to be partially updated")
                .additionalField("color", "red")
                .additionalField("details", Collections.singletonMap("status", "pending"))
                .userId(userId)
                .build())
        .request()
        .getMessage();

// partial update message text
Message updated =
    Message.partialUpdate(originalMessage.getId())
        .setValue("text", "the text was partial updated")
        .userId(userId)
        .request()
        .getMessage();

// unset color property
updated =
    Message.partialUpdate(originalMessage.getId())
        .unsetValue("color")
        .userId(userId)
        .request()
        .getMessage();

// set nested property
updated =
    Message.partialUpdate(originalMessage.getId())
        .setValue("details.status", "complete")
        .userId(userId)
        .request()
        .getMessage();

Delete message

Message.delete(messageId).request();

// hard delete the message (works only server-side)
Message.delete(messageId).hard(true).request();

Upload file or image

String pdfFileUrl =
    Message.uploadFile("messaging", "general", userId, "application/pdf")
        .file(new File("./helloworld.pdf"))
        .request()
        .getFile();
String pngFileUrl =
    Message.uploadImage("messaging", "general", userId, "image/png")
        .file(new File("./helloworld.png"))
        .request()
        .getFile();
Message.send("messaging", "general")
    .message(
        MessageRequestObject.builder()
            .text("Check out what I have uploaded in parallel")
            .attachment(
                AttachmentRequestObject.builder()
                    .type("image")
                    .assetURL(pngFileUrl)
                    .thumbURL(pngFileUrl)
                    .build())
            .attachment(
                AttachmentRequestObject.builder().type("url").assetURL(pdfFileUrl).build())
            .userId(userId)
            .build())
    .request();

Send reaction

Standard

// Add reaction 'love' with custom field
Reaction.send(messageId)
    .enforceUnique(false)
    .reaction(
        ReactionRequestObject.builder()
            .type("love")
            .additionalField("myCustomField", 123)
            .userId(userId)
            .build())
    .request();

// Add reaction 'like' and replace all other reactions of this user by it
Reaction.send(messageId)
    .enforceUnique(true)
    .reaction(ReactionRequestObject.builder().type("like").userId(userId).build())
    .request();

Clap reaction

// user claps 5 times on a message
Reaction.send(messageId)
    .enforceUnique(false)
    .reaction(ReactionRequestObject.builder().type("clap").score(5).userId(userId).build())
    .request();
// same user claps 20 times more
Reaction.send(messageId)
    .enforceUnique(false)
    .reaction(ReactionRequestObject.builder().type("clap").score(25).userId(userId).build())
    .request();

Delete reaction

Reaction.delete(messageId, "love").request();

Get reactions

// get the first 10 reactions
Reaction.list(messageId).limit(10).request();

// get 3 reactions past the first 10
Reaction.list(messageId).limit(3).offset(10).request();

Get replies

// retrieve the first 20 messages inside the thread
Message.getReplies(parentMessageId).limit(20).request();

// retrieve the 20 more messages before the message with id "42"
Message.getReplies(parentMessageId).limit(20).idLte("42").request();

Search messages

Search by user and text

Message.search()
    .filterCondition(FilterCondition.in("members", "john"))
    .messageFilterCondition(
        FilterCondition.autocomplete("text", "supercalifragilisticexpialidocious"))
    .limit(2)
    .offset(0)
    .request();

Search messages with attachment

// Search by Attachment
Message.search().messageFilterCondition(FilterCondition.exists("attachments")).request();

Flag message

Message.flag(messageId).userId(userId).request();

Mute user

// mute
User.mute().targetId(targetUserId).userId(userId).request();

// mute for 60 minutes
User.mute().targetId(targetUserId).timeout(60).userId(userId).request();

Ban/unban user

Standard

// ban a user for 60 minutes from all channel
User.ban()
    .targetUserId("eviluser")
    .timeout(60)
    .reason("Banned for one hour")
    .bannedById(userId)
    .request();
// ban a user and their IP address for 24 hours
User.ban()
    .targetUserId("eviluser")
    .timeout(24 * 60)
    .ipBan(true)
    .reason("Please come back tomorrow")
    .bannedById(userId)
    .request();

// ban a user from the livestream:fortnite channel
User.ban()
    .targetUserId("eviluser")
    .id("livestream:fortnite")
    .reason("Profanity is not allowed here")
    .bannedById(userId)
    .request();

// remove ban from channel
User.unban("eviluser").type("livestream").id("fortnite").request();

// remove global ban
User.unban("eviluser").request();

Shadow ban

// shadow ban a user from all channels
User.ban().targetUserId("eviluser").shadow(true).bannedById(userId).request();

// shadow ban a user from a channel
User.ban().targetUserId("eviluser").type(type).id(id).shadow(true).bannedById(userId).request();

// remove shadow ban from channel
User.unban("eviluser").type(type).id(id).request();

// remove global shadow ban
User.unban("eviluser").request();

Query Banned Users

Standard

// retrieve the list of banned users
User.list().filterCondition("banned", true).limit(10).offset(0).request();

// query for banned members from one channel
User.queryBanned().filterCondition("channel_cid", "livestream:123").request();

With pagination

// get the bans for channel livestream:123 in descending order
List<Ban> bans =
    User.queryBanned()
        .filterCondition("channel_cid", "livestream:123")
        .sort(Sort.builder().field("created_at").direction(Direction.DESC).build())
        .request()
        .getBans();

// get the next page of bans for the same channel
List<Ban> nextPageBans =
    User.queryBanned()
        .filterCondition("channel_cid", "livestream:123")
        .createdAtBefore(bans.get(bans.size() - 1).getCreatedAt())
        .sort(Sort.builder().field("created_at").direction(Direction.DESC).build())
        .request()
        .getBans();

Create block list

// add a new block list for this app
Blocklist.create().name("no-cakes").words(Arrays.asList("fudge", "cream", "sugar")).request();

// use the block list for all channels of type messaging
ChannelType.update("messaging")
    .blocklist("no-cakes")
    .blocklistBehavior(BlocklistBehavior.BLOCK)
    .request();

List block lists

Blocklist.list().request();

Get block list

Blocklist.get("no-cakes").request();

Update block list

Blocklist.update("no-cakes").words(Arrays.asList("fudge", "cream", "sugar", "vanilla")).request();

Delete block list

Blocklist.delete("no-cakes").request();

Send event

// sends an event to all connected clients on the channel
Event.send(channelType, channelId)
    .event(
        EventRequestObject.builder()
            .type("friendship_request")
            .additionalField("text", "Hey there, long time no see!")
            .userId(userId)
            .build())
    .request();

Send user event

Event.sendUserCustom(targetUserId)
    .event(
        EventUserCustomRequestObject.builder()
            .type("friendship_request")
            .additionalField("text", "Hey there, long time no see!")
            .build())
    .request();

Create command

Command.create()
    .name("ticket")
    .description("Create a support ticket")
    .args("[description]")
    .setValue("support_commands_set")
    .request();

List commands

Command.list().request();

Get command

Command.get("ticket").request();

Update command

Command.update("ticket").description("Create customer support tickets").request();

Delete command

Command.delete("ticket").request();

Check SQS

// set your SQS queue details
App.update()
    .sqsKey("yourkey")
    .sqsKey("yoursecret")
    .sqsUrl("https://sqs.us-east-1.amazonaws.com/123456789012/MyQueue")
    .request();

// send a test message
App.checkSqs().request();

Create device

Device.create().id("firebase-token").userId(userId).request();

Delete device

Device.delete("firebase-token", userId).request();

List devices

Device.list(targetUserId).request();

Check push

App.checkPush().messageId(messageId).userId(userId).request();

Get rate limits

// 1. Get Rate limits
App.getRateLimits().request();
// 2. Get Rate limits, iOS and Android
App.getRateLimits().ios(true).android(true).request();
// 3. Get Rate limits for specific endpoints
App.getRateLimits().endpoint("QueryChannels").endpoint("SendMessage").request();

Export user

User.export(userId).request();

Deactivate user

User.deactivate(targetUserId).request();

User.deactivate(targetUserId).createdById(userId).markMessagesDeleted(true).request();

Reactivate user

User.reactivate(targetUserId).request();

User.reactivate(targetUserId).restoreMessages(true).name("I am back").createdById(userId).request();

Delete user

Standard

User.delete(targetUserId).markMessagesDeleted(false).request();

Hard delete

User.delete(targetUserId)
    .deleteConversationChannels(true)
    .markMessagesDeleted(true)
    .hardDelete(true)
    .request();

Delete many users

var taskId = User.deleteMany(List.of("u1", "u2"))
        .deleteUserStrategy(DeleteStrategy.SOFT)
        .deleteMessagesStrategy(DeleteStrategy.HARD)
        .request().getTaskId();

var taskStatusResponse = TaskStatus.get(taskId).request();
// "completed".equals(taskStatusResponse.status);

Translate message

Message.send(channelType, channelId)
    .message(
        MessageRequestObject.builder()
            .id(messageId)
            .text("Hello, I would like to have more information about your product.")
            .userId(userId)
            .build())
    .request();
// returns the message.text translated into French
MessageTranslateResponse response =
    Message.translate(messageId).language(Language.FR).request();
System.out.println(response.getMessage().getI18n().get("fr_text"));
// "Bonjour, J'aimerais avoir plus d'informations sur votre produit."

Mark all read

Channel.markAllRead().userId(userId).request();

Mark read

Channel.markRead(channelType, channelId).request();

Delete file

Message.deleteFile(channelType, channelId, url).request();

Delete image

Message.deleteImage(channelType, channelId, url).request();

Flag user

User.flag(targetUserId).userId(userId).request();

Get many messages

Message.getMany(channelType, channelId, Arrays.asList(messageId1, messageId2)).request();

Run message command action

Message.runCommandAction(messageId)
    .formData(Collections.singletonMap("image_action", "send"))
    .userId(userId)
    .request();

Unflag message

Message.unflag(messageId).userId(userId).request();

Unflag user

User.unflag(targetUserId).userId(userId).request();

Create custom permission

Permission.create().id("MyCustomId").name("My custom permission").action("DeleteChannel").request());

Create custom role

Role.create().name("My custom role").request();

Delete custom permission

Permission.delete("MyCustomId").request()

Delete custom role

Role.delete("My custom role").request();

Get custom permission

Permission.get("MyCustomId").request()

List custom permission

Permission.list().request();

List custom roles

Role.list().request();

Update custom permission

Permission.update("MyCustomId", "My custom permission")
          .action("DeleteChannel")
          .owner(true)
          .request());

Get App Settings

App.get().request();

Create guest

User.createGuest()
    .user(UserRequestObject.builder().id(guestId).name("Guest user").build())
    .request();

Unmute user

User.unmute().singleTargetId(targetUserId).userId(userId).request();

Query message flags

Message.queryFlags().request();

Get task status

var taskId = "123";
var taskStatusResponse = TaskStatus.get(taskId).request();
// {LinkedHashMap@4341}  size = 2
// id = "b9843be8-bcf0-484b-af01-726e1d3b82a3"
// status = "completed"
// createdAt = {Date@4339} "Tue Nov 02 17:54:28 CET 2021"
// updatedAt = {Date@4340} "Tue Nov 02 17:54:32 CET 2021"
// rateLimit = {RateLimit@4342} "RateLimit(limit=300, remaining=299, reset=Tue Nov 02 17:55:00 CET 2021)"
// duration = "10.07ms"

Verify webhook

// signature comes from the HTTP header x-signature
boolean valid =  App.verifyWebhook(body, signature)

Changelog

See the detailed changes.

FAQ

  1. If you get this exception: java.lang.ClassNotFoundException: io.jsonwebtoken.SignatureAlgorithm:

See issue #16 for a work around. We only provide runtime only dependency for JWT per recommendation. That's why it might be missing in your runtime and by addding implementation library into your deps, it should be gone.

Contribute

See the guide to contribute.

License

Project is licensed under the Stream License.

We are hiring!

We've recently closed a $38 million Series B funding round and we keep actively growing. Our APIs are used by more than a billion end-users, and you'll have a chance to make a huge impact on the product within a team of the strongest engineers all over the world.

Check out our current openings and apply via Stream's website.