1

I have this issue where, upon sending ANY request from my frontend web app (Next.js) to my backend application in Spring Boot, I get a Stack Overflow Error, and not only that, but my application becomes completely useless and always throws this error, no matter what, until computer restart. So if this error is triggered, which I have not found the source of but only some possible causes, then basically my entire ide (IntelliJ) is screwed, even if I rebuild the application from source, or invalidate caches, or clean with maven, nothing works except a clean Windows restart to solve the issue. I have also observed this issue to happen in Linux as well via an active ec2 instance I have running.

Here is what the error looks like in the terminal, it returns an error 500 InternalServerError:

May 17 20:14:16 ip-IP.us-east-2.compute.internal java[2209]: 2025-05-17T20:14:16.996Z ERROR 2209 --- [nio-8080-exec-7] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Filter execution threw an exception] with root cause
May 17 20:14:16 ip-IP.us-east-2.compute.internal java[2209]: java.lang.StackOverflowError: null
May 17 20:14:17 ip-IP.us-east-2.compute.internal java[2209]: at org.springframework.core.BridgeMethodResolver.findBridgedMethod(BridgeMethodResolver.java:71) ~[spring-core-6.2.3.jar!/:6.2.3]
May 17 20:14:17 ip-IP.us-east-2.compute.internal java[2209]: at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:356) ~[spring-aop-6.2.3.jar!/:6.2.3]
May 17 20:14:17 ip-IP.us-east-2.compute.internal java[2209]: at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:216) ~[spring-aop-6.2.3.jar!/:6.2.3]
May 17 20:14:17 ip-IP.us-east-2.compute.internal java[2209]: at jdk.proxy2/jdk.proxy2.$Proxy167.authenticate(Unknown Source) ~[na:na]
May 17 20:14:17 ip-IP.us-east-2.compute.internal java[2209]: at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[na:na]
May 17 20:14:17 ip-IP.us-east-2.compute.internal java[2209]: at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[na:na]
May 17 20:14:17 ip-IP.us-east-2.compute.internal java[2209]: at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) ~[spring-aop-6.2.3.jar!/:6.2.3]
May 17 20:14:17 ip-IP.us-east-2.compute.internal java[2209]: at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:216) ~[spring-aop-6.2.3.jar!/:6.2.3]
May 17 20:14:17 ip-IP.us-east-2.compute.internal java[2209]: at jdk.proxy2/jdk.proxy2.$Proxy167.authenticate(Unknown Source) ~[na:na]
May 17 20:14:17 ip-IP.us-east-2.compute.internal java[2209]: at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[na:na]
May 17 20:14:17 ip-IP.us-east-2.compute.internal java[2209]: at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[na:na]
May 17 20:14:17 ip-IP.us-east-2.compute.internal java[2209]: at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) ~[spring-aop-6.2.3.jar!/:6.2.3]
May 17 20:14:17 ip-IP.us-east-2.compute.internal java[2209]: at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:216) ~[spring-aop-6.2.3.jar!/:6.2.3]
May 17 20:14:17 ip-IP.us-east-2.compute.internal java[2209]: at jdk.proxy2/jdk.proxy2.$Proxy167.authenticate(Unknown Source) ~[na:na]

aaaaand repeat for about a thousand more lines.

The issue is triggered specifically when sending API requests to the backend, I THINK to the authentication endpoint, in particular at /api/v1/login. This results in an error 500, and sometimes it just hangs the response to the frontend, other times it will actually throw the InternalServerError with 500.

This error doesn't occur all the time, only sometimes, but it is much more common when messing with certain code, like trying to configure and adjust the authentication cookies to send from my backend to my frontend upon login. I notice that the error occurs most frequently when trying to fix these cookies / httpServletResponse related bugs that form in my code.

