'random NullPointerException / onErrorDropped using webClient, due to request.getSession() being null

I have a Spring Boot (2.5) application in which I need to make a REST call to a remote system (a Solr instance where I store a denormalized view), in which I can either create or update records.

I don't really care about the response I get (and sometimes the remote system is slow to respond), so I am making an async call like this in createIndexForTicket / updateIndexForTicket :

public MyService(WebClient webClient, String solrUpdateUrl) {
    this.webClient = webClient;
    this.solrUpdateUrl = solrUpdateUrl;
}

  public void createIndexForTicket(TicketIndex ticketIndex) {
    // build the request
    var createRequest = webClient.post()
        .uri(solrUpdateUrl);

    triggerRequest(createRequest, ticketIndex,"creation");

    log.info("payload sent, creating index for ticket {} : {}",ticketIndex.getUserFriendlyTicketId(),ticketIndex);
  }

  
  public void updateIndexForTicket(TicketIndex ticketIndex) {
    // build the request
    var updateRequest = webClient.put()
        .uri(solrUpdateUrl + "/" + ticketIndex.getInternalTicketId());

    triggerRequest(updateRequest, ticketIndex,"update");

    log.info("payload sent, updating index for ticket {} : {}",ticketIndex.getUserFriendlyTicketId(),ticketIndex);
  }

  private static void triggerRequest(RequestBodySpec requestToSolr,
                                                                 TicketIndex ticketIndex,
                                                                  String action) {

    requestToSolr.bodyValue(ticketIndex)
        .retrieve()
        .onStatus(HttpStatus::is2xxSuccessful,
                  resp -> logSuccess(ticketIndex,action))
        .bodyToMono(String.class)
        .doOnError(t ->
            log.error("problem while performing a "+action+", "
                + "calling Solr for ticket "+ticketIndex.getUserFriendlyTicketId(),t))
        .subscribe();
  }

it works fine, most of the times. But I noticed that I sometimes get an Operator called default onErrorDropped error, with below stacktrace :

reactor.core.Exceptions$ErrorCallbackNotImplemented: java.lang.NullPointerException
Caused by: java.lang.NullPointerException: null
    at org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizedClientRepository.saveAuthorizedClient(HttpSessionOAuth2AuthorizedClientRepository.java:63)
    Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
    |_ checkpoint ⇢ Request to PUT https://myRemoteSolrSystem/services/v2/tickets/dGlja2V0aW5nLXNlcnZpY2UxNDEzNzM1 [DefaultWebClient]
Stack trace:
        at org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizedClientRepository.saveAuthorizedClient(HttpSessionOAuth2AuthorizedClientRepository.java:63)
        at org.springframework.security.oauth2.client.web.AuthenticatedPrincipalOAuth2AuthorizedClientRepository.saveAuthorizedClient(AuthenticatedPrincipalOAuth2AuthorizedClientRepository.java:92)
        at org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager.lambda$new$0(DefaultOAuth2AuthorizedClientManager.java:126)
        at org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager.authorize(DefaultOAuth2AuthorizedClientManager.java:184)
        at org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction.lambda$authorizeClient$24(ServletOAuth2AuthorizedClientExchangeFilterFunction.java:552)
        at reactor.core.publisher.MonoSupplier.call(MonoSupplier.java:86)
        at reactor.core.publisher.FluxSubscribeOnCallable$CallableSubscribeOnSubscription.run(FluxSubscribeOnCallable.java:227)
        at reactor.core.scheduler.SchedulerTask.call(SchedulerTask.java:68)
        at reactor.core.scheduler.SchedulerTask.call(SchedulerTask.java:28)
        at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
        at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304)
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
        at java.base/java.lang.Thread.run(Thread.java:834)

Looking in source code, I find this leads to spring-security-oauth2-client 5.5.1, in HttpSessionOAuth2AuthorizedClientRepository.saveAuthorizedClient

@Override
public void saveAuthorizedClient(OAuth2AuthorizedClient authorizedClient, Authentication principal,
        HttpServletRequest request, HttpServletResponse response) {
    Assert.notNull(authorizedClient, "authorizedClient cannot be null");
    Assert.notNull(request, "request cannot be null");
    Assert.notNull(response, "response cannot be null");
    Map<String, OAuth2AuthorizedClient> authorizedClients = this.getAuthorizedClients(request);
    authorizedClients.put(authorizedClient.getClientRegistration().getRegistrationId(), authorizedClient);
    request.getSession().setAttribute(this.sessionAttributeName, authorizedClients);
}

l.63, where the exception happens is the last one :

request.getSession().setAttribute(this.sessionAttributeName, authorizedClients);

So it looks like request.getSession() returns null... but I have no idea why, and I am not able to find a pattern. Sometimes I fire 2 consecutive calls from the same thread, one is successful while the other is not.. sometimes both fail, and sometimes both succeed. Some other time, I trigger only one call and it fails, while another thread does something similar more or less at the same time, and it works.

The webClient that gets injected is built like that :

@Bean
@Primary
WebClient servletWebClient(ClientRegistrationRepository clientRegistrations,
                         OAuth2AuthorizedClientRepository authorizedClients) {

var oauth = new ServletOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrations, authorizedClients);

oauth.setDefaultClientRegistrationId("keycloak");

return WebClient.builder()
    .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
    .apply(oauth.oauth2Configuration())
    .build();
}

Any hint on what I am not doing correctly, or on what I could try to understand better what is going on ?

Thanks



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source