I am working on a Spring Boot (WebFlux) backend and an Angular frontend. The client requests the user's conversations, but the API returns 401 Unauthorized, and the server logs show a NullPointerException. Backend (Spring Boot - WebFlux) I have a controller for the messages: package com.bluevenomeb.read_api.controller; import com.bluevenomeb.read_api.model.Conversation; import com.bluevenomeb.read_api.model.Message; import com.bluevenomeb.read_api.service.MessageService; import org.springframework.web.bind.annotation.*; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.util.List; @RestController @RequestMapping("/messages") public class MessageController { private final MessageService messageService; public MessageController(MessageService messageService) { this.messageService = messageService; } @PostMapping public Mono<Message> sendMessage(@RequestParam String sender, @RequestParam String receiver, @RequestParam String content) { return messageService.sendMessage(sender, receiver, content); } @GetMapping("/sent/{sender}") public Flux<Message> getMessagesBySender(@PathVariable String sender) { return messageService.getMessagesBySender(sender); } @GetMapping("/received/{receiver}") public Flux<Message> getMessagesByReceiver(@PathVariable String receiver) { return messageService.getMessagesByReceiver(receiver); } @GetMapping("/conversation/{user}/{participant}") public Mono<Conversation> getConversation(@PathVariable String user, @PathVariable String participant) { return messageService.getConversation(user, participant); } @GetMapping("/conversations/{user}") public Flux<Conversation> getAllConversations(@PathVariable String user) { System.out.println("Getting all conversations for user: " + user); return messageService.getAllConversations(user); } } Inside the MessageService, we have this placeholder code: public Flux<Conversation> getAllConversations(String user) { return Flux.empty(); } Here is the security configuration, followed by the requests filter: package com.bluevenomeb.read_api.config; import com.bluevenomeb.read_api.utils.security.JwtRequestFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import static org.springframework.security.config.Customizer.withDefaults; @Configuration public class SecurityConfig { JwtRequestFilter jwtRequestFilter; public SecurityConfig(JwtRequestFilter jwtRequestFilter) { this.jwtRequestFilter = jwtRequestFilter; } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .cors(withDefaults()) .csrf(csrf -> csrf .ignoringRequestMatchers("/auth/**") // Disable CSRF for /auth/** endpoints ) .authorizeHttpRequests(authz -> authz .requestMatchers("/auth/**", "/texts/**").permitAll() .anyRequest().authenticated() ) .httpBasic(withDefaults()) .addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } @Bean public WebMvcConfigurer corsConfigurer() { return new WebMvcConfigurer() { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("http://localhost:4200") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true); } }; } } package com.bluevenomeb.read_api.utils.security; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebFilter; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.lang.NonNull; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.User; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; import java.util.ArrayList; @WebFilter("/*") public class JwtRequestFilter extends OncePerRequestFilter { private final JwtUtil jwtUtil; public JwtRequestFilter(JwtUtil jwtUtil) { this.jwtUtil = jwtUtil; } @Override protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain) throws ServletException, IOException { final String authorizationHeader = request.getHeader("Authorization"); String username = null; String jwt = null; // Check if Authorization header exists and starts with "Bearer " if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) { jwt = authorizationHeader.substring(7); // Remove "Bearer " if (!jwtUtil.isTokenExpired(jwt)) { username = jwtUtil.extractEmail(jwt); // Extract the username from the JWT } } // Validate the token and set authentication context if (username != null && jwtUtil.validateToken(jwt, username)) { // Create an authentication object (you can use your custom user details if needed) User principal = new User(username, "", new ArrayList<>()); // Create an authentication object and set it into the SecurityContext SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(principal, null, new ArrayList<>())); } // Continue the filter chain filterChain.doFilter(request, response); } } Here is the JwtUtil class: package com.bluevenomeb.read_api.utils.security; import io.jsonwebtoken.Claims; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.Jwts; import javax.crypto.SecretKey; import java.util.Date; public class JwtUtil { private final SecretKey secretKey; public JwtUtil(SecretKey secretKey) { this.secretKey = secretKey; } public String generateToken(String email) { return Jwts.builder() .subject(email) .issuedAt(new Date()) .expiration(new Date(System.currentTimeMillis() + 3600000)) // 1 hour expiration .signWith(secretKey) .compact(); } public Claims extractClaims(String token) throws ExpiredJwtException { return Jwts.parser() .verifyWith(secretKey) .build() .parseSignedClaims(token) .getPayload(); } public String extractEmail(String token) { return extractClaims(token).getSubject(); } public boolean isTokenExpired(String token) { try { return extractClaims(token).getExpiration().before(new Date()); } catch (ExpiredJwtException e) { return true; } catch (Exception e) { System.out.println("Error parsing token: " + e.getMessage()); return true; } } public boolean validateToken(String token, String username) { return username.equals(extractEmail(token)) && !isTokenExpired(token); } } And finally, the relevant Angular codes: ApiService get<T>(endpoint: string, params?: HttpParams, headers?: HttpHeaders): Observable<T> { const token = localStorage.getItem('token') || ''; if (token) { headers = headers || new HttpHeaders(); headers = headers.set('Authorization', `Bearer ${token}`); } return this.http.get<T>(`${this.apiUrl}/${endpoint}`, { params, headers }) .pipe(catchError(this.handleError)); } getAllConversations(userName: string): Observable<Conversation[]> { return this.get<Conversation[]>(`${Endpoints.MESSAGES}/conversations/${encodeURIComponent(userName)}`); } HomeComponent ngOnInit(): void { this.authService.user$.subscribe((user: User | null) => { if (user) { this.loadConversations(user); } }); } private loadConversations(user: User) { this.apiService.getAllConversations(user.username) .pipe(take(1)) .subscribe((conversations: Conversation[]) => { this.conversations = conversations; console.log("Conversations loaded for user " + user.email, conversations); }); } I use SpringBoot v3.4.3, with MongoDB. Server Side error: java.lang.NullPointerException: Cannot invoke "java.lang.Throwable.getMessage()" because the return value of "org.springframework.core.NestedExceptionUtils.getMostSpecificCause(java.lang.Throwable)" is null at org.springframework.web.util.DisconnectedClientHelper.isClientDisconnectedException(DisconnectedClientHelper.java:120) at org.springframework.web.context.request.async.WebAsyncManager.lambda$startDeferredResultProcessing$6(WebAsyncManager.java:450) at org.springframework.web.context.request.async.StandardServletAsyncWebRequest.lambda$onError$0(StandardServletAsyncWebRequest.java:195) at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) Client Side error: Failed to load resource: the server responded with a status of 401 () http://localhost:8080/messages/conversations/dstk Unauthorized request detected. ERROR Error: An unknown error occurred! at api.service.ts:63:29 handleError @ core.mjs:6662 Questions: Why is my Spring Boot API returning 401 Unauthorized, even when the Authorization header is set? Could the NullPointerException be related to the authentication failure, or is it a separate issue? Plus: The token is valid during the request, as I logout and login again (which generates a new token), before testing. Thanks a lot for your time! Any help would be really appreciated. Continue reading...