When I get the SOE error, it is usually triggered (I believe) by my login endpoint, and in dev, once triggered, it remains for ALL endpoints in the server, regardless of the arguments. Even endpoints that do not EXIST (I haven't written controllers for them) return the same SOE error when requested to, when they should usually return "No static resource". This is true for all types of requests as well, including ones that require a Jwt and ones that don't.

Here is a fragment of the code I use in my authenticate() service method taking arguments @RequestBody request (a normal java dto class, basically), and HttpServletResponse. The following code is what I use to assign cookies to the response that will be going back to the requestor frontend to verify the user with a JWT cookie. Typically, the issue is most likely to be caused by something to do with this cookies code, and rerunning, and I'm not sure why.

        // Create and configure cookie for access token
        Cookie jwtCookie = new Cookie("auth-token", accessToken);
        jwtCookie.setHttpOnly(true);
        System.out.println("SECURE COOKIES: " + useSecureCookies);
        jwtCookie.setSecure(useSecureCookies); // Change to true in production
        jwtCookie.setPath("/");
        // Set domain conditionally
        if (Objects.equals(frontendUrl, "mydomain.com")) {
            jwtCookie.setDomain(".mydomain.com");
            System.out.println("TO DOMAIN: " + jwtCookie.getDomain());
        }

        // Add cookie to response
//        String cookieValue = String.format("jwt=%s; Path=/; Max-Age=3600; HttpOnly; Secure; SameSite=None", accessToken);
//        response.addHeader("Set-Cookie", cookieValue);
        // THIS is the actual thing that adds cookies to the frontend client
        response.addCookie(jwtCookie);

        // Create and configure cookie for refresh token as well
        Cookie refreshCookie = new Cookie("refresh-token", refreshToken);
        refreshCookie.setHttpOnly(true);
        refreshCookie.setSecure(useSecureCookies); // Change to true in production
        refreshCookie.setPath("/");
        if (Objects.equals(frontendUrl, "mydomain.com")) {
            refreshCookie.setDomain(".mydomain.com");
            System.out.println("TO DOMAIN: " + jwtCookie.getDomain());
        }

        // Conditionally set age based on if user entered "rememberMe" or not
        if (request.getRememberMe()) {
            refreshCookie.setMaxAge((int) Duration.ofDays(30).toSeconds());
            System.out.println("REMEMBERME: " + request.getRememberMe());
            System.out.println("MAXAGE: 30 DAYS");
        }
        else {
            refreshCookie.setMaxAge(-1);
            System.out.println("REMEMBERME: " + request.getRememberMe());
            System.out.println("MAXAGE: UNTIL END OF SESSION");
        }

        // Add refresh cookie to response
//        String refreshValue = String.format("refresh=%s; Path=/auth; Max-Age=2592000; HttpOnly; Secure; SameSite=None", refreshToken);
//        response.addHeader("Set-Cookie", refreshValue); // 30 days
        // THIS is the actual thing that adds cookies to the frontend client
        response.addCookie(refreshCookie)

Here also is the code of the controller that calls this service method

    @PostMapping("/login")
    public ResponseEntity<?> login(
            @RequestBody LoginRequest loginRequest,
            HttpServletResponse response
    ) {
        try {
            return loginService.authenticate(loginRequest, response);
        } catch (RuntimeException e) {
            e.printStackTrace();
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(e.getMessage());
        } catch (Exception e) {
            e.printStackTrace();
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Unexpected error");
        }
    }

The HttpStatus.INTERNAL_SERVER_ERROR is never returned, by the way. Even if the ISE 500 is thrown from the SOE, that line is never returned to the API request, only the huge chunk of text from the SOE directly.

When I make the login request in dev, I get the above error ALWAYS, however when I do it in prod, something about the build jar is different I guess and it is able to return me the more predictable correct response code (still an error, but fixable):

May 17 20:25:31 ip-IP.us-east-2.compute.internal java[2209]: Verifying credentials
May 17 20:25:31 ip-IP.us-east-2.compute.internal java[2209]: SECURE COOKIES: true
May 17 20:25:31 ip-IP.us-east-2.compute.internal java[2209]: TO DOMAIN: .mydomain.com
May 17 20:25:31 ip-IP.us-east-2.compute.internal java[2209]: java.lang.IllegalArgumentException: An invalid domain [.mydomain.com] was specified for this cookie
May 17 20:25:31 ip-IP.us-east-2.compute.internal java[2209]: at org.apache.tomcat.util.http.Rfc6265CookieProcessor.validateDomain(Rfc6265CookieProcessor.java:253)
May 17 20:25:31 ip-IP.us-east-2.compute.internal java[2209]: at org.apache.tomcat.util.http.Rfc6265CookieProcessor.generateHeader(Rfc6265CookieProcessor.java:147)
May 17 20:25:31 ip-IP.us-east-2.compute.internal java[2209]: at org.apache.catalina.connector.Response.generateCookieString(Response.java:881)
May 17 20:25:31 ip-IP.us-east-2.compute.internal java[2209]: at org.apache.catalina.connector.Response.addCookie(Response.java:837)

Interestingly, I noticed that in production, the SOE bug can still occur, but usually goes away after a little bit or a few requests, while in IntelliJ on my dev setup it sticks around and will be there EVERY time I run the program, regardless of whether I change things or invalidate caches or restart IntelliJ. And just to clarify, this issue does not affect the startup of the program at all. It always works perfectly fine as long as nothing else is wrong, in both dev and prod. It is only any request made to the application that causes the error 500 and huge response code.

The ONLY fix I have found to work is by restarting Windows. And yes, I can confirm that the code is the exact same from restart to restart, but the build and startup of the jar and request all go smoothly after a restart, at least until the error is triggered again. Then, like I said, it will sort of fix itself in prod after a bit but in dev I'm just screwed permanently every time I start the app until I restart my computer.

Some things I should note about my project, is that I'm using keycloak as an authentication provider with a Direct Access Grant to user accounts, and because of its jwt structuring I'm using a custom JwtAuthenticationConverter:

@Component
public class JwtAuthenticationConverter implements Converter<Jwt, AbstractAuthenticationToken> {

    private final JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter =
            new JwtGrantedAuthoritiesConverter();


    // These annotations pull data from the yml file
    @Value("${jwt.auth.converter.resource-id}")
    private String resourceId;
    @Value("${jwt.auth.converter.principal-attribute}")
    private String principalAttribute = "resource-id";

    @Override
    public AbstractAuthenticationToken convert(@NonNull Jwt jwt) {
        Collection<GrantedAuthority> authorities = Stream.concat(
                        jwtGrantedAuthoritiesConverter.convert(jwt).stream(),
                        extractResourceRoles(jwt).stream()
                )
                .collect(Collectors.toSet());


        return new JwtAuthenticationToken(jwt, authorities, getPrincipalClaimName(jwt));
    }

    private String getPrincipalClaimName(Jwt jwt) {
        String claimName = JwtClaimNames.SUB;
        if (principalAttribute != null) {
            claimName = principalAttribute;
        }
        return jwt.getClaim(claimName);
    }

    private Collection<? extends GrantedAuthority> extractResourceRoles(Jwt jwt) {
        Map<String, Object> resourceAccess = jwt.getClaim("resource_access");

        if (resourceAccess == null || !resourceAccess.containsKey(resourceId)) {
            return Set.of();
        }

        Map<String, Object> resource = (Map<String, Object>) resourceAccess.get(resourceId);

        if (resource == null || resource.get("roles") == null) {
            return Set.of();
        }

        Collection<String> resourceRoles;
        try {
            resourceRoles = (Collection<String>) resource.get("roles");
        } catch (ClassCastException e) {
            return Set.of(); // roles field is wrong type somehow
        }

        return resourceRoles.stream()
                .map(role -> new SimpleGrantedAuthority("ROLE_" + role))
                .collect(Collectors.toSet());
    }
}

and additionally I am using a created annotation @CurrentUser which takes type Jwt from all frontend api requests I add it to with the "credentials: include" directive:

// @CurrentUser class
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CurrentUser {
}

// Argument resolver in separate class file
@Component
public class CurrentUserArgumentResolver implements HandlerMethodArgumentResolver {

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(CurrentUser.class) && parameter.getParameterType().equals(Jwt.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter,
                                  ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest,
                                  WebDataBinderFactory binderFactory) throws Exception {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication != null && authentication.getPrincipal() instanceof Jwt jwt) {
            return jwt;
        }
        return null;
    }
}

