Why does this work
@Bean
public ReactiveUserDetailsService userDetailsService() {
return username -> {
Mono<UserDetails> userDetailsMono = Mono.justOrEmpty(userRepository.findByUsername(username));
return userDetailsMono.switchIfEmpty(Mono.error(() -> new UsernameNotFoundException("No such user: " + username)));
};
}
but this doesn't (which I would prefer as a more concise option)?
@Bean
public ReactiveUserDetailsService userDetailsService() {
return username -> Mono.justOrEmpty(userRepository.findByUsername(username))
.switchIfEmpty(Mono.error(() -> new UsernameNotFoundException("No such user: " + username)));
}
Bad return type in lambda expression: Mono<User> cannot be converted to Mono<UserDetails>
User
is my custom user class that implements UserDetails
. findByUsername()
returns Mono<User>
// @Entity etc.
public class User implements UserDetails {
public interface UserRepository extends Repository<User, UUID> {
//...
Optional<User> findByUsername(String username);
ReactiveUserDetailsService
's abstract method, findByUsername(String username)
, returns Mono<UserDetails>
. Go figure why it doesn't return Mono<? extends UserDetails>
. It means your lambda should return Mono<UserDetails>
tooMono.justOrEmpty()
is generic and has this contract: <T> Mono<T> justOrEmpty(@Nullable Optional<? extends T> data)
. It relies on Java's type inference which has its limitations @Bean
public ReactiveUserDetailsService userDetailsService() {
return username -> Mono.justOrEmpty(userRepository.findByUsername(username));
}
the actual return type of justOrEmpty()
is inferred from the return type of the wrapping function, fetchByUsername()
. Which is Mono<UserDetails>
@Bean
public ReactiveUserDetailsService userDetailsService() {
return username -> {
Mono<UserDetails> userDetailsMono = Mono.justOrEmpty(userRepository.findByUsername(username));
return userDetailsMono.switchIfEmpty(Mono.error(() -> new UsernameNotFoundException("No such user: " + username)));
};
}
the actual return type of justOrEmpty()
is inferred from the type of the variable you assign the result to. Which is, again, Mono<UserDetails>
. Inferring from an expected return type and a variable type are two the most common ways Java's type inference works for generic methods
@Bean
public ReactiveUserDetailsService userDetailsService() {
return username -> Mono.justOrEmpty(userRepository.findByUsername(username))
.switchIfEmpty(Mono.error(() -> new UsernameNotFoundException("No such user: " + username)));
}
Java can infer justOrEmpty()
's return type neither from the return type of the outer function (you don't return the result of justOrEmpty()
) nor a variable type (you don't assign the result of justOrEmpty()
). So Java assumes the actual return type to be equal to the return type of the invoked function, findByUsername()
. Which is Optional<User>
(not Optional<UserDetails>
)
switchIfEmpty()
doesn't change the type of Mono
, it stays Mono<User>
. So in the end the lambda returns Mono<User>
which is different from Mono<UserDetails>
that the lambda should returnMono<User>
is not a subtype of Mono<UserDetails>
even though User
is a subtype of UserDetails
(Java's generics are invariant)justOrEmpty()
should return, sort of "turning off" the type inference. This works too: @Bean
public ReactiveUserDetailsService userDetailsService() {
return username -> Mono.<UserDetails>justOrEmpty(userRepository.findByUsername(username))
.switchIfEmpty(Mono.error(() -> new UsernameNotFoundException("No such user: " + username)));
}