OddSockets Java SDK

Official Java SDK for OddSockets real-time messaging platform

Java 11+ Maven Central Thread Safe High Performance Auto Reconnect

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

xml
<dependency>
    <groupId>com.oddsockets</groupId>
    <artifactId>oddsockets-java-sdk</artifactId>
    <version>1.0.0</version>
</dependency>
gradle
implementation 'com.oddsockets:oddsockets-java-sdk:1.0.0'
scala
libraryDependencies += "com.oddsockets" % "oddsockets-java-sdk" % "1.0.0"

Quick Start

Basic Usage

java
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

java
@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

java
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

java
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

java
// 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

java
// 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

java
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.

  • TypingstartTyping, stopTypinguser_typing, user_stopped_typing
  • ReactionsaddReaction, removeReaction, getReactionsreaction_added, reaction_removed
  • ThreadsthreadReply, getThread, subscribeThread, followThread, markThreadReadthread_reply, thread_subscribed, thread_followed, thread_read_updated
  • Read receiptsmarkRead, markAllRead, getUnreadCountsuser_read, unread_count_updated, all_marked_read
  • MessageseditMessage, deleteMessage, pinMessage, unpinMessage, getPinnedMessages, searchMessagesmessage_edited, message_deleted, message_pinned, message_unpinned
  • Presence & statussetStatus, setCustomStatus, setDND, getUserPresenceuser_status_changed, custom_status_updated, dnd_status_changed
  • ChannelscreateChannel, updateChannel, archiveChannel, inviteToChannel, joinChannel, leaveChannelchannel_created, channel_updated, user_invited, user_joined_channel, user_left_channel
  • DMscreateDM, sendDM, getDMConversationsdm_created, dm_received
  • NotificationssubscribeNotifications, getNotifications, markNotificationRead, clearNotificationsnotification, notification_read, notifications_cleared
  • SearchsearchMessages, 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

java
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 — loss
  • tied — draw
  • conceded — resign / concede
  • expired — 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 broadcastschallenge_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:

<50ms
Latency
99.9%
Uptime
32KB
Max Message
1M+
Messages/sec

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

java
@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

java
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

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

java
@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

java
@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());
    }
}