diff --git a/README.md b/README.md
index e494336..e66343a 100644
--- a/README.md
+++ b/README.md
@@ -23,6 +23,10 @@ dependencies {
}
```
+## Migrating from stream-chat-java?
+
+If you are currently using [`stream-chat-java`](https://github.com/GetStream/stream-chat-java), we have a detailed migration guide with side-by-side code examples for common Chat use cases. See the [Migration Guide](docs/migration-from-stream-chat-java/README.md).
+
## ✨ Getting started
### Configuration
diff --git a/docs/migration-from-stream-chat-java/01-setup-and-auth.md b/docs/migration-from-stream-chat-java/01-setup-and-auth.md
new file mode 100644
index 0000000..e8d2a01
--- /dev/null
+++ b/docs/migration-from-stream-chat-java/01-setup-and-auth.md
@@ -0,0 +1,176 @@
+# Setup and Authentication
+
+This guide shows how to migrate setup and authentication code from `stream-chat-java` to `stream-sdk-java`.
+
+## Installation
+
+**Before (stream-chat-java):**
+
+Maven:
+```xml
+
+ io.getstream
+ stream-chat-java
+ $streamVersion
+
+```
+
+Gradle:
+```groovy
+implementation 'io.getstream:stream-chat-java:$streamVersion'
+```
+
+**After (stream-sdk-java):**
+
+Maven:
+```xml
+
+ io.getstream
+ stream-sdk-java
+ $streamVersion
+
+```
+
+Gradle:
+```groovy
+implementation 'io.getstream:stream-sdk-java:$streamVersion'
+```
+
+**Key changes:**
+- Artifact ID changes from `stream-chat-java` to `stream-sdk-java`
+
+## Client Initialization
+
+**Before (stream-chat-java):**
+
+```java
+import io.getstream.chat.java.services.framework.DefaultClient;
+import java.util.Properties;
+
+Properties props = new Properties();
+props.put("io.getstream.chat.apiKey", "your-api-key");
+props.put("io.getstream.chat.apiSecret", "your-api-secret");
+
+DefaultClient client = new DefaultClient(props);
+DefaultClient.setInstance(client);
+```
+
+**After (stream-sdk-java):**
+
+```java
+import io.getstream.services.framework.StreamSDKClient;
+
+StreamSDKClient client = new StreamSDKClient("your-api-key", "your-api-secret");
+```
+
+**Key changes:**
+- `DefaultClient` with singleton pattern becomes `StreamSDKClient` with direct instantiation
+- Property names change from `io.getstream.chat.*` to `io.getstream.*`
+- Environment variables change from `STREAM_KEY`/`STREAM_SECRET` to `STREAM_API_KEY`/`STREAM_API_SECRET`
+
+## Client Initialization with Environment Variables
+
+**Before (stream-chat-java):**
+
+```java
+import io.getstream.chat.java.services.framework.DefaultClient;
+
+// Reads STREAM_KEY and STREAM_SECRET from environment
+DefaultClient client = new DefaultClient();
+DefaultClient.setInstance(client);
+```
+
+**After (stream-sdk-java):**
+
+```java
+import io.getstream.services.framework.StreamSDKClient;
+
+// Reads STREAM_API_KEY and STREAM_API_SECRET from environment
+StreamSDKClient client = new StreamSDKClient();
+```
+
+**Key changes:**
+- Environment variable names change: `STREAM_KEY` to `STREAM_API_KEY`, `STREAM_SECRET` to `STREAM_API_SECRET`
+- No singleton pattern required; use the client instance directly
+
+## Token Generation
+
+**Before (stream-chat-java):**
+
+```java
+import io.getstream.chat.java.models.User;
+import java.util.Date;
+import java.util.Calendar;
+
+// Token with no expiry
+String token = User.createToken("user-id", null, null);
+
+// Token with expiry
+Calendar cal = Calendar.getInstance();
+cal.add(Calendar.HOUR, 24);
+String tokenWithExpiry = User.createToken("user-id", cal.getTime(), null);
+```
+
+**After (stream-sdk-java):**
+
+```java
+import io.getstream.services.framework.StreamSDKClient;
+
+StreamSDKClient client = new StreamSDKClient("your-api-key", "your-api-secret");
+
+// Token with no expiry
+String token = client.tokenBuilder().createToken("user-id");
+
+// Token with expiry (validity in seconds)
+String tokenWithExpiry = client.tokenBuilder().createToken("user-id", 24 * 60 * 60);
+```
+
+**Key changes:**
+- Token creation moves from static `User.createToken()` to `client.tokenBuilder().createToken()`
+- Expiry is specified as seconds (integer) instead of a `Date` object
+- No need to pass `issuedAt` separately
+
+## Sub-clients
+
+**Before (stream-chat-java):**
+
+```java
+import io.getstream.chat.java.models.User;
+import io.getstream.chat.java.models.Channel;
+import io.getstream.chat.java.models.Message;
+
+// All operations are static methods on model classes
+User.upsert().user(...).request();
+Channel.getOrCreate("messaging", "general").request();
+Message.send("messaging", "general").message(...).request();
+```
+
+**After (stream-sdk-java):**
+
+```java
+import io.getstream.services.framework.StreamSDKClient;
+
+StreamSDKClient client = new StreamSDKClient("your-api-key", "your-api-secret");
+
+// Operations are on sub-client interfaces
+client.updateUsers(...).execute(); // Common (user operations)
+client.chat().getOrCreateChannel(...).execute(); // Chat
+client.chat().sendMessage(...).execute(); // Chat
+client.moderation().ban(...).execute(); // Moderation
+client.video().getOrCreateCall(...).execute(); // Video
+```
+
+**Key changes:**
+- Static methods on model classes (`User.upsert()`, `Channel.getOrCreate()`) become instance methods on sub-clients
+- User and device operations are on the root client (Common interface)
+- `.request()` becomes `.execute()` to run the API call
+
+**Available sub-clients:**
+
+| Sub-client | Access | Description |
+|------------|--------|-------------|
+| Common | `client.*` (root) | Users, devices, app settings |
+| Chat | `client.chat()` | Channels, messages, reactions |
+| Video | `client.video()` | Calls, call types |
+| Moderation | `client.moderation()` | Ban, mute, flag |
+| Feeds | `client.feeds()` | Activity feeds |
diff --git a/docs/migration-from-stream-chat-java/02-users.md b/docs/migration-from-stream-chat-java/02-users.md
new file mode 100644
index 0000000..dea08ee
--- /dev/null
+++ b/docs/migration-from-stream-chat-java/02-users.md
@@ -0,0 +1,275 @@
+# Users
+
+This guide shows how to migrate user operations from `stream-chat-java` to `stream-sdk-java`.
+
+## Upsert User
+
+**Before (stream-chat-java):**
+
+```java
+import io.getstream.chat.java.models.User;
+import io.getstream.chat.java.models.User.UserRequestObject;
+
+User.upsert()
+ .user(UserRequestObject.builder()
+ .id("john")
+ .name("John Doe")
+ .role("admin")
+ .additionalField("country", "US")
+ .build())
+ .request();
+```
+
+**After (stream-sdk-java):**
+
+```java
+import io.getstream.services.framework.StreamSDKClient;
+import io.getstream.models.UpdateUsersRequest;
+import io.getstream.models.UserRequest;
+import java.util.Map;
+
+StreamSDKClient client = new StreamSDKClient("your-api-key", "your-api-secret");
+
+client.updateUsers(UpdateUsersRequest.builder()
+ .users(Map.of("john", UserRequest.builder()
+ .id("john")
+ .name("John Doe")
+ .role("admin")
+ .custom(Map.of("country", "US"))
+ .build()))
+ .build())
+ .execute();
+```
+
+**Key changes:**
+- `User.upsert().user(...)` becomes `client.updateUsers(UpdateUsersRequest)` with a map of users keyed by ID
+- `UserRequestObject` becomes `UserRequest`
+- `additionalField()` becomes `custom(Map.of(...))`
+- `.request()` becomes `.execute()`
+
+## Batch Upsert Users
+
+**Before (stream-chat-java):**
+
+```java
+import io.getstream.chat.java.models.User;
+import io.getstream.chat.java.models.User.UserRequestObject;
+
+User.upsert()
+ .user(UserRequestObject.builder().id("alice").name("Alice").build())
+ .user(UserRequestObject.builder().id("bob").name("Bob").build())
+ .request();
+```
+
+**After (stream-sdk-java):**
+
+```java
+import io.getstream.services.framework.StreamSDKClient;
+import io.getstream.models.UpdateUsersRequest;
+import io.getstream.models.UserRequest;
+import java.util.Map;
+
+StreamSDKClient client = new StreamSDKClient("your-api-key", "your-api-secret");
+
+client.updateUsers(UpdateUsersRequest.builder()
+ .users(Map.of(
+ "alice", UserRequest.builder().id("alice").name("Alice").build(),
+ "bob", UserRequest.builder().id("bob").name("Bob").build()))
+ .build())
+ .execute();
+```
+
+**Key changes:**
+- Multiple `.user()` calls become a single map passed to `UpdateUsersRequest`
+
+## Query Users
+
+**Before (stream-chat-java):**
+
+```java
+import io.getstream.chat.java.models.User;
+import io.getstream.chat.java.models.User.UserListResponse;
+
+UserListResponse response = User.list()
+ .filterCondition("id", "john")
+ .limit(10)
+ .offset(0)
+ .request();
+```
+
+**After (stream-sdk-java):**
+
+```java
+import io.getstream.services.framework.StreamSDKClient;
+import io.getstream.models.QueryUsersRequest;
+import io.getstream.models.QueryUsersPayload;
+import io.getstream.models.QueryUsersResponse;
+import java.util.Map;
+
+StreamSDKClient client = new StreamSDKClient("your-api-key", "your-api-secret");
+
+var response = client.queryUsers(QueryUsersRequest.builder()
+ .payload(QueryUsersPayload.builder()
+ .filterConditions(Map.of("id", "john"))
+ .limit(10)
+ .offset(0)
+ .build())
+ .build())
+ .execute();
+
+var users = response.getData().getUsers();
+```
+
+**Key changes:**
+- `User.list()` with chained filter methods becomes `client.queryUsers()` with `QueryUsersRequest`
+- Filters are a `Map` instead of chained `.filterCondition()` calls
+- Response data is accessed via `.getData().getUsers()` instead of direct response fields
+
+## Partial Update User
+
+**Before (stream-chat-java):**
+
+```java
+import io.getstream.chat.java.models.User;
+import io.getstream.chat.java.models.User.UserPartialUpdateRequestObject;
+
+User.partialUpdate()
+ .user(UserPartialUpdateRequestObject.builder()
+ .id("john")
+ .setValue("role", "admin")
+ .unsetValue("obsolete_field")
+ .build())
+ .request();
+```
+
+**After (stream-sdk-java):**
+
+```java
+import io.getstream.services.framework.StreamSDKClient;
+import io.getstream.models.UpdateUsersPartialRequest;
+import io.getstream.models.UpdateUserPartialRequest;
+import java.util.List;
+import java.util.Map;
+
+StreamSDKClient client = new StreamSDKClient("your-api-key", "your-api-secret");
+
+client.updateUsersPartial(UpdateUsersPartialRequest.builder()
+ .users(List.of(UpdateUserPartialRequest.builder()
+ .id("john")
+ .set(Map.of("role", "admin"))
+ .unset(List.of("obsolete_field"))
+ .build()))
+ .build())
+ .execute();
+```
+
+**Key changes:**
+- `User.partialUpdate().user(...)` becomes `client.updateUsersPartial()` with a list of `UpdateUserPartialRequest`
+- `.setValue(key, value)` becomes `.set(Map.of(key, value))`
+- `.unsetValue(key)` becomes `.unset(List.of(key))`
+
+## Deactivate User
+
+**Before (stream-chat-java):**
+
+```java
+import io.getstream.chat.java.models.User;
+
+User.deactivate("john")
+ .createdById("admin-user")
+ .markMessagesDeleted(true)
+ .request();
+```
+
+**After (stream-sdk-java):**
+
+```java
+import io.getstream.services.framework.StreamSDKClient;
+import io.getstream.models.DeactivateUserRequest;
+
+StreamSDKClient client = new StreamSDKClient("your-api-key", "your-api-secret");
+
+client.deactivateUser("john", DeactivateUserRequest.builder()
+ .createdByID("admin-user")
+ .markMessagesDeleted(true)
+ .build())
+ .execute();
+```
+
+**Key changes:**
+- `User.deactivate(userId)` becomes `client.deactivateUser(userId, request)`
+- Builder options become fields on `DeactivateUserRequest`
+
+## Reactivate User
+
+**Before (stream-chat-java):**
+
+```java
+import io.getstream.chat.java.models.User;
+
+User.reactivate("john")
+ .createdById("admin-user")
+ .request();
+```
+
+**After (stream-sdk-java):**
+
+```java
+import io.getstream.services.framework.StreamSDKClient;
+import io.getstream.models.ReactivateUserRequest;
+
+StreamSDKClient client = new StreamSDKClient("your-api-key", "your-api-secret");
+
+client.reactivateUser("john", ReactivateUserRequest.builder()
+ .createdByID("admin-user")
+ .build())
+ .execute();
+```
+
+**Key changes:**
+- `User.reactivate(userId)` becomes `client.reactivateUser(userId, request)`
+
+## Delete Users
+
+**Before (stream-chat-java):**
+
+```java
+import io.getstream.chat.java.models.User;
+import io.getstream.chat.java.models.User.DeleteStrategy;
+import java.util.Arrays;
+
+// Single user delete
+User.delete("john")
+ .markMessagesDeleted(true)
+ .hardDelete(false)
+ .request();
+
+// Batch delete
+User.deleteMany(Arrays.asList("alice", "bob"))
+ .setDeleteStrategy(DeleteStrategy.HARD)
+ .request();
+```
+
+**After (stream-sdk-java):**
+
+```java
+import io.getstream.services.framework.StreamSDKClient;
+import io.getstream.models.DeleteUsersRequest;
+import java.util.List;
+
+StreamSDKClient client = new StreamSDKClient("your-api-key", "your-api-secret");
+
+// Single or batch delete (same API)
+client.deleteUsers(DeleteUsersRequest.builder()
+ .userIds(List.of("john"))
+ .user("hard")
+ .messages("hard")
+ .conversations("hard")
+ .build())
+ .execute();
+```
+
+**Key changes:**
+- `User.delete()` and `User.deleteMany()` are unified into `client.deleteUsers()`
+- Delete strategy is specified per resource type: `user`, `messages`, `conversations` (each accepts `"hard"` or `"soft"`)
+- Always uses a list of user IDs, even for a single user
diff --git a/docs/migration-from-stream-chat-java/03-channels.md b/docs/migration-from-stream-chat-java/03-channels.md
new file mode 100644
index 0000000..132d2ae
--- /dev/null
+++ b/docs/migration-from-stream-chat-java/03-channels.md
@@ -0,0 +1,418 @@
+# Channels
+
+This guide shows how to migrate channel operations from `stream-chat-java` to `stream-sdk-java`.
+
+## Create or Get a Channel
+
+**Before (stream-chat-java):**
+
+```java
+import io.getstream.chat.java.models.Channel;
+import io.getstream.chat.java.models.Channel.ChannelRequestObject;
+import io.getstream.chat.java.models.User.UserRequestObject;
+
+Channel.getOrCreate("messaging", "general")
+ .data(ChannelRequestObject.builder()
+ .createdBy(UserRequestObject.builder().id("admin").build())
+ .additionalField("description", "General chat")
+ .build())
+ .request();
+```
+
+**After (stream-sdk-java):**
+
+```java
+import io.getstream.services.framework.StreamSDKClient;
+import io.getstream.models.GetOrCreateChannelRequest;
+import io.getstream.models.ChannelInput;
+import java.util.Map;
+
+StreamSDKClient client = new StreamSDKClient("your-api-key", "your-api-secret");
+
+client.chat().getOrCreateChannel("messaging", "general",
+ GetOrCreateChannelRequest.builder()
+ .data(ChannelInput.builder()
+ .createdByID("admin")
+ .custom(Map.of("description", "General chat"))
+ .build())
+ .build())
+ .execute();
+```
+
+**Key changes:**
+- `Channel.getOrCreate(type, id)` becomes `client.chat().getOrCreateChannel(type, id, request)`
+- `ChannelRequestObject` becomes `ChannelInput`
+- `.createdBy(UserRequestObject)` becomes `.createdByID("admin")` (just the user ID)
+- `.additionalField()` becomes `.custom(Map.of(...))`
+
+## Create a Channel with Members
+
+**Before (stream-chat-java):**
+
+```java
+import io.getstream.chat.java.models.Channel;
+import io.getstream.chat.java.models.Channel.ChannelRequestObject;
+import io.getstream.chat.java.models.Channel.ChannelMemberRequestObject;
+import io.getstream.chat.java.models.User.UserRequestObject;
+import java.util.Arrays;
+
+Channel.getOrCreate("messaging", "team-chat")
+ .data(ChannelRequestObject.builder()
+ .createdBy(UserRequestObject.builder().id("admin").build())
+ .members(Arrays.asList(
+ ChannelMemberRequestObject.builder().userId("alice").build(),
+ ChannelMemberRequestObject.builder().userId("bob").build()))
+ .build())
+ .request();
+```
+
+**After (stream-sdk-java):**
+
+```java
+import io.getstream.services.framework.StreamSDKClient;
+import io.getstream.models.GetOrCreateChannelRequest;
+import io.getstream.models.ChannelInput;
+import io.getstream.models.ChannelMemberRequest;
+import java.util.List;
+
+StreamSDKClient client = new StreamSDKClient("your-api-key", "your-api-secret");
+
+client.chat().getOrCreateChannel("messaging", "team-chat",
+ GetOrCreateChannelRequest.builder()
+ .data(ChannelInput.builder()
+ .createdByID("admin")
+ .members(List.of(
+ ChannelMemberRequest.builder().userID("alice").build(),
+ ChannelMemberRequest.builder().userID("bob").build()))
+ .build())
+ .build())
+ .execute();
+```
+
+**Key changes:**
+- `ChannelMemberRequestObject` becomes `ChannelMemberRequest`
+- `.userId("alice")` becomes `.userID("alice")`
+
+## Create a Distinct (1:1) Channel
+
+**Before (stream-chat-java):**
+
+```java
+import io.getstream.chat.java.models.Channel;
+import io.getstream.chat.java.models.Channel.ChannelRequestObject;
+import io.getstream.chat.java.models.Channel.ChannelMemberRequestObject;
+import java.util.Arrays;
+
+Channel.getOrCreate("messaging")
+ .data(ChannelRequestObject.builder()
+ .members(Arrays.asList(
+ ChannelMemberRequestObject.builder().userId("alice").build(),
+ ChannelMemberRequestObject.builder().userId("bob").build()))
+ .build())
+ .request();
+```
+
+**After (stream-sdk-java):**
+
+```java
+import io.getstream.services.framework.StreamSDKClient;
+import io.getstream.models.GetOrCreateDistinctChannelRequest;
+import io.getstream.models.ChannelInput;
+import io.getstream.models.ChannelMemberRequest;
+import java.util.List;
+
+StreamSDKClient client = new StreamSDKClient("your-api-key", "your-api-secret");
+
+client.chat().getOrCreateDistinctChannel("messaging",
+ GetOrCreateDistinctChannelRequest.builder()
+ .data(ChannelInput.builder()
+ .members(List.of(
+ ChannelMemberRequest.builder().userID("alice").build(),
+ ChannelMemberRequest.builder().userID("bob").build()))
+ .build())
+ .build())
+ .execute();
+```
+
+**Key changes:**
+- `Channel.getOrCreate(type)` without an ID becomes `client.chat().getOrCreateDistinctChannel(type, request)`
+- Separate method name makes the intent explicit
+
+## Query Channels
+
+**Before (stream-chat-java):**
+
+```java
+import io.getstream.chat.java.models.Channel;
+import io.getstream.chat.java.models.Channel.ChannelListResponse;
+import io.getstream.chat.java.models.User.UserRequestObject;
+import io.getstream.chat.java.models.Sort;
+
+ChannelListResponse response = Channel.list()
+ .user(UserRequestObject.builder().id("admin").build())
+ .filterCondition("type", "messaging")
+ .sort(Sort.builder().field("last_message_at").direction(Sort.Direction.DESC).build())
+ .limit(10)
+ .request();
+```
+
+**After (stream-sdk-java):**
+
+```java
+import io.getstream.services.framework.StreamSDKClient;
+import io.getstream.models.QueryChannelsRequest;
+import io.getstream.models.SortParamRequest;
+import java.util.List;
+import java.util.Map;
+
+StreamSDKClient client = new StreamSDKClient("your-api-key", "your-api-secret");
+
+var response = client.chat().queryChannels(QueryChannelsRequest.builder()
+ .userID("admin")
+ .filterConditions(Map.of("type", "messaging"))
+ .sort(List.of(SortParamRequest.builder()
+ .field("last_message_at")
+ .direction(-1)
+ .build()))
+ .limit(10)
+ .build())
+ .execute();
+
+var channels = response.getData().getChannels();
+```
+
+**Key changes:**
+- `Channel.list()` becomes `client.chat().queryChannels()`
+- `.user(UserRequestObject)` becomes `.userID("admin")`
+- Filters and sort are typed objects instead of chained builder calls
+- Sort direction uses `-1` (descending) / `1` (ascending) instead of `Sort.Direction` enum
+
+## Update a Channel
+
+**Before (stream-chat-java):**
+
+```java
+import io.getstream.chat.java.models.Channel;
+import io.getstream.chat.java.models.User.UserRequestObject;
+import java.util.Arrays;
+
+Channel.update("messaging", "general")
+ .user(UserRequestObject.builder().id("admin").build())
+ .addModerators(Arrays.asList("alice"))
+ .removeModerators(Arrays.asList("bob"))
+ .cooldown(30)
+ .request();
+```
+
+**After (stream-sdk-java):**
+
+```java
+import io.getstream.services.framework.StreamSDKClient;
+import io.getstream.models.UpdateChannelRequest;
+import java.util.List;
+
+StreamSDKClient client = new StreamSDKClient("your-api-key", "your-api-secret");
+
+client.chat().updateChannel("messaging", "general",
+ UpdateChannelRequest.builder()
+ .addModerators(List.of("alice"))
+ .demoteModerators(List.of("bob"))
+ .cooldown(30)
+ .build())
+ .execute();
+```
+
+**Key changes:**
+- `Channel.update(type, id)` becomes `client.chat().updateChannel(type, id, request)`
+- `.removeModerators()` becomes `.demoteModerators()`
+- No user object required on the request
+
+## Add and Remove Members
+
+**Before (stream-chat-java):**
+
+```java
+import io.getstream.chat.java.models.Channel;
+import java.util.Arrays;
+
+Channel.update("messaging", "general")
+ .addUserIds(Arrays.asList("charlie"))
+ .removeUserIds(Arrays.asList("dave"))
+ .request();
+```
+
+**After (stream-sdk-java):**
+
+```java
+import io.getstream.services.framework.StreamSDKClient;
+import io.getstream.models.UpdateChannelRequest;
+import io.getstream.models.ChannelMemberRequest;
+import java.util.List;
+
+StreamSDKClient client = new StreamSDKClient("your-api-key", "your-api-secret");
+
+client.chat().updateChannel("messaging", "general",
+ UpdateChannelRequest.builder()
+ .addMembers(List.of(
+ ChannelMemberRequest.builder().userID("charlie").build()))
+ .removeMembers(List.of("dave"))
+ .build())
+ .execute();
+```
+
+**Key changes:**
+- `.addUserIds()` becomes `.addMembers()` with `ChannelMemberRequest` objects
+- `.removeUserIds()` becomes `.removeMembers()` with a list of user ID strings
+
+## Partial Update a Channel
+
+**Before (stream-chat-java):**
+
+```java
+import io.getstream.chat.java.models.Channel;
+
+Channel.partialUpdate("messaging", "general")
+ .setValue("topic", "New Topic")
+ .unsetValue("old_field")
+ .request();
+```
+
+**After (stream-sdk-java):**
+
+```java
+import io.getstream.services.framework.StreamSDKClient;
+import io.getstream.models.UpdateChannelPartialRequest;
+import java.util.List;
+import java.util.Map;
+
+StreamSDKClient client = new StreamSDKClient("your-api-key", "your-api-secret");
+
+client.chat().updateChannelPartial("messaging", "general",
+ UpdateChannelPartialRequest.builder()
+ .set(Map.of("topic", "New Topic"))
+ .unset(List.of("old_field"))
+ .build())
+ .execute();
+```
+
+**Key changes:**
+- `Channel.partialUpdate()` becomes `client.chat().updateChannelPartial()`
+- `.setValue(key, val)` becomes `.set(Map.of(key, val))`
+- `.unsetValue(key)` becomes `.unset(List.of(key))`
+
+## Query Members
+
+**Before (stream-chat-java):**
+
+```java
+import io.getstream.chat.java.models.Channel;
+import io.getstream.chat.java.models.Sort;
+
+Channel.queryMembers()
+ .type("messaging")
+ .id("general")
+ .filterCondition("notifications_muted", true)
+ .sort(Sort.builder().field("created_at").build())
+ .request();
+```
+
+**After (stream-sdk-java):**
+
+```java
+import io.getstream.services.framework.StreamSDKClient;
+import io.getstream.models.QueryMembersRequest;
+import io.getstream.models.QueryMembersPayload;
+import io.getstream.models.SortParamRequest;
+import java.util.List;
+import java.util.Map;
+
+StreamSDKClient client = new StreamSDKClient("your-api-key", "your-api-secret");
+
+client.chat().queryMembers(QueryMembersRequest.builder()
+ .payload(QueryMembersPayload.builder()
+ .type("messaging")
+ .id("general")
+ .filterConditions(Map.of("notifications_muted", true))
+ .sort(List.of(SortParamRequest.builder().field("created_at").build()))
+ .build())
+ .build())
+ .execute();
+```
+
+**Key changes:**
+- `Channel.queryMembers()` becomes `client.chat().queryMembers()`
+- Filter conditions are a `Map` instead of chained calls
+
+## Delete a Channel
+
+**Before (stream-chat-java):**
+
+```java
+import io.getstream.chat.java.models.Channel;
+
+Channel.delete("messaging", "general").request();
+```
+
+**After (stream-sdk-java):**
+
+```java
+import io.getstream.services.framework.StreamSDKClient;
+import io.getstream.models.DeleteChannelRequest;
+
+StreamSDKClient client = new StreamSDKClient("your-api-key", "your-api-secret");
+
+// Soft delete
+client.chat().deleteChannel("messaging", "general").execute();
+
+// Hard delete
+client.chat().deleteChannel("messaging", "general",
+ DeleteChannelRequest.builder()
+ .hardDelete(true)
+ .build())
+ .execute();
+```
+
+**Key changes:**
+- `Channel.delete(type, id)` becomes `client.chat().deleteChannel(type, id)`
+- Hard delete is specified via `DeleteChannelRequest` with `.hardDelete(true)`
+
+## Truncate a Channel
+
+**Before (stream-chat-java):**
+
+```java
+import io.getstream.chat.java.models.Channel;
+import io.getstream.chat.java.models.Message.MessageRequestObject;
+
+Channel.truncate("messaging", "general")
+ .skipPush(true)
+ .message(MessageRequestObject.builder()
+ .text("Channel truncated")
+ .userId("admin")
+ .build())
+ .request();
+```
+
+**After (stream-sdk-java):**
+
+```java
+import io.getstream.services.framework.StreamSDKClient;
+import io.getstream.models.TruncateChannelRequest;
+import io.getstream.models.MessageRequest;
+
+StreamSDKClient client = new StreamSDKClient("your-api-key", "your-api-secret");
+
+client.chat().truncateChannel("messaging", "general",
+ TruncateChannelRequest.builder()
+ .skipPush(true)
+ .message(MessageRequest.builder()
+ .text("Channel truncated")
+ .userID("admin")
+ .build())
+ .build())
+ .execute();
+```
+
+**Key changes:**
+- `Channel.truncate()` becomes `client.chat().truncateChannel()`
+- `MessageRequestObject` becomes `MessageRequest`
diff --git a/docs/migration-from-stream-chat-java/04-messages-and-reactions.md b/docs/migration-from-stream-chat-java/04-messages-and-reactions.md
new file mode 100644
index 0000000..e080b6c
--- /dev/null
+++ b/docs/migration-from-stream-chat-java/04-messages-and-reactions.md
@@ -0,0 +1,422 @@
+# Messages and Reactions
+
+This guide shows how to migrate message and reaction operations from `stream-chat-java` to `stream-sdk-java`.
+
+## Send a Message
+
+**Before (stream-chat-java):**
+
+```java
+import io.getstream.chat.java.models.Message;
+import io.getstream.chat.java.models.Message.MessageRequestObject;
+
+Message.send("messaging", "general")
+ .message(MessageRequestObject.builder()
+ .text("Hello, world!")
+ .userId("john")
+ .build())
+ .request();
+```
+
+**After (stream-sdk-java):**
+
+```java
+import io.getstream.services.framework.StreamSDKClient;
+import io.getstream.models.SendMessageRequest;
+import io.getstream.models.MessageRequest;
+
+StreamSDKClient client = new StreamSDKClient("your-api-key", "your-api-secret");
+
+client.chat().sendMessage("messaging", "general",
+ SendMessageRequest.builder()
+ .message(MessageRequest.builder()
+ .text("Hello, world!")
+ .userID("john")
+ .build())
+ .build())
+ .execute();
+```
+
+**Key changes:**
+- `Message.send(type, id)` becomes `client.chat().sendMessage(type, id, request)`
+- `MessageRequestObject` becomes `MessageRequest`
+- `.userId()` becomes `.userID()`
+
+## Send a Message with Options
+
+**Before (stream-chat-java):**
+
+```java
+import io.getstream.chat.java.models.Message;
+import io.getstream.chat.java.models.Message.MessageRequestObject;
+
+Message.send("messaging", "general")
+ .message(MessageRequestObject.builder()
+ .text("Silent notification")
+ .userId("john")
+ .build())
+ .skipPush(true)
+ .skipEnrichUrl(true)
+ .request();
+```
+
+**After (stream-sdk-java):**
+
+```java
+import io.getstream.services.framework.StreamSDKClient;
+import io.getstream.models.SendMessageRequest;
+import io.getstream.models.MessageRequest;
+
+StreamSDKClient client = new StreamSDKClient("your-api-key", "your-api-secret");
+
+client.chat().sendMessage("messaging", "general",
+ SendMessageRequest.builder()
+ .message(MessageRequest.builder()
+ .text("Silent notification")
+ .userID("john")
+ .build())
+ .skipPush(true)
+ .skipEnrichUrl(true)
+ .build())
+ .execute();
+```
+
+**Key changes:**
+- Options like `skipPush` and `skipEnrichUrl` go on `SendMessageRequest` (the outer wrapper), not chained on the builder
+
+## Send a Thread Reply
+
+**Before (stream-chat-java):**
+
+```java
+import io.getstream.chat.java.models.Message;
+import io.getstream.chat.java.models.Message.MessageRequestObject;
+
+Message.send("messaging", "general")
+ .message(MessageRequestObject.builder()
+ .text("This is a reply")
+ .userId("john")
+ .parentId("parent-message-id")
+ .showInChannel(true)
+ .build())
+ .request();
+```
+
+**After (stream-sdk-java):**
+
+```java
+import io.getstream.services.framework.StreamSDKClient;
+import io.getstream.models.SendMessageRequest;
+import io.getstream.models.MessageRequest;
+
+StreamSDKClient client = new StreamSDKClient("your-api-key", "your-api-secret");
+
+client.chat().sendMessage("messaging", "general",
+ SendMessageRequest.builder()
+ .message(MessageRequest.builder()
+ .text("This is a reply")
+ .userID("john")
+ .parentID("parent-message-id")
+ .showInChannel(true)
+ .build())
+ .build())
+ .execute();
+```
+
+**Key changes:**
+- `.parentId()` becomes `.parentID()`
+
+## Get a Message
+
+**Before (stream-chat-java):**
+
+```java
+import io.getstream.chat.java.models.Message;
+
+var response = Message.get("message-id")
+ .showDeletedMessages(true)
+ .request();
+```
+
+**After (stream-sdk-java):**
+
+```java
+import io.getstream.services.framework.StreamSDKClient;
+import io.getstream.models.GetMessageRequest;
+
+StreamSDKClient client = new StreamSDKClient("your-api-key", "your-api-secret");
+
+var response = client.chat().getMessage("message-id",
+ GetMessageRequest.builder()
+ .ShowDeletedMessage(true)
+ .build())
+ .execute();
+
+var message = response.getData().getMessage();
+```
+
+**Key changes:**
+- `Message.get(id)` becomes `client.chat().getMessage(id, request)`
+- Response accessed via `.getData().getMessage()`
+
+## Update a Message
+
+**Before (stream-chat-java):**
+
+```java
+import io.getstream.chat.java.models.Message;
+import io.getstream.chat.java.models.Message.MessageRequestObject;
+
+Message.update("message-id")
+ .message(MessageRequestObject.builder()
+ .text("Updated text")
+ .userId("john")
+ .build())
+ .request();
+```
+
+**After (stream-sdk-java):**
+
+```java
+import io.getstream.services.framework.StreamSDKClient;
+import io.getstream.models.UpdateMessageRequest;
+import io.getstream.models.MessageRequest;
+
+StreamSDKClient client = new StreamSDKClient("your-api-key", "your-api-secret");
+
+client.chat().updateMessage("message-id",
+ UpdateMessageRequest.builder()
+ .message(MessageRequest.builder()
+ .text("Updated text")
+ .userID("john")
+ .build())
+ .build())
+ .execute();
+```
+
+**Key changes:**
+- `Message.update(id)` becomes `client.chat().updateMessage(id, request)`
+
+## Partial Update a Message
+
+**Before (stream-chat-java):**
+
+```java
+import io.getstream.chat.java.models.Message;
+
+Message.partialUpdate("message-id")
+ .setValue("custom_field", "new value")
+ .unsetValue("old_field")
+ .request();
+```
+
+**After (stream-sdk-java):**
+
+```java
+import io.getstream.services.framework.StreamSDKClient;
+import io.getstream.models.UpdateMessagePartialRequest;
+import java.util.List;
+import java.util.Map;
+
+StreamSDKClient client = new StreamSDKClient("your-api-key", "your-api-secret");
+
+client.chat().updateMessagePartial("message-id",
+ UpdateMessagePartialRequest.builder()
+ .set(Map.of("custom_field", "new value"))
+ .unset(List.of("old_field"))
+ .build())
+ .execute();
+```
+
+**Key changes:**
+- `Message.partialUpdate(id)` becomes `client.chat().updateMessagePartial(id, request)`
+- `.setValue()`/`.unsetValue()` becomes `.set(Map)`/`.unset(List)`
+
+## Delete a Message
+
+**Before (stream-chat-java):**
+
+```java
+import io.getstream.chat.java.models.Message;
+
+// Soft delete
+Message.delete("message-id").request();
+
+// Hard delete
+Message.hardDelete("message-id").request();
+
+// Delete by specific user
+Message.delete("message-id")
+ .deletedBy("admin")
+ .request();
+```
+
+**After (stream-sdk-java):**
+
+```java
+import io.getstream.services.framework.StreamSDKClient;
+import io.getstream.models.DeleteMessageRequest;
+
+StreamSDKClient client = new StreamSDKClient("your-api-key", "your-api-secret");
+
+// Soft delete
+client.chat().deleteMessage("message-id").execute();
+
+// Hard delete
+client.chat().deleteMessage("message-id",
+ DeleteMessageRequest.builder()
+ .hard(true)
+ .build())
+ .execute();
+
+// Delete by specific user
+client.chat().deleteMessage("message-id",
+ DeleteMessageRequest.builder()
+ .deletedBy("admin")
+ .build())
+ .execute();
+```
+
+**Key changes:**
+- `Message.delete()`/`Message.hardDelete()` are unified into `client.chat().deleteMessage()` with optional `DeleteMessageRequest`
+- Hard delete is a boolean flag instead of a separate method
+
+## Get Replies
+
+**Before (stream-chat-java):**
+
+```java
+import io.getstream.chat.java.models.Message;
+
+var response = Message.getReplies("parent-message-id")
+ .limit(25)
+ .request();
+```
+
+**After (stream-sdk-java):**
+
+```java
+import io.getstream.services.framework.StreamSDKClient;
+import io.getstream.models.GetRepliesRequest;
+
+StreamSDKClient client = new StreamSDKClient("your-api-key", "your-api-secret");
+
+var response = client.chat().getReplies("parent-message-id",
+ GetRepliesRequest.builder()
+ .limit(25)
+ .build())
+ .execute();
+
+var replies = response.getData().getMessages();
+```
+
+**Key changes:**
+- `Message.getReplies(parentId)` becomes `client.chat().getReplies(parentId, request)`
+
+## Send a Reaction
+
+**Before (stream-chat-java):**
+
+```java
+import io.getstream.chat.java.models.Reaction;
+import io.getstream.chat.java.models.Reaction.ReactionRequestObject;
+import io.getstream.chat.java.models.User.UserRequestObject;
+
+Reaction.send("message-id")
+ .reaction(ReactionRequestObject.builder()
+ .type("like")
+ .user(UserRequestObject.builder().id("john").build())
+ .build())
+ .enforceUnique(true)
+ .request();
+```
+
+**After (stream-sdk-java):**
+
+```java
+import io.getstream.services.framework.StreamSDKClient;
+import io.getstream.models.SendReactionRequest;
+import io.getstream.models.ReactionRequest;
+
+StreamSDKClient client = new StreamSDKClient("your-api-key", "your-api-secret");
+
+client.chat().sendReaction("message-id",
+ SendReactionRequest.builder()
+ .reaction(ReactionRequest.builder()
+ .type("like")
+ .userID("john")
+ .build())
+ .enforceUnique(true)
+ .build())
+ .execute();
+```
+
+**Key changes:**
+- `Reaction.send(messageId)` becomes `client.chat().sendReaction(messageId, request)`
+- `.user(UserRequestObject)` becomes `.userID("john")` on `ReactionRequest`
+- `ReactionRequestObject` becomes `ReactionRequest`
+
+## List Reactions
+
+**Before (stream-chat-java):**
+
+```java
+import io.getstream.chat.java.models.Reaction;
+
+var response = Reaction.list("message-id")
+ .limit(25)
+ .offset(0)
+ .request();
+```
+
+**After (stream-sdk-java):**
+
+```java
+import io.getstream.services.framework.StreamSDKClient;
+import io.getstream.models.GetReactionsRequest;
+
+StreamSDKClient client = new StreamSDKClient("your-api-key", "your-api-secret");
+
+var response = client.chat().getReactions("message-id",
+ GetReactionsRequest.builder()
+ .limit(25)
+ .offset(0)
+ .build())
+ .execute();
+
+var reactions = response.getData().getReactions();
+```
+
+**Key changes:**
+- `Reaction.list(messageId)` becomes `client.chat().getReactions(messageId, request)`
+
+## Delete a Reaction
+
+**Before (stream-chat-java):**
+
+```java
+import io.getstream.chat.java.models.Reaction;
+
+Reaction.delete("message-id", "like")
+ .userId("john")
+ .request();
+```
+
+**After (stream-sdk-java):**
+
+```java
+import io.getstream.services.framework.StreamSDKClient;
+import io.getstream.models.DeleteReactionRequest;
+
+StreamSDKClient client = new StreamSDKClient("your-api-key", "your-api-secret");
+
+client.chat().deleteReaction("message-id", "like",
+ DeleteReactionRequest.builder()
+ .userID("john")
+ .build())
+ .execute();
+```
+
+**Key changes:**
+- `Reaction.delete(messageId, type)` becomes `client.chat().deleteReaction(messageId, type, request)`
+- `.userId()` moves into `DeleteReactionRequest` as `.userID()`
diff --git a/docs/migration-from-stream-chat-java/05-moderation.md b/docs/migration-from-stream-chat-java/05-moderation.md
new file mode 100644
index 0000000..ad72c96
--- /dev/null
+++ b/docs/migration-from-stream-chat-java/05-moderation.md
@@ -0,0 +1,337 @@
+# Moderation
+
+This guide shows how to migrate moderation operations from `stream-chat-java` to `stream-sdk-java`.
+
+## Add Moderators
+
+**Before (stream-chat-java):**
+
+```java
+import io.getstream.chat.java.models.Channel;
+import java.util.Arrays;
+
+Channel.update("messaging", "general")
+ .addModerators(Arrays.asList("alice", "bob"))
+ .request();
+```
+
+**After (stream-sdk-java):**
+
+```java
+import io.getstream.services.framework.StreamSDKClient;
+import io.getstream.models.UpdateChannelRequest;
+import java.util.List;
+
+StreamSDKClient client = new StreamSDKClient("your-api-key", "your-api-secret");
+
+client.chat().updateChannel("messaging", "general",
+ UpdateChannelRequest.builder()
+ .addModerators(List.of("alice", "bob"))
+ .build())
+ .execute();
+```
+
+**Key changes:**
+- `Channel.update()` becomes `client.chat().updateChannel()`
+- Same field name `.addModerators()` on `UpdateChannelRequest`
+
+## Remove Moderators
+
+**Before (stream-chat-java):**
+
+```java
+import io.getstream.chat.java.models.Channel;
+import java.util.Arrays;
+
+Channel.update("messaging", "general")
+ .removeModerators(Arrays.asList("alice"))
+ .request();
+```
+
+**After (stream-sdk-java):**
+
+```java
+import io.getstream.services.framework.StreamSDKClient;
+import io.getstream.models.UpdateChannelRequest;
+import java.util.List;
+
+StreamSDKClient client = new StreamSDKClient("your-api-key", "your-api-secret");
+
+client.chat().updateChannel("messaging", "general",
+ UpdateChannelRequest.builder()
+ .demoteModerators(List.of("alice"))
+ .build())
+ .execute();
+```
+
+**Key changes:**
+- `.removeModerators()` becomes `.demoteModerators()`
+
+## Ban a User (App-level)
+
+**Before (stream-chat-java):**
+
+```java
+import io.getstream.chat.java.models.User;
+
+User.ban()
+ .userId("admin")
+ .targetUserId("spammer")
+ .reason("Spamming")
+ .timeout(3600)
+ .request();
+```
+
+**After (stream-sdk-java):**
+
+```java
+import io.getstream.services.framework.StreamSDKClient;
+import io.getstream.models.BanRequest;
+
+StreamSDKClient client = new StreamSDKClient("your-api-key", "your-api-secret");
+
+client.moderation().ban(BanRequest.builder()
+ .targetUserID("spammer")
+ .bannedByID("admin")
+ .reason("Spamming")
+ .timeout(3600)
+ .build())
+ .execute();
+```
+
+**Key changes:**
+- `User.ban()` becomes `client.moderation().ban()`
+- `.userId()` (the banner) becomes `.bannedByID()`
+- `.targetUserId()` becomes `.targetUserID()`
+
+## Ban a User (Channel-level)
+
+**Before (stream-chat-java):**
+
+```java
+import io.getstream.chat.java.models.User;
+
+User.ban()
+ .userId("admin")
+ .targetUserId("spammer")
+ .type("messaging")
+ .id("general")
+ .reason("Off-topic")
+ .request();
+```
+
+**After (stream-sdk-java):**
+
+```java
+import io.getstream.services.framework.StreamSDKClient;
+import io.getstream.models.BanRequest;
+
+StreamSDKClient client = new StreamSDKClient("your-api-key", "your-api-secret");
+
+client.moderation().ban(BanRequest.builder()
+ .targetUserID("spammer")
+ .bannedByID("admin")
+ .channelCid("messaging:general")
+ .reason("Off-topic")
+ .build())
+ .execute();
+```
+
+**Key changes:**
+- `.type("messaging").id("general")` becomes `.channelCid("messaging:general")` (combined CID format)
+
+## Shadow Ban
+
+**Before (stream-chat-java):**
+
+```java
+import io.getstream.chat.java.models.User;
+
+User.shadowBan()
+ .userId("admin")
+ .targetUserId("troll")
+ .request();
+```
+
+**After (stream-sdk-java):**
+
+```java
+import io.getstream.services.framework.StreamSDKClient;
+import io.getstream.models.BanRequest;
+
+StreamSDKClient client = new StreamSDKClient("your-api-key", "your-api-secret");
+
+client.moderation().ban(BanRequest.builder()
+ .targetUserID("troll")
+ .bannedByID("admin")
+ .shadow(true)
+ .build())
+ .execute();
+```
+
+**Key changes:**
+- `User.shadowBan()` is no longer a separate method; use `client.moderation().ban()` with `.shadow(true)`
+
+## Unban a User
+
+**Before (stream-chat-java):**
+
+```java
+import io.getstream.chat.java.models.User;
+
+User.unban("spammer")
+ .createdBy("admin")
+ .request();
+
+// Channel-level unban
+User.unban("spammer")
+ .type("messaging")
+ .id("general")
+ .createdBy("admin")
+ .request();
+```
+
+**After (stream-sdk-java):**
+
+```java
+import io.getstream.services.framework.StreamSDKClient;
+import io.getstream.models.UnbanRequest;
+
+StreamSDKClient client = new StreamSDKClient("your-api-key", "your-api-secret");
+
+// App-level unban
+client.moderation().unban(UnbanRequest.builder()
+ .targetUserID("spammer")
+ .build())
+ .execute();
+
+// Channel-level unban
+client.moderation().unban(UnbanRequest.builder()
+ .targetUserID("spammer")
+ .channelCid("messaging:general")
+ .build())
+ .execute();
+```
+
+**Key changes:**
+- `User.unban(targetId)` becomes `client.moderation().unban(UnbanRequest)`
+- `.type().id()` becomes `.channelCid("type:id")`
+
+## Query Banned Users
+
+**Before (stream-chat-java):**
+
+```java
+import io.getstream.chat.java.models.User;
+
+var response = User.queryBanned()
+ .filterCondition("username", "john")
+ .limit(25)
+ .offset(0)
+ .request();
+```
+
+**After (stream-sdk-java):**
+
+```java
+import io.getstream.services.framework.StreamSDKClient;
+import io.getstream.models.QueryBannedUsersRequest;
+import io.getstream.models.QueryBannedUsersPayload;
+import java.util.Map;
+
+StreamSDKClient client = new StreamSDKClient("your-api-key", "your-api-secret");
+
+var response = client.moderation().queryBannedUsers(QueryBannedUsersRequest.builder()
+ .payload(QueryBannedUsersPayload.builder()
+ .filterConditions(Map.of("username", "john"))
+ .limit(25)
+ .offset(0)
+ .build())
+ .build())
+ .execute();
+```
+
+**Key changes:**
+- `User.queryBanned()` becomes `client.moderation().queryBannedUsers()`
+
+## Mute a User
+
+**Before (stream-chat-java):**
+
+```java
+import io.getstream.chat.java.models.User;
+
+User.mute()
+ .userId("john")
+ .targetUserId("noisy-user")
+ .timeout(60)
+ .request();
+```
+
+**After (stream-sdk-java):**
+
+```java
+import io.getstream.services.framework.StreamSDKClient;
+import io.getstream.models.MuteRequest;
+import java.util.List;
+
+StreamSDKClient client = new StreamSDKClient("your-api-key", "your-api-secret");
+
+client.moderation().mute(MuteRequest.builder()
+ .userID("john")
+ .targetIds(List.of("noisy-user"))
+ .timeout(60)
+ .build())
+ .execute();
+```
+
+**Key changes:**
+- `User.mute()` becomes `client.moderation().mute()`
+- `.targetUserId()` becomes `.targetIds(List.of(...))`, allowing batch muting
+
+## Unmute a User
+
+**Before (stream-chat-java):**
+
+```java
+import io.getstream.chat.java.models.User;
+
+User.unmute()
+ .userId("john")
+ .targetUserId("noisy-user")
+ .request();
+```
+
+**After (stream-sdk-java):**
+
+```java
+import io.getstream.services.framework.StreamSDKClient;
+import io.getstream.models.UnmuteRequest;
+import java.util.List;
+
+StreamSDKClient client = new StreamSDKClient("your-api-key", "your-api-secret");
+
+client.moderation().unmute(UnmuteRequest.builder()
+ .userID("john")
+ .targetIds(List.of("noisy-user"))
+ .build())
+ .execute();
+```
+
+**Key changes:**
+- `User.unmute()` becomes `client.moderation().unmute()`
+- `.targetUserId()` becomes `.targetIds(List.of(...))`
+
+## Method Mapping Summary
+
+| Operation | stream-chat-java | stream-sdk-java |
+|-----------|-----------------|-----------------|
+| Add moderators | `Channel.update().addModerators()` | `client.chat().updateChannel()` with `.addModerators()` |
+| Remove moderators | `Channel.update().removeModerators()` | `client.chat().updateChannel()` with `.demoteModerators()` |
+| Ban (app-level) | `User.ban()` | `client.moderation().ban()` |
+| Ban (channel-level) | `User.ban().type().id()` | `client.moderation().ban()` with `.channelCid()` |
+| Shadow ban | `User.shadowBan()` | `client.moderation().ban()` with `.shadow(true)` |
+| Unban | `User.unban()` | `client.moderation().unban()` |
+| Mute | `User.mute()` | `client.moderation().mute()` |
+| Unmute | `User.unmute()` | `client.moderation().unmute()` |
+| Query banned | `User.queryBanned()` | `client.moderation().queryBannedUsers()` |
diff --git a/docs/migration-from-stream-chat-java/06-devices.md b/docs/migration-from-stream-chat-java/06-devices.md
new file mode 100644
index 0000000..904a45f
--- /dev/null
+++ b/docs/migration-from-stream-chat-java/06-devices.md
@@ -0,0 +1,179 @@
+# Devices
+
+This guide shows how to migrate device management operations from `stream-chat-java` to `stream-sdk-java`.
+
+## Add a Device (APN)
+
+**Before (stream-chat-java):**
+
+```java
+import io.getstream.chat.java.models.Device;
+import io.getstream.chat.java.models.Device.PushProviderType;
+import io.getstream.chat.java.models.User.UserRequestObject;
+
+Device.create()
+ .id("device-token-123")
+ .user(UserRequestObject.builder().id("john").build())
+ .pushProvider(PushProviderType.Apn)
+ .request();
+```
+
+**After (stream-sdk-java):**
+
+```java
+import io.getstream.services.framework.StreamSDKClient;
+import io.getstream.models.CreateDeviceRequest;
+
+StreamSDKClient client = new StreamSDKClient("your-api-key", "your-api-secret");
+
+client.createDevice(CreateDeviceRequest.builder()
+ .id("device-token-123")
+ .userID("john")
+ .pushProvider("apn")
+ .build())
+ .execute();
+```
+
+**Key changes:**
+- `Device.create()` becomes `client.createDevice()`
+- `.user(UserRequestObject)` becomes `.userID("john")`
+- `PushProviderType.Apn` enum becomes the string `"apn"`
+
+## Add a Device (Firebase)
+
+**Before (stream-chat-java):**
+
+```java
+import io.getstream.chat.java.models.Device;
+import io.getstream.chat.java.models.Device.PushProviderType;
+import io.getstream.chat.java.models.User.UserRequestObject;
+
+Device.create()
+ .id("fcm-token-456")
+ .user(UserRequestObject.builder().id("john").build())
+ .pushProvider(PushProviderType.Firebase)
+ .request();
+```
+
+**After (stream-sdk-java):**
+
+```java
+import io.getstream.services.framework.StreamSDKClient;
+import io.getstream.models.CreateDeviceRequest;
+
+StreamSDKClient client = new StreamSDKClient("your-api-key", "your-api-secret");
+
+client.createDevice(CreateDeviceRequest.builder()
+ .id("fcm-token-456")
+ .userID("john")
+ .pushProvider("firebase")
+ .build())
+ .execute();
+```
+
+**Key changes:**
+- `PushProviderType.Firebase` becomes the string `"firebase"`
+
+## Add a VoIP Device
+
+**Before (stream-chat-java):**
+
+```java
+import io.getstream.chat.java.models.Device;
+import io.getstream.chat.java.models.Device.PushProviderType;
+import io.getstream.chat.java.models.User.UserRequestObject;
+
+// No dedicated VoIP support; same as regular APN device
+Device.create()
+ .id("voip-token-789")
+ .user(UserRequestObject.builder().id("john").build())
+ .pushProvider(PushProviderType.Apn)
+ .request();
+```
+
+**After (stream-sdk-java):**
+
+```java
+import io.getstream.services.framework.StreamSDKClient;
+import io.getstream.models.CreateDeviceRequest;
+
+StreamSDKClient client = new StreamSDKClient("your-api-key", "your-api-secret");
+
+client.createDevice(CreateDeviceRequest.builder()
+ .id("voip-token-789")
+ .userID("john")
+ .pushProvider("apn")
+ .voipToken(true)
+ .build())
+ .execute();
+```
+
+**Key changes:**
+- New SDK adds `.voipToken(true)` for Apple VoIP push tokens
+
+## List Devices
+
+**Before (stream-chat-java):**
+
+```java
+import io.getstream.chat.java.models.Device;
+
+var response = Device.list("john").request();
+```
+
+**After (stream-sdk-java):**
+
+```java
+import io.getstream.services.framework.StreamSDKClient;
+import io.getstream.models.ListDevicesRequest;
+
+StreamSDKClient client = new StreamSDKClient("your-api-key", "your-api-secret");
+
+var response = client.listDevices(ListDevicesRequest.builder()
+ .userID("john")
+ .build())
+ .execute();
+
+var devices = response.getData().getDevices();
+```
+
+**Key changes:**
+- `Device.list(userId)` becomes `client.listDevices(ListDevicesRequest)`
+- User ID moves from a positional argument to a field on the request object
+
+## Delete a Device
+
+**Before (stream-chat-java):**
+
+```java
+import io.getstream.chat.java.models.Device;
+
+Device.delete("device-token-123", "john").request();
+```
+
+**After (stream-sdk-java):**
+
+```java
+import io.getstream.services.framework.StreamSDKClient;
+import io.getstream.models.DeleteDeviceRequest;
+
+StreamSDKClient client = new StreamSDKClient("your-api-key", "your-api-secret");
+
+client.deleteDevice(DeleteDeviceRequest.builder()
+ .id("device-token-123")
+ .userID("john")
+ .build())
+ .execute();
+```
+
+**Key changes:**
+- `Device.delete(deviceId, userId)` becomes `client.deleteDevice(DeleteDeviceRequest)`
+- Positional arguments become named fields on the request object
+
+## Method Mapping Summary
+
+| Operation | stream-chat-java | stream-sdk-java |
+|-----------|-----------------|-----------------|
+| Add device | `Device.create()` | `client.createDevice()` |
+| List devices | `Device.list(userId)` | `client.listDevices()` |
+| Delete device | `Device.delete(deviceId, userId)` | `client.deleteDevice()` |
diff --git a/docs/migration-from-stream-chat-java/README.md b/docs/migration-from-stream-chat-java/README.md
new file mode 100644
index 0000000..ba5141b
--- /dev/null
+++ b/docs/migration-from-stream-chat-java/README.md
@@ -0,0 +1,82 @@
+# Migrating from stream-chat-java to stream-sdk-java
+
+## Why Migrate?
+
+- `stream-sdk-java` is the actively developed, long-term-supported SDK
+- Covers Chat, Video, Moderation, and Feeds in a single package
+- Strongly typed models generated from the official OpenAPI spec
+- `stream-chat-java` will enter maintenance mode (critical fixes only)
+
+## Key Differences
+
+| Aspect | stream-chat-java | stream-sdk-java |
+|--------|-----------------|-----------------|
+| Package | `io.getstream:stream-chat-java` | `io.getstream:stream-sdk-java` |
+| Client class | `DefaultClient` (singleton) | `StreamSDKClient` (instance) |
+| API pattern | Static methods on model classes (`User.upsert()`, `Channel.getOrCreate()`) | Instance methods on sub-clients (`client.chat()`, `client.moderation()`) |
+| Models | Builder classes nested in model files (`UserRequestObject`, `ChannelRequestObject`) | Standalone generated request classes (`UserRequest`, `ChannelInput`) |
+| Custom fields | `.additionalField(key, value)` | `.custom(Map.of(key, value))` |
+| Execution | `.request()` | `.execute()` |
+| Tokens | `User.createToken(userId, expiry, issuedAt)` | `client.tokenBuilder().createToken(userId, validitySeconds)` |
+| Env vars | `STREAM_KEY`, `STREAM_SECRET` | `STREAM_API_KEY`, `STREAM_API_SECRET` |
+
+## Quick Example
+
+**Before (stream-chat-java):**
+
+```java
+import io.getstream.chat.java.services.framework.DefaultClient;
+import io.getstream.chat.java.models.Message;
+import io.getstream.chat.java.models.Message.MessageRequestObject;
+import java.util.Properties;
+
+Properties props = new Properties();
+props.put("io.getstream.chat.apiKey", "your-api-key");
+props.put("io.getstream.chat.apiSecret", "your-api-secret");
+DefaultClient client = new DefaultClient(props);
+DefaultClient.setInstance(client);
+
+Message.send("messaging", "general")
+ .message(MessageRequestObject.builder()
+ .text("Hello!")
+ .userId("john")
+ .build())
+ .request();
+```
+
+**After (stream-sdk-java):**
+
+```java
+import io.getstream.services.framework.StreamSDKClient;
+import io.getstream.models.SendMessageRequest;
+import io.getstream.models.MessageRequest;
+
+StreamSDKClient client = new StreamSDKClient("your-api-key", "your-api-secret");
+
+client.chat().sendMessage("messaging", "general",
+ SendMessageRequest.builder()
+ .message(MessageRequest.builder()
+ .text("Hello!")
+ .userID("john")
+ .build())
+ .build())
+ .execute();
+```
+
+## Migration Guides by Topic
+
+| # | Topic | File |
+|---|-------|------|
+| 1 | [Setup and Authentication](01-setup-and-auth.md) | Client init, tokens |
+| 2 | [Users](02-users.md) | Upsert, query, update, delete |
+| 3 | [Channels](03-channels.md) | Create, query, members, update |
+| 4 | [Messages and Reactions](04-messages-and-reactions.md) | Send, reply, react |
+| 5 | [Moderation](05-moderation.md) | Ban, mute, moderators |
+| 6 | [Devices](06-devices.md) | Push device management |
+
+## Notes
+
+- `stream-chat-java` is not going away. Your existing integration will keep working.
+- The new SDK uses Lombok builders for all request model creation.
+- All API calls return `StreamRequest` objects; call `.execute()` to run them.
+- If you find a use case missing from this guide, please open an issue.
diff --git a/src/main/java/io/getstream/services/ModerationClient.java b/src/main/java/io/getstream/services/ModerationClient.java
new file mode 100644
index 0000000..2ff8ce1
--- /dev/null
+++ b/src/main/java/io/getstream/services/ModerationClient.java
@@ -0,0 +1,16 @@
+package io.getstream.services;
+
+import io.getstream.services.framework.StreamSDKClient;
+
+public class ModerationClient extends ModerationImpl implements Moderation {
+ StreamSDKClient client;
+
+ public ModerationClient(StreamSDKClient client) {
+ super(client.getHttpClient());
+ this.client = client;
+ }
+
+ public StreamSDKClient getSDKClient() {
+ return client;
+ }
+}
diff --git a/src/main/java/io/getstream/services/framework/StreamSDKClient.java b/src/main/java/io/getstream/services/framework/StreamSDKClient.java
index 75f36ff..4134538 100644
--- a/src/main/java/io/getstream/services/framework/StreamSDKClient.java
+++ b/src/main/java/io/getstream/services/framework/StreamSDKClient.java
@@ -40,6 +40,10 @@ public Feeds feeds() {
return new FeedsClient(this);
}
+ public Moderation moderation() {
+ return new ModerationClient(this);
+ }
+
public TokenBuilder tokenBuilder() {
var tb = new TokenBuilder(httpClient.getApiSecret());
return tb;
diff --git a/src/test/java/io/getstream/ModerationIntegrationTest.java b/src/test/java/io/getstream/ModerationIntegrationTest.java
index dfd354b..2af2bd7 100644
--- a/src/test/java/io/getstream/ModerationIntegrationTest.java
+++ b/src/test/java/io/getstream/ModerationIntegrationTest.java
@@ -3,7 +3,7 @@
import static org.junit.jupiter.api.Assertions.*;
import io.getstream.models.*;
-import io.getstream.services.ModerationImpl;
+import io.getstream.services.Moderation;
import java.util.*;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.MethodOrderer;
@@ -49,7 +49,7 @@ void testFlagMessageAndUser() throws Exception {
// Send a message to flag
String msgId = sendTestMessage("messaging", channelId, userId, "Message to be flagged");
- ModerationImpl moderation = new ModerationImpl(client.getHttpClient());
+ Moderation moderation = client.moderation();
// Flag the message
var flagMsgResp =
@@ -90,7 +90,7 @@ void testMuteUnmuteUser() throws Exception {
String muterId = userIds.get(0);
String targetId = userIds.get(1);
- ModerationImpl moderation = new ModerationImpl(client.getHttpClient());
+ Moderation moderation = client.moderation();
// Mute target user as muter (no timeout)
var muteResp =
@@ -146,7 +146,7 @@ void testBanUnbanUser() throws Exception {
createdChannelIds.add(channelId);
String cid = "messaging:" + channelId;
- ModerationImpl moderation = new ModerationImpl(client.getHttpClient());
+ Moderation moderation = client.moderation();
// Ban target user from channel
var banResp =