So I believe I figured out a simple way. Looking at AbstractMessageListenerContainer source it will always rollback the session if the SessionAwareMessageListener throws an exception.
So the simple solution is to not throw an exception from the listener. However, I need to force the rollback of the jdbc transaction which is usually done by throwing an exception. Per Spring docs, I can force the rollback programmatically via:
Yes, this tightly couples my code to the spring framework, but that doesn't matter in this case and it greatly simplifies the logic and keeps things simple.
In my quick testing, this successfully rolled back the current JDBC transaction while still committing the local JMS transaction.