I should note I haven't been able to positively identify any one cause of this problem, only that it occurs typically around the /login endpoint. And Spring Boot, keycloak, postgres, docker all function perfectly normally and fine in the absence of this error, I have been working on this project for some time now and I don't think it is caused directly by any one of those.

I've tried fixing the issue first by just asking ChatGPT 4o / o4-mini-high / 4.5, didn't help, moved on to Gemini 2.5 pro and Gemini definitely knew how to debug better, but even after venturing into the java Spring dependency source code, it couldn't be helped, and nothing seemed to fix the issue.

This is my first Stack Overflow question, so let me know if I need to change anything or if more info is needed. Sorry for the long read.

6
  • You shouldn't send tokens to the front end. Instead, use the OAuth2 BFF pattern either with the NextAuth.js lib or with a Spring Cloud Gateway instance. Commented May 18 at 1:57
  • 1
    Welcome to Stack Overflow. I hope your error is not literally unsolvable. If it were, posting it here could not help. Which I hope it does. Commented May 18 at 6:58
  • Maybe a bit off-topic, but why are you adding cookies to the response? You should just return the tokens in JSON response and manage tokens on front-end Commented May 18 at 7:04
  • @gaurav-pingale, he probably does this to hide tokens from the JavaScript code and its dependencies (to prevent XSS attacks). Tokens, like any other kind of credentials, shouldn't be accessible to the code running on remote devices. Using HttpOnly cookies achieves this purpose, but tokens still transit on the network. Using server memory (session) is more efficient and safer. Commented May 18 at 19:08
  • @ch4mp that's absolutely what I'm doing (or trying to do). If I'm being honest, I'm not a programmer by trade, but I'm actually a business student who has learned some web dev over the last 5 months to work on a project idea with a friend, so much of this is new to me. There are still lots of holes in my understanding - auth has been especially complicated - so I redirect most of my 'theory' and conventions questions to chatgpt, but I don't always get the best answer. If there is a better way to do this, like with session, I can try that as well. Commented May 18 at 22:39

1 Answer 1

0

Honestly, I don’t believe your StackOverflowError is caused by cookies—especially since it happens sporadically and is “fixed” by a reboot. It’s far more likely that the JVM simply doesn’t have enough stack space. I’d start by increasing the thread stack size with the -Xss option, or even try a different JVM distribution.

It’s also possible that you have excessively deep call chains—either through recursion or deeply nested method calls—that exhaust the stack. I recommend reviewing your code for any deep recursion or long chains of method invocations and refactoring them (for example, converting recursive algorithms to iterative ones) to prevent the error.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for responding. I can try increasing stack size, it just seems like a strange thing to need since I still feel like my project is relatively small in overall size, and I have been having this issue which seems to be most likely caused by the authentication processes I am running. I also don't think I have a single recursive call in the entire project, but I will have to check to confirm. I think my deepest method nesting is probably 3-4 calls deep from the initial controller call, but it could be much deeper.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.