I'm using Spring Framework with the Vavr library to handle exceptions using the Try class.

While Try.of() is well supported and integrates well with @Transactional, I've noticed that using mapTry/map (map is a shortcut for mapTry) after Try.of() doesn't propagate exceptions to onFailure and instead throws exceptions with proxy class.

It will be nice to have this enhancement if it's possible so we can get exceptions on failure if something happens on mapTry.

Steps to Reproduce:

  1. Create a Spring Boot application and include Vavr as a dependency.
  2. Use @Transactional annotation on a service method.
  3. Use Try.of() and mapTry in the method body.
@Transactional
public void myMethod() {
  Try.of(() -> {
    // Do something
  })
  .mapTry(value -> {
    // Do something that throws an exception
    throw new Exception("Error");
  })
  .onFailure(error -> {
    // This block is not executed when an exception is thrown
  });
}

Expected Behavior:

When an exception is thrown inside mapTry, it should be caught by the onFailure block.

Actual Behavior:

The exception is not caught by onFailure.

Comment From: jhoeller

I'm not sure what we could do differently here. Is our internally registered onFailure block for transaction processing somehow interfering with your declared one there? Beyond that, it's all standard Vavr semantics.

Comment From: Lunatix01

@jhoeller sorry if I confused you, what I mean here is when we use Transactional annotation in an example like this:

if findById fails for any reason transactional doesn't return the error, onFailure catches the error, but in mapTry if this save fails for a reason transactional annotation throws an exception.

https://github.com/spring-projects/spring-framework/issues/20361 We have this issue that transactional supports Try its been implemented a long time ago, and I was wondering if it's possible to do with .mapTry also if it's possible.

    public Try<Void> savePhoneNumber(PhoneNumberParams params) {
        return Try.of(() ->         
         userRepository.findById(params.getUserId()).orElseThrow())
            .mapTry(userEntity -> {
                userEntity.setPhoneNumber(params.getPhoneNumber());
                return userRepository.save(userEntity);
            })
            .<Void>map(ignored -> null)
            .onFailure(ex -> log.error("phone number cannot be saved.", ex));
    }

I hope I make sense here. and thanks for your time

Comment From: jhoeller

I see what you are trying to accomplish there. It's just that Spring is not really involved in that call arrangement up until the resulting Try instance is returned from savePhoneNumber, at which point we are going to add our internal onFailure block for rollback handling. Not sure why the mapTry block leads to an exception rather than an onFailure invocation then. Shouldn't this be transparent for calling code that adds a further onFailure block, being decoupled from how the original Try instance has been composed and whether mapTry has been involved there?

Comment From: Lunatix01

for your question yes, I also expected the behavior to be transparent. My assumption was that any onFailure block I add should catch exceptions irrespective of how the Try instance has been composed, including when using methods like mapTry.

I'm curious that onFailureblock doesn't seem to catch exceptions raised within the mapTry. I was wondering if Spring's transactional handling could be adjusted to account for this, or if this is something that needs to be addressed in conjunction with Vavr's own functionality. Thank you for your time again.

Comment From: jhoeller

I'm afraid I cannot reproduce this, several variations of your second example work for me both in terms of Spring's rollback handling and with a custom onFailure block always executing.

FWIW your first example does not actually return the Try instance, so Spring's rollback handling won't kick in at all.

Comment From: Lunatix01

Sorry for the late reply sir, I created a POC repo, you can check it if it's possible, basically I run a postgres instance in docker, and in demo.sql you can run those in the Postgres, so I made slug column to be not more than 3 characters so I can make @Transactional get failed by giving more than 3 characters, it throws DataIntegrityViolationException .map cant throw the exception to onFailure

This makes the application stop working and here is the piece of code in UserService

    @Transactional
    public void test() {
        Try.of(() -> userRepository.findByName("aland").orElseThrow())
                .map(userEntity -> {
                    userEntity.setSlug("aland");
                    return userRepository.save(userEntity);
                })
                .<Void>map(ignored -> null)
                .onFailure(ex -> log.error("slug cannot be saved.", ex));
    }

So I'm not sure here if I miss the concept of @Transactional and the way it works with vavr or what.

And thanks yes you are abs right, It was my bad I just wanted to show the skeleton of the error instead of a real example :)

Comment From: jhoeller

In the code example above, Spring is not actually involved. All that Spring does is to detect a returned Try instance in order to add a transaction-driven onFailure block that marks the transaction as rollback-only when a Vavr failure is present. With a void method as above, there is nothing we can intercept there, it all happens within the method body.

So as long as you can structure your code to return a Try instance from your @Transactional method, and as long as that Try instance exposes a failure that we can react to, you may rely on Spring's automatic rollback management. I'm afraid there isn't anything more we can do about this.

Comment From: Lunatix01

@jhoeller Thank you i understand now