Migrating to v3

Found an error? Have a suggestion?Edit this page on GitHub

Migration to a new major version is always difficult, and AsyncAPI is no exception. To provide as smooth a transition as possible, this document shows the breaking changes between AsyncAPI v2 and v3 in an interactive manner.

If you want to update your AsyncAPI document, use the AsyncAPI converter directly in the CLI with the following command:

asyncapi convert asyncapi.json --output=asyncapi_v3.json --target-version=3.0.0

For a detailed read-through about all the changes (non-breaking as well), read all the v3 release notes first to acquire additional context about the changes introduced in v3.

Moved metadata

In v2, two properties of tags and externalDocs were placed outside of the Info Object. For consistency, info has been moved in v3.

AsyncAPI 2.x

Info

Tags

External Docs

AsyncAPI 3.0

Info

Tags

External Docs

1asyncapi: 2.6.0
2info: 
3  ...
4externalDocs:
5  description: Find more info here
6  url: https://www.asyncapi.com
7tags:
8  - name: e-commerce
1asyncapi: 3.0.0
2info:
3  externalDocs:
4    description: Find more info here
5    url: https://www.asyncapi.com
6  tags:
7    - name: e-commerce

Server URL splitting up

There was occasional confusion regarding what the URL of a Server Object should include.

AsyncAPI 2.x

Servers
Server

Url

AsyncAPI 3.0

Servers
Server

Host

Pathname

In v2, the URL was often a lengthy string, sometimes redundantly including details like the protocol.

In v3, the url property has been divided into host, pathname, and protocol—as was the case in v2—making the information more explicit.

1asyncapi: 2.6.0
2servers:
3  production:
4    url: "amqp://rabbitmq.in.mycompany.com:5672/production"
5    protocol: "amqp"
1asyncapi: 3.0.0
2servers:
3  production:
4    host: "rabbitmq.in.mycompany.com:5672",
5    pathname: "/production",
6    protocol: "amqp",

Operation, channel, and message decoupling

The decoupling of operations, channels, and messages is the most significant breaking change in v3, fundamentally altering how they relate to each other.

AsyncAPI 2.x

Channels
Channel Item
Operation (Publish and Subscribe)
Messages
Message
Headers
Payload

AsyncAPI 3.0

Channels
Channel
Messages
Message
Headers
Payload
Operations
Operation
action (send or receive)
channel
messages

In v2, reusing channels and having multiple operations per channel, such as operation variants, was impossible.

In v3, this has become possible, emphasizing that a channel and message should be independent of the operations performed.

For message brokers like Kafka, this is akin to defining topics and their associated messages. In REST interfaces, it pertains to the path and request type (e.g., POST, GET), along with the corresponding request and response messages. For WebSocket, it encompasses all messages transmitted through the WebSocket server. For Socket.IO, it delineates all the rooms and their messages.

Channels are now reusable across multiple AsyncAPI documents, each facilitating a slightly different action.

1asyncapi: 2.6.0
2...
3channels: 
4  user/signedup:
5    publish:
6      message:
7        payload:
8          type: object
9          properties:
10            displayName:
11              type: string
12              description: Name of the user
1asyncapi: 3.0.0
2...
3channels:
4  UserSignup:
5    address: "user/signedup"
6    messages: 
7      UserMessage: 
8        payload:
9          type: object
10          properties:
11            displayName:
12              type: string
13              description: Name of the user
14operations:
15  ConsumeUserSignups:
16    action: receive
17    channel: 
18      $ref: "#/channels/UserSignup"

Read more about the confusion between publishing and subscribing in the Operation keywords section.

Channel address and channel key

Another breaking change is that the channel key no longer represents the channel path. Instead, it's now an arbitrary unique ID. The channel paths are now defined using the address property within the Channel Object.

AsyncAPI 2.x

Channels
Channel Item

AsyncAPI 3.0

Channels
Channel
address

In v2, the channel's address/topic/path doubled as its ID, hindering reusability and preventing the definition of scenarios where the same address was used in different contexts.

In v3, the address/topic/path has been shifted to an address property, allowing the channel ID to be distinct and arbitrary.

