Skip to content

Migration guides

Here are some details about how to migrate from one major version to another.

From 6.x to 7.x

Web socket subprotocol negotiation

As part of issue #493, a separate protocols parameter was added to WebSocketClient.connect to enable web socket subprotocol negotiation.

Binary compatibility is preserved through some hidden synthetic functions. However, source compatibility isn't: usages of the connect() method that passed custom headers without a named headers parameter will no longer compile. Adding the headers = parameter name will solve the issue.

For full negotiation support, clients need to be aware of which subprotocol the server chose to speak. This is why the protocol property was added to WebSocketConnection (#498). Implementers of this interface must implement this property. There shouldn't be many 3rd party implementations of the connection interface, so binary compatibility should not be a real issue here.

STOMP web socket subprotocol negotiation

Some servers like ActiveMQ require negotiating the STOMP protocol as a web socket subprotocol during the web socket handshake (see issue #492), and cannot work otherwise.

Breaking change: To make the experience smoother, Krossbow v7.0.0 now automatically sends STOMP subprotocols (in all supported versions) during the web socket handshake via the Sec-WebSocket-Protocol header.

If your server doesn't support it, you can customize the web socket handshake by manually connecting using WebSocketClient.connect() with the parameters that suit you best, and then connect at STOMP level using the WebSocketConnection.stomp() extension (without the need for a StompClient at all):

val client: WebSocketClient = TODO("get some web socket client implementation")
val wsConnection = client.connect(url) // without any subprotocols
val stompConfig = StompConfig().apply { 
    // set your config here if needed
}
val stompSession = wsConnection.stomp(config)

No host header sent for STOMP 1.0

Thanks to the aforementioned changes, we can now detect the STOMP protocol version used by the server before sending the first STOMP frame (CONNECT or STOMP). If STOMP 1.0 is detected as web socket subprotocol during the web socket handshake, we no longer send by default the host header which was introduced in 1.1 (and effectively breaks some old servers, see #122). It can still be sent by manually specifying it of course.

STOMP protocol version negotiation

The STOMP protocol itself supports negotiation of the version via headers in the CONNECT (or STOMP) frame. So far, Krossbow only specified 1.2 as supported version. From now on, all 3 versions 1.0, 1.1, and 1.2 are advertised as supported by the client.

If necessary, this behavior can be overridden by sending the accept-version header manually in customStompConnectHeaders.

Because the protocol version can be negotiated both via web socket subprotocol and at STOMP level, there could potentially be a mismatch. If this happens, the connect() call throws an exception. This can be disabled with StompConfig.failOnStompVersionMismatch.

StompClient.connect() now throws a different WebSocketConnectionException

The org.hildan.krossbow.stomp.WebSocketConnectionException is deprecated in favor of org.hildan.krossbow.websocket.WebSocketConnectionException. That exception has been around for a few years now and encapsulates all connection failures from different client implementations already, so there is no need for a similar exception at the StompClient level.

If you used to catch this exception, make sure to update your import (the error-level deprecation should mitigate any risk of missing the new exception).

From 5.x to 6.x

Switch to kotlinx-io and the ByteString type

The kotlinx-io library has been revamped, with an implementation that closely matches Okio now. Krossbow has now internally switched from Okio to kotlinx-io as a result, but this part should have no visible effect for the consumers of the library.

However, since kotlinx-io is a somewhat "standard" extension library for Kotlin, Krossbow can now more legitimately use its ByteString type in public APIs that handled binary data (for both web socket and STOMP sessions). This type represents immutable sequences of bytes, which is more convenient API-wise than byte arrays.

You will have to switch to these types if you used binary-based APIs.

Deprecations cleanup

This is a major version, and therefore we allowed ourselves some cleanup by removing a bunch of deprecated APIs. If you see unresolved references and are not sure how to fix them, please switch back to the Krossbow version 5 and fix deprecation warnings by following the corresponding instructions.

Please check the changelog for the list of removals.

From 4.x to 5.x

End of Ktor 1.x support

krossbow-websocket-ktor-legacy artifact was removed. This means Krossbow no longer works with Ktor 1.x. If you were using this module, please migrate to Ktor 2, and use the non-legacy krossbow-websocket-ktor module.

Kotlin 1.8

This update of Krossbow brings Kotlin 1.8, which might bring some incompatible changes to the Kotlin stdlib. Please check the compatibility guide if you were using an older version of Kotlin.

From 3.x to 4.x

withJsonConversions moved to its own module

If you were using Krossbow with krossbow-stomp-kxserialization, the withJsonConversions helper has moved to a new module called krossbow-stomp-kxserialization-json. This new module now transitively brings kotlinx-serialization-json so you don't need to depend on that one explicitly.

Built-in web socket clients moved to their own module and default StompClient constructor removed

Up to (and including) version 3.x of Krossbow, the built-in web socket clients for the supported platforms were part of the krossbow-websocket-core module. This module provided a WebSocketClient.Companion.default() factory function to provide the built-in web socket implementation of the current platform. Likewise, the krossbow-stomp-core module provided a StompClient constructor that used the "default" built-in web socket implementation for the current platform.

This approach limited the targets supported by those 2 core modules, even though all of their functionality was target-agnostic. In order to support all Kotlin platforms in pure Kotlin modules, the built-in websocket implementations had to be moved to a separate module, and the constructor without web socket client was moved to a separate module (and later removed completely for simplicity).

Breaking dependency changes, in short:

  • if you used WebSocketClient.default() from krossbow-websocket-core, or any of the built-in clients directly, simply change your dependency to krossbow-websocket-builtin instead.
  • if you used the StompClient() constructor without WS client argument (using the default value), add an explicit dependency on krossbow-websocket-builtin and pass the built-in client explicitly to the constructor: StompClient(WebSocketClient.default()).

Note: the WebSocketClient.default() function was since renamed WebSocketClient.builtIn() in newer versions.

If you used other web socket implementations than the built-in ones, you don't have to change anything to your dependencies.

From 2.x to 3.x

Use Durations instead of millis

StompConfiguration no longer uses amounts of milliseconds, but uses the kotlin.time.Duration API. The -Millis suffixes for the relevant properties were therefore dropped and the types changed.

Before:

val stomp = StompClient {
    connectionTimeoutMillis = 2000
    receiptTimeoutMillis = 5000
    disconnectTimeoutMillis = 300
}

After:

import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds

val stomp = StompClient {
    connectionTimeout = 2.seconds
    receiptTimeout = 5.seconds
    disconnectTimeout = 300.milliseconds
}

Flow instead of Channel in WebSocketConnection

If you used the websocket API directly, the incomingFrames channel is now a Flow.

Before:

val conn = wsClient.connect(url)
for (frame in conn.incomingFrames) {
    // do stuff
}

After:

val conn = wsClient.connect(url)
conn.incomingFrames.collect {
    // do stuff
}

Tyrus no longer embedded in krossbow-websocket-spring

krossbow-websocket-spring no longer transitively brings a dependency on Tyrus.

If you didn't add any JSR-356 implementation manually, you now have to explicitly depend on one. If you want the same behaviour as before, add the Tyrus dependency to your build.gradle.kts as follows:

dependencies {
    implementation("org.glassfish.tyrus.bundles:tyrus-standalone-client-jdk:2.1.5")
}

From 1.x to 2.x

StompSession.use now passes the session as it, not this

In order to align with Closeable.use, the lambda for StompSession.use now receives the session as an argument (it) and not receiver (this).

Before:

StompClient().connect(url).use {
    sendText("/dest", "message")
}

After:

StompClient().connect(url).use {
    it.sendText("/dest", "message")
}
// or
StompClient().connect(url).use { session ->
    session.sendText("/dest", "message")
}