OddSockets Java SDK
Official Java SDK for OddSockets real-time messaging platform
Overview & Features
The OddSockets Java SDK provides a robust, enterprise-ready interface for real-time messaging in Java applications, with full support for Spring Boot, Android, and standalone applications.
Java 11+ Support
Compatible with Java 11+ and all modern JVM languages including Kotlin and Scala.
Thread Safe
Fully thread-safe implementation with concurrent collections and proper synchronization.
Spring Boot Ready
Auto-configuration and starter dependencies for seamless Spring Boot integration.
High Performance
Optimized for low latency with efficient WebSocket connections and smart routing.
Cost Effective
No per-message pricing, industry-standard 32KB message limits, transparent pricing.
Automatic Failover
Built-in redundancy and intelligent error handling for 99.9% uptime.
Installation
<dependency>
<groupId>com.oddsockets</groupId>
<artifactId>oddsockets-java-sdk</artifactId>
<version>1.0.0</version>
</dependency>
implementation 'com.oddsockets:oddsockets-java-sdk:1.0.0'
libraryDependencies += "com.oddsockets" % "oddsockets-java-sdk" % "1.0.0"
Quick Start
Basic Usage
import com.oddsockets.OddSockets;
import com.oddsockets.Channel;
import com.oddsockets.config.OddSocketsConfig;
public class BasicExample {
public static void main(String[] args) {
// Create configuration
OddSocketsConfig config = new OddSocketsConfig.Builder()
.apiKey("ak_live_1234567890abcdef")
.build();
// Initialize client
OddSockets client = new OddSockets(config);
// Get channel
Channel channel = client.channel("my-channel");
// Subscribe to messages
channel.subscribe(message -> {
System.out.println("Received: " + message.getData());
});
// Publish a message
channel.publish("Hello, World!");
}
}
Spring Boot Integration
@RestController
@RequestMapping("/api/chat")
public class ChatController {
@Autowired
private OddSockets oddSocketsClient;
@PostMapping("/send")
public ResponseEntity<String> sendMessage(@RequestBody ChatMessage message) {
Channel channel = oddSocketsClient.channel("chat-room");
try {
channel.publish(message);
return ResponseEntity.ok("Message sent successfully");
} catch (Exception e) {
return ResponseEntity.status(500).body("Failed to send message");
}
}
}
Async/CompletableFuture Usage
import java.util.concurrent.CompletableFuture;
public class AsyncExample {
public void asyncPublish() {
OddSockets client = new OddSockets(config);
Channel channel = client.channel("async-channel");
// Async publish
CompletableFuture<PublishResult> future = channel.publishAsync("Hello Async!");
future.thenAccept(result -> {
System.out.println("Message published: " + result.getMessageId());
}).exceptionally(throwable -> {
System.err.println("Publish failed: " + throwable.getMessage());
return null;
});
}
}
Configuration
Client Configuration
OddSocketsConfig config = new OddSocketsConfig.Builder()
.apiKey("ak_live_1234567890abcdef") // Required: Your OddSockets API key
.userId("user-123") // Optional: User identifier
.autoConnect(true) // Optional: Auto-connect on creation
.reconnectAttempts(5) // Optional: Max reconnection attempts
.heartbeatInterval(30000) // Optional: Heartbeat interval (ms)
.timeout(10000) // Optional: Connection timeout (ms)
.build();
Channel Options
// Subscribe with options
SubscribeOptions subscribeOptions = new SubscribeOptions.Builder()
.enablePresence(true) // Enable presence tracking
.retainHistory(true) // Retain message history
.filter("user.premium == true") // Message filter expression
.build();
channel.subscribe(callback, subscribeOptions);
// Publish with options
PublishOptions publishOptions = new PublishOptions.Builder()
.ttl(3600) // Time to live (seconds)
.metadata(Map.of("priority", "high")) // Additional metadata
.storeInHistory(true) // Store in message history
.build();
channel.publish(message, publishOptions);
Examples
Explore comprehensive examples demonstrating the OddSockets Java SDK in production applications:
Enhanced Features
Beyond core pub/sub, OddSockets ships a Slack-like enhanced surface —
reactions, typing indicators, threads, read receipts, presence/status, notifications, DMs,
channel management, message editing and search. It lives on client.enhanced.
The pattern is always the same: send an action with a
client.enhanced.* method, then receive the paired broadcast
with client.on("<event>", handler).
Typing & Reactions
// Receive-path: broadcasts from other users on the channel
client.on("user_typing", data -> System.out.println("user is typing"));
client.on("reaction_added", data -> System.out.println("reaction added"));
// Send-path: enhanced actions over the live socket
client.enhanced.startTyping("alice", "room-42");
client.enhanced.addReaction("msg-1", "room-42", ":thumbsup:", "alice", "Alice");
Threads
client.on("thread_reply", data -> System.out.println("new thread reply"));
CompletableFuture<JsonObject> reply = client.enhanced.threadReply(
"room-42", "msg-1", "Replying in the thread", "alice", "Alice");
System.out.println("thread reply: " + reply.get());
Enhanced surface
Each area exposes methods on client.enhanced; the worker broadcasts the
paired events which you handle with client.on(...). Query methods
(get*, search*) return a
CompletableFuture<JsonObject> that completes with the worker response.
- Typing —
startTyping,stopTyping→user_typing,user_stopped_typing - Reactions —
addReaction,removeReaction,getReactions→reaction_added,reaction_removed - Threads —
threadReply,getThread,subscribeThread,followThread,markThreadRead→thread_reply,thread_subscribed,thread_followed,thread_read_updated - Read receipts —
markRead,markAllRead,getUnreadCounts→user_read,unread_count_updated,all_marked_read - Messages —
editMessage,deleteMessage,pinMessage,unpinMessage,getPinnedMessages,searchMessages→message_edited,message_deleted,message_pinned,message_unpinned - Presence & status —
setStatus,setCustomStatus,setDND,getUserPresence→user_status_changed,custom_status_updated,dnd_status_changed - Channels —
createChannel,updateChannel,archiveChannel,inviteToChannel,joinChannel,leaveChannel→channel_created,channel_updated,user_invited,user_joined_channel,user_left_channel - DMs —
createDM,sendDM,getDMConversations→dm_created,dm_received - Notifications —
subscribeNotifications,getNotifications,markNotificationRead,clearNotifications→notification,notification_read,notifications_cleared - Search —
searchMessages,searchInChannel,searchByUser,filterMessages→ (future results)
For any worker event not wrapped above, subscribe with the raw
client.on("<event>", handler) API — all enhanced broadcasts are
forwarded onto the client surface.
Challenges & Leaderboards
Challenges, leaderboards and achievements build on the same live socket. The send
side lives on the enhanced surface (client.enhanced);
request/query methods return a CompletableFuture<JsonObject> that
resolves with the worker's reply, while progress and achievement calls are
fire-and-forget. Inbound broadcasts arrive on the client event surface —
subscribe with the client's normal client.on("<event>", handler).
Quick example
client.enhanced.createChallenge(Map.of(
"challengeId", "weekly-sprint", "metric", "points", "ranked", true)).get();
client.enhanced.reportProgress(Map.of("challengeId", "weekly-sprint", "value", 120));
JsonObject board = client.enhanced.getStandings(
Map.of("challengeId", "weekly-sprint", "limit", 10)).get();
client.enhanced.completeChallenge(
Map.of("challengeId", "weekly-sprint", "outcome", "completed")).get();
Methods
All methods live on client.enhanced. Request/query methods return a
CompletableFuture<JsonObject> that completes with the acked reply;
the fire-and-forget methods return void.
- createChallenge — create a challenge/leaderboard. Ack
challenge_create_success. - reportProgress — fire-and-forget metric progress. (no ack)
- completeChallenge — finalize with an outcome. Ack
challenge_complete_success. - unlockAchievement — fire-and-forget; pass
percentComplete(0–100). (no ack) - getStandings — request top-N + caller rank. Ack
challenge_standings_success. - getAchievements — query achievement state. Ack
achievement_state. - sendChallengeInvite — directed invite to another user. Ack
challenge_invite_success. - replyChallengeInvite — accept/decline an invite. Ack
challenge_reply_success. - cancelChallengeInvite — cancel a sent invite. Ack
challenge_invite_cancel_success. - getChallengeInvites — list pending invites. Ack
challenge_invites.
Completion outcomes
Pass one of these as the outcome to completeChallenge:
completed— win (rank 1)failed— losstied— drawconceded— resign / concedeexpired— timed out
Progressive achievements
unlockAchievement always emits the wire event achievement_unlock;
the worker is authoritative and derives the outbound broadcast from
percentComplete: a value < 100 broadcasts
achievement_progress (status in_progress), while
>= 100 or an omitted value broadcasts achievement_unlock
(status unlocked). You never emit achievement_progress yourself.
Inbound events
Subscribe with the client's normal client.on("<event>", handler).
- Room broadcasts —
challenge_progress,leaderboard_rank_change,challenge_complete,achievement_unlock,achievement_progress - Directed (per-user) —
challenge_invited,challenge_reply_received,challenge_invite_cancelled
Performance & Compatibility
OddSockets Java SDK delivers superior performance with broad JVM compatibility:
JVM Support
- Java 11+ (LTS)
- Kotlin 1.8+
- Scala 2.13+
- Android API 26+
Framework Support
- Spring Boot 2.7+
- Micronaut 3.0+
- Quarkus 2.0+
- Jakarta EE 9+
Framework Integrations
The OddSockets Java SDK works seamlessly with all modern Java frameworks. Here are examples showing how to integrate with popular frameworks:
Spring Boot
@Configuration
@EnableConfigurationProperties(OddSocketsProperties.class)
public class OddSocketsConfig {
@Bean
@ConditionalOnMissingBean
public OddSockets oddSocketsClient(OddSocketsProperties properties) {
OddSocketsConfig config = new OddSocketsConfig.Builder()
.apiKey(properties.getApiKey())
.userId(properties.getUserId())
.autoConnect(properties.isAutoConnect())
.build();
return new OddSockets(config);
}
}
@Service
public class ChatService {
@Autowired
private OddSockets oddSocketsClient;
@EventListener
public void handleUserMessage(UserMessageEvent event) {
Channel channel = oddSocketsClient.channel("chat-" + event.getRoomId());
channel.publish(event.getMessage());
}
}
Android
public class ChatActivity extends AppCompatActivity {
private OddSockets oddSocketsClient;
private Channel chatChannel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_chat);
// Initialize OddSockets client
OddSocketsConfig config = new OddSocketsConfig.Builder()
.apiKey("ak_live_1234567890abcdef")
.userId(getCurrentUserId())
.build();
oddSocketsClient = new OddSockets(config);
chatChannel = oddSocketsClient.channel("mobile-chat");
// Subscribe to messages
chatChannel.subscribe(message -> {
runOnUiThread(() -> {
displayMessage(message);
});
});
}
private void sendMessage(String text) {
ChatMessage message = new ChatMessage(getCurrentUserId(), text);
chatChannel.publishAsync(message)
.thenAccept(result -> {
Log.d("Chat", "Message sent: " + result.getMessageId());
})
.exceptionally(throwable -> {
Log.e("Chat", "Failed to send message", throwable);
return null;
});
}
@Override
protected void onDestroy() {
super.onDestroy();
if (oddSocketsClient != null) {
oddSocketsClient.disconnect();
}
}
}
Kotlin
import com.oddsockets.OddSockets
import com.oddsockets.config.OddSocketsConfig
import kotlinx.coroutines.*
class ChatService {
private val oddSocketsClient: OddSockets
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
init {
val config = OddSocketsConfig.Builder()
.apiKey("ak_live_1234567890abcdef")
.userId("kotlin-user")
.build()
oddSocketsClient = OddSockets(config)
}
suspend fun subscribeToChannel(channelName: String) {
val channel = oddSocketsClient.channel(channelName)
channel.subscribe { message ->
scope.launch {
handleMessage(message)
}
}
}
suspend fun publishMessage(channelName: String, message: Any) {
val channel = oddSocketsClient.channel(channelName)
try {
val result = channel.publishAsync(message).await()
println("Message published: ${result.messageId}")
} catch (e: Exception) {
println("Failed to publish message: ${e.message}")
}
}
private suspend fun handleMessage(message: Message) {
// Process message on background thread
withContext(Dispatchers.Default) {
println("Received message: ${message.data}")
}
}
fun cleanup() {
scope.cancel()
oddSocketsClient.disconnect()
}
}
Micronaut
@Factory
public class OddSocketsFactory {
@Singleton
public OddSockets oddSocketsClient(@Property(name = "oddsockets.api-key") String apiKey,
@Property(name = "oddsockets.user-id") Optional<String> userId) {
OddSocketsConfig.Builder builder = new OddSocketsConfig.Builder()
.apiKey(apiKey);
userId.ifPresent(builder::userId);
return new OddSockets(builder.build());
}
}
@Controller("/api/chat")
public class ChatController {
private final OddSockets oddSocketsClient;
public ChatController(OddSockets oddSocketsClient) {
this.oddSocketsClient = oddSocketsClient;
}
@Post("/send")
public Single<HttpResponse<String>> sendMessage(@Body ChatMessage message) {
Channel channel = oddSocketsClient.channel("micronaut-chat");
return Single.fromFuture(channel.publishAsync(message))
.map(result -> HttpResponse.ok("Message sent: " + result.getMessageId()))
.onErrorReturn(throwable ->
HttpResponse.serverError("Failed to send message: " + throwable.getMessage()));
}
}
Quarkus
@ApplicationScoped
public class OddSocketsProducer {
@ConfigProperty(name = "oddsockets.api-key")
String apiKey;
@ConfigProperty(name = "oddsockets.user-id", defaultValue = "quarkus-user")
String userId;
@Produces
@Singleton
public OddSockets oddSocketsClient() {
OddSocketsConfig config = new OddSocketsConfig.Builder()
.apiKey(apiKey)
.userId(userId)
.build();
return new OddSockets(config);
}
}
@Path("/api/chat")
@ApplicationScoped
public class ChatResource {
@Inject
OddSockets oddSocketsClient;
@POST
@Path("/send")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Uni<Response> sendMessage(ChatMessage message) {
Channel channel = oddSocketsClient.channel("quarkus-chat");
return Uni.createFrom().completionStage(channel.publishAsync(message))
.map(result -> Response.ok()
.entity(Map.of("messageId", result.getMessageId()))
.build())
.onFailure().recoverWithItem(throwable ->
Response.serverError()
.entity(Map.of("error", throwable.getMessage()))
.build());
}
}