1asyncapi: 2.6.0
2...
3channels: 
4  test/path:
5    ...
1asyncapi: 3.0.0
2channels:
3  testPathChannel:
4    address: "test/path"

Operation keywords

Another significant change is the shift away from defining operations using publish and subscribe, which had inverse meanings for your application. Now, you directly specify your application's behavior using send and receive via the action property in the Operation Object.

AsyncAPI 2.x

Channels
Channel Item
Operation (Publish and Subscribe)

AsyncAPI 3.0

Operations
Operation
action (send or receive)

In v2, the publish and subscribe operations consistently caused confusion, even among those familiar with the intricacies.

When you specified publish, it implied that others could publish to this channel since your application subscribed to it. Conversely, subscribe meant that others could subscribe because your application was the one publishing.

In v3, these operations have been entirely replaced with an action property that clearly indicates what your application does. That eliminates ambiguities related to other parties or differing perspectives.

Read more information about the confusion between publishing and subscribing:

Here is an example where the application both consumes and produces messages to the test channel:

1asyncapi: 2.6.0
2...
3channels: 
4  test/path:
5    subscribe:
6      ...
7    publish:
8      ...
1asyncapi: 3.0.0
2channels:
3  testPathChannel:
4    address: "test/path"
5    ...
6operations: 
7  publishToTestPath:
8    action: send
9    channel: 
10      $ref: "#/channels/testPathChannel"
11  consumeFromTestPath:
12    action: receive
13    channel: 
14      $ref: "#/channels/testPathChannel"

Messages instead of message

In v2, channels were defined with one or more messages using the oneOf property.

In v3, messages are defined using the Messages Object. For a channel with multiple messages, you specify multiple key-value pairs. For a channel with just one message, you use a single key-value pair.

1asyncapi: 2.6.0
2...
3channels:
4  user/signedup:
5    message: 
6      oneOf:
7        - messageId: UserMessage
8          ...
9        - messageId: UserMessage2
10          ...
11
12asyncapi: 2.6.0
13...
14channels:
15  user/signedup:
16    message: 
17      messageId: UserMessage
18      ...
1asyncapi: 3.0.0
2...
3channels:
4  UserSignup:
5    address: user/signedup
6    messages: 
7      UserMessage: 
8        ...
9      UserMessage2:
10        ...
11
12asyncapi: 3.0.0
13...
14channels:
15  UserSignup:
16    address: user/signedup
17    messages: 
18      UserMessage: 
19        ...

We have updated the structure of the Message Object by eliminating the messageId property. We now use the ID of the Message Object itself as the key in the key/value pairing, rendering a separate messageId property redundant.

Unifying explicit and implicit references

In v2, implicit references were allowed in certain instances. For instance, the server security configuration was identified by name, linking to a Security Schema Object within the components. Similarly, a channel could reference global servers by name.

In v3, all such references MUST be explicit. As a result, we made a minor modification to the Server Object security property, transforming it from an object to an array. The details regarding required scopes for OAuth and OpenID Connect were then relocated to the Security Scheme Object.

1asyncapi: 2.6.0
2servers:
3  production:
4    ...
5    security:
6      oauth_test: ["write:pets"]
7...
8channels: 
9  test/path:
10    servers:
11      - production
12components:
13  securitySchemes:
14    oauth_test: 
15      type: oauth2
16      flows:
17        implicit:
18          authorizationUrl: https://example.com/api/oauth/dialog
19          availableScopes:
20            write:pets: modify pets in your account
21            read:pets: read your pets
22      scopes:
23        - 'write:pets'
1asyncapi: 3.0.0
2servers:
3  production:
4    ...
5    security:
6      - $ref: "#/components/securitySchemes/oauth_test"
7...
8channels: 
9  test/path:
10    servers:
11      - $ref: "#/servers/production"
12components:
13  securitySchemes:
14    oauth_test:
15      type: oauth2
16      flows:
17        implicit:
18          authorizationUrl: https://example.com/api/oauth/dialog
19          availableScopes:
20            write:pets: modify pets in your account
21            read:pets: read your pets
22      scopes:
23        - "write:pets"

New trait behavior

