pragmatism should be the solution here. If you are in a high-volume, low-latency environment, you should definitely try not to send any responses. If you are not in such an environment, there is no problem sending a return value (or exception) from your command handler. One thing that’s not a good idea is to use the return value to return a crucial value for further activity, such as a server-generated identifier. Such a design will reduce the flexibility on the client to decide whether they want to wait for the response or not.
If you want to use events to notify components of an error, you can use a DomainEvent or an ApplicationEvent, depending on whether you want the error to be available in the EventStore (read: audit trail). Application events can be raised from the command handler (or the aggregate if you have injected an EventBus there) using CurrentUnitOfWork.get().publishEvent(event, eventBus). This will only publish the event if the UnitOfWork is committed.
So my vote is: unless you’re in a high-speed environment, go for the CommandCallback.