In Java EE there are basically 3 mechanisms for dealing with asynchronicity:
- JMS
- The CDI event bus
- @Asynchronous annotated methods of stateless session beans
ewernli already gives a very good explanation of what advantages JMS has. It's a very fully featured system, but all these features do cost a little in overhead and in complexity of e.g. managing the administrative objects involved.
In addition the JMS spec hasn't been updated for nearly a decade. It still being really useful shows the great deal of foresight that went into the design of the API, but at times it can feel a little arcane. The way in which administrative objects like destinations have to be defined, the way that you need to obtain a connection, create a session, etc, it all feels a little low-level, old and arcane. Receiving messages though has been greatly simplified with message driven beans, but sending has not.
Then, for some legacy bizarre reason, web modules are forbidden to listen to JMS destinations. Of course there is no rationale for that anymore, but it's in the ancient J2EE 1.3 and later specs. Compliant application servers still uphold this rule, but they all provide vendor specific configuration to allow it anyway. This however makes your application less portable.
A subset of the use cases for which JMS was historically used is very simple event based programming within a single application. For that the CDI event bus is arguable better suited now, as it's a simpler, more modern and an overall more lighter weight approach.
Another subset of use cases for which JMS was used is simply doing any work asynchronously. For that people sometimes used to make an MDB which would then just unwrap the parameters from the message and call some stateless session bean's method directly passing in those parameters. In this case the messaging paradigm is absolutely not needed and just calling an @Asynchronous annotated method is a simpler and more direct approach.