In v2, traits invariably overwrote any duplicate properties specified both in the traits and the corresponding object. For instance, if both message traits and the message object defined headers, only the headers from the message traits would be recognized, effectively overriding those in the Message Object.

In v3, this behavior has been revised. The primary objects now take precedence over any definitions in the traits. Such an adjustment is consistent for traits in both operation and message objects.

Here is a message object and associated traits:

1messageId: userSignup
2description: A longer description.
3payload:
4  $ref: '#/components/schemas/userSignupPayload'
5traits:
6  - summary: Action to sign a user up.
7    description: Description from trait.

In v2, after applying the traits, the complete message object appeared as follows. Note how the description was overridden:

1messageId: userSignup
2summary: Action to sign a user up.
3description: Description from trait.
4payload:
5  $ref: '#/components/schemas/userSignupPayload'

That is the default behavior of the JSON Merge Patch algorithm we use.

In v3, we've instituted a guideline stating, A property on a trait MUST NOT override the same property on the target object. Consequently, after applying the traits in v3, the complete message object appears as follows:

1messageId: userSignup
2summary: Action to sign a user up.
3description: A longer description. # it's still description from "main" object
4payload:
5  $ref: '#/components/schemas/userSignupPayload'

Notice how the description is no longer overwritten.

Schema format and schemas

One limitation with schemas has always been the inability to reuse them across different schema formats.

AsyncAPI 2.x

components | channels
messages
message
schemaFormat
payload
schema

AsyncAPI 3.0

components | channels
messages
message
payload
schemaFormat
schema

In v2, the details about which schema format the payload uses are found within the message object, rather than being directly linked to the schema itself. Such separation hampers reusability, as the two data points aren't directly correlated.

To address this in v3, we've introduced a multi-format schema object that consolidates this information. Consequently, whenever you utilize schemaFormat, you'll need to modify the schema as follows:

1asyncapi: 2.6.0
2...
3channels:
4  user/signedup:
5    publish:
6      message: 
7        schemaFormat: 'application/vnd.apache.avro;version=1.9.0'
8        payload:
9          type: record
10          name: User
11          namespace: com.company
12          doc: User information
13          fields:
14            - name: displayName
15              type: string
1asyncapi: 3.0.0
2...
3channels:
4  UserSignup:
5    address: user/signedup
6    messages: 
7      userSignup: 
8        payload:
9          schemaFormat: 'application/vnd.apache.avro;version=1.9.0'
10          schema:
11            type: record
12            name: User
13            namespace: com.company
14            doc: User information
15            fields:
16              - name: displayName
17                type: string

Optional channels

In v3, defining channels has become entirely optional, eliminating the need to specify channels as an empty object (required in v2).

1asyncapi: 2.6.0
2...
3channels: {}
1asyncapi: 3.0.0
2...

Restricted parameters object

Parameters have often prioritized convenience over accurately reflecting real-world use cases.

AsyncAPI 2.x

components | channels
parameters
parameter
location
description
schema
type
enum
examples
default
description
pattern
multipleOf
And all other properties

AsyncAPI 3.0

components | channels
parameters
parameter
location
description
enum
examples
default

In v2, we significantly streamlined the Schema Object. While the previous version offered full capability with numerous, often underutilized options, it posed challenges in serializing objects or booleans in the channel path.

The new v3 simplifies this by consistently using the string type and limiting available properties. Now, you can only access enum, default, description, examples, and location, ensuring a more focused and practical approach.

1asyncapi: 2.6.0
2...
3channels: 
4  user/{user_id}/signedup:
5    parameters:
6      location: "$message.payload"
7      description: Just a test description
8      schema:
9        type: string
10        enum: ["test"]
11        default: "test"
12        examples: ["test"]
13    ...
1asyncapi: 3.0.0
2...
3channels: 
4  userSignedUp:
5    address: user/{user_id}/signedup
6    parameters:
7      user_id: 
8        enum: ["test"]
9        default: "test"
10        description: Just a test description
11        examples: ["test"]
12        location: "$message.payload"
Was this helpful?
Help us improve the docs by adding your contribution.
OR
Github:AsyncAPICreate Issue on GitHub