Scope 'session' is not active for the current thread; IllegalStateException: No thread-bound request found
Asked Answered
B

9

73

I have a controller that I'd like to be unique per session. According to the spring documentation there are two details to the implementation:

1. Initial web configuration

To support the scoping of beans at the request, session, and global session levels (web-scoped beans), some minor initial configuration is required before you define your beans.

I've added the following to my web.xml as shown in the documentation:

<listener>
  <listener-class>
    org.springframework.web.context.request.RequestContextListener
  </listener-class>
</listener>

2. Scoped beans as dependencies

If you want to inject (for example) an HTTP request scoped bean into another bean, you must inject an AOP proxy in place of the scoped bean.

I've annotated the bean with @Scope providing the proxyMode as shown below:

@Controller
@Scope(value="session", proxyMode=ScopedProxyMode.TARGET_CLASS)
public class ReportBuilder implements Serializable {
    ...
    ...
}

Problem

In spite of the above configuration, I get the following exception:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.reportBuilder': Scope 'session' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.

Update 1

Below is my component scan. I have the following in web.xml:

<context-param>
  <param-name>contextClass</param-name>
  <param-value>
    org.springframework.web.context.support.AnnotationConfigWebApplicationContext
  </param-value>
</context-param>

<context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>org.example.AppConfig</param-value>
</context-param>

<listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

And the following in AppConfig.java:

@Configuration
@EnableAsync
@EnableCaching
@ComponentScan("org.example")
@ImportResource("classpath:applicationContext.xml")
public class AppConfig implements AsyncConfigurer {
  ...
  ...
}

Update 2

I've created a reproducible test case. This is a much smaller project, so there are differences, but the same error happens. There's quite a few files, so I've uploaded it as a tar.gz to megafileupload.

Bashemath answered 22/1, 2014 at 15:11 Comment(13)
Please post your component scan configuration (it is likely that the controller is found in the wrong context). If you have two component scans (ContextLoaderListener and DispatcherServlet (see #9016622)), then please post both.Lampley
Controllers are meant to be singleton scoped.Edithe
@SotiriosDelimanolis are you sure? Mine work fine, at least in the test project :-) It seems to be some wicket configuration specifics when they declare everything session scope but I'm not sureMeaganmeager
@BorisTreukhov There's nothing preventing them from being any other scope. However, in my opinion, it would be more reasonable to create a different session scoped bean that will contain whatever state you need for your session.Edithe
@BorisTreukhov Do you have a source for information on wicket declaring everything in session scope?Bashemath
I downloaded it and run maven install, and got this error "No WebApplicationContext found: no ContextLoaderListener registered?", but this is not the error you mention. The same error occurs skipping the tests with mvn clean install jetty:run-war. How do you run this to reproduce the error? Can you fix this and provide another version of the demo or provide instructions of how to run it.Pullen
I run it with mvn jetty:runBashemath
I just tried it again, it does not work with mvn clean install jetty:run, it aborts in a test. If you skip it, it aborts with missing keystorePullen
@jhadesdev I'm not sure why you're getting that problem. On a clean machine after installing maven and running 'mvn clean install -Dmaven.test.skip=true jetty:run` it works for me. Does anyone else have this problem?Bashemath
With a clean copy of the tar.gz, and scrapping my local maven (3.1.1) repository I did 'mvn clean install -Dmaven.test.skip=true jetty:run', and got the same error .FileNotFoundException: /code/demos/sw-session-scope/target/test-classes/keystore (No such file or directory). In your test did you download the version from megafileupload, could it be that the wrong version got uploaded accidentally? Also what maven version are you using.Pullen
I used the one from megafileupload. I'm using maven3.Bashemath
#17425239 Here][1] [1]: #17425239Zabaglione
[Using a request scoped bean outside of an actual web request][1] [1]: https://mcmap.net/q/275519/-using-a-request-scoped-bean-outside-of-an-actual-web-requestZabaglione
B
38

I'm answering my own question because it provides a better overview of the cause and possible solutions. I've awarded the bonus to @Martin because he pin pointed the cause.

Cause

As suggested by @Martin the cause is the use of multiple threads. The request object is not available in these threads, as mentioned in the Spring Guide:

DispatcherServlet, RequestContextListener and RequestContextFilter all do exactly the same thing, namely bind the HTTP request object to the Thread that is servicing that request. This makes beans that are request- and session-scoped available further down the call chain.

Solution 1

It is possible to make the request object available to other threads, but it places a couple of limitations on the system, which may not be workable in all projects. I got this solution from Accessing request scoped beans in a multi-threaded web application:

I managed to get around this issue. I started using SimpleAsyncTaskExecutor instead of WorkManagerTaskExecutor / ThreadPoolExecutorFactoryBean. The benefit is that SimpleAsyncTaskExecutor will never re-use threads. That's only half the solution. The other half of the solution is to use a RequestContextFilter instead of RequestContextListener. RequestContextFilter (as well as DispatcherServlet) has a threadContextInheritable property which basically allows child threads to inherit the parent context.

Solution 2

The only other option is to use the session scoped bean inside the request thread. In my case this wasn't possible because:

  1. The controller method is annotated with @Async;
  2. The controller method starts a batch job which uses threads for parallel job steps.
Bashemath answered 28/1, 2014 at 8:55 Comment(1)
I tried all of these... none works. I don't know why everywhere these solution are being suggested but they don't work. For those who still have this issue, have a look at this link: medium.com/@pranav_maniar/…Marietta
T
75

The problem is not in your Spring annotations but your design pattern. You mix together different scopes and threads:

  • singleton
  • session (or request)
  • thread pool of jobs

The singleton is available anywhere, it is ok. However session/request scope is not available outside a thread that is attached to a request.

Asynchronous job can run even the request or session doesn't exist anymore, so it is not possible to use a request/session dependent bean. Also there is no way to know, if your are running a job in a separate thread, which thread is the originator request (it means aop:proxy is not helpful in this case).


I think your code looks like that you want to make a contract between ReportController, ReportBuilder, UselessTask and ReportPage. Is there a way to use just a simple class (POJO) to store data from UselessTask and read it in ReportController or ReportPage and do not use ReportBuilder anymore?

Tychonn answered 25/1, 2014 at 20:39 Comment(6)
Isn't the purpose of the RequestContextListener to make the request available to other threads? The report builder builds the report in steps. As the report is built, the page updates via AJAX.Bashemath
NO, it isn't, I tried it before I deeply studied your code. I also tried a small code modification, but none of them works from the reason described in my answer.Tychonn
@Bashemath in this case you are not using Dispatcher Servlet, so you should use org.springframework.web.context.request.RequestContextListener instead(see its javadoc), but anyway it won't work because your work is done in a thread pool as Martin has noted. So this answer is correct.Meaganmeager
In my case, I have an OAuth2RestTemplate with request scope OAuth2ClientContext and when I use the oauth2 rest template in a new thread (an asyn call) I get this error. Now the question is, does changing the scope to prototype fix my issue? I don't really need that to be bound to request, all I want is to send a request to another micro-service for an incoming request, but I do need my OAuth2RestTemplate to issue an access token before sending the second requestMarietta
@Marietta this is long ago. however did you find a solution for your issue here?Hiltan
@Hiltan yes it was a long time ago and I don't remember exactly what I did and I don't have that source code ready either. But vaguely I remember that I ended up debugging OAuth2RestTemplate to find out where/when a token is requested and stored, then I built the rest template manually myself with making that class request scope, the rest were session scope... that way, only that class gets instantiated and because there was no token for the newly instantiated class, it would request a new one per request. I also added a cache mechanism to avoid this to happen too often (e.g. for the same user)Marietta
B
38

I'm answering my own question because it provides a better overview of the cause and possible solutions. I've awarded the bonus to @Martin because he pin pointed the cause.

Cause

As suggested by @Martin the cause is the use of multiple threads. The request object is not available in these threads, as mentioned in the Spring Guide:

DispatcherServlet, RequestContextListener and RequestContextFilter all do exactly the same thing, namely bind the HTTP request object to the Thread that is servicing that request. This makes beans that are request- and session-scoped available further down the call chain.

Solution 1

It is possible to make the request object available to other threads, but it places a couple of limitations on the system, which may not be workable in all projects. I got this solution from Accessing request scoped beans in a multi-threaded web application:

I managed to get around this issue. I started using SimpleAsyncTaskExecutor instead of WorkManagerTaskExecutor / ThreadPoolExecutorFactoryBean. The benefit is that SimpleAsyncTaskExecutor will never re-use threads. That's only half the solution. The other half of the solution is to use a RequestContextFilter instead of RequestContextListener. RequestContextFilter (as well as DispatcherServlet) has a threadContextInheritable property which basically allows child threads to inherit the parent context.

Solution 2

The only other option is to use the session scoped bean inside the request thread. In my case this wasn't possible because:

  1. The controller method is annotated with @Async;
  2. The controller method starts a batch job which uses threads for parallel job steps.
Bashemath answered 28/1, 2014 at 8:55 Comment(1)
I tried all of these... none works. I don't know why everywhere these solution are being suggested but they don't work. For those who still have this issue, have a look at this link: medium.com/@pranav_maniar/…Marietta
B
37

If anyone else stuck on same point, following solved my problem.

In web.xml

 <listener>
            <listener-class>
                    org.springframework.web.context.request.RequestContextListener 
            </listener-class>
  </listener>

In Session component

@Component
@Scope(value = "session",  proxyMode = ScopedProxyMode.TARGET_CLASS)

In pom.xml

    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.1</version>
    </dependency>
Bellboy answered 12/10, 2014 at 17:21 Comment(3)
You can also use ScopedProxyMode.INTERFACE without the need for cglib dependency. In this case you need an interface of course.Cobaltite
saved my life too!! I was missing the Listener-Class org.springframework.web.context.request.RequestContextListener in web.xmlPerk
As of Spring framework version 3.2, there is no need to add explicit dependency of CGLIB to your project any more. docs.spring.io/spring/docs/3.2.x/spring-framework-reference/…Swell
L
10

I fixed this issue by adding following code in my file.

@Component
@Scope(value = "session",  proxyMode = ScopedProxyMode.TARGET_CLASS)

XML configuration -

<listener>
        <listener-class>
            org.springframework.web.context.request.RequestContextListener 
        </listener-class>
</listener>

Above we can do using Java configuration -

@Configuration
@WebListener
public class MyRequestContextListener extends RequestContextListener {
}

How to add a RequestContextListener with no-xml configuration?

I am using spring version 5.1.4.RELEASE and no need to add below changes in pom.

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.10</version>
</dependency>
Lambert answered 13/2, 2019 at 14:5 Comment(1)
I used annotations as described in the answer and it worked out for me. Thanks. :)Gama
D
7

As per documentation:

If you are accessing scoped beans within Spring Web MVC, i.e. within a request that is processed by the Spring DispatcherServlet, or DispatcherPortlet, then no special setup is necessary: DispatcherServlet and DispatcherPortlet already expose all relevant state.

If you are runnning outside of Spring MVC ( Not processed by DispatchServlet) you have to use the RequestContextListener Not just ContextLoaderListener .

Add the following in your web.xml

   <listener>
            <listener-class>
                    org.springframework.web.context.request.RequestContextListener 
            </listener-class>
    </listener>        

That will provide session to Spring in order to maintain the beans in that scope

Update : As per other answers , the @Controller only sensible when you are with in Spring MVC Context, So the @Controller is not serving actual purpose in your code. Still you can inject your beans into any where with session scope / request scope ( you don't need Spring MVC / Controller to just inject beans in particular scope) .

Update : RequestContextListener exposes the request to the current Thread only.
You have autowired ReportBuilder in two places

1. ReportPage - You can see Spring injected the Report builder properly here, because we are still in Same web Thread. i did changed the order of your code to make sure the ReportBuilder injected in ReportPage like this.

log.info("ReportBuilder name: {}", reportBuilder.getName());
reportController.getReportData();

i knew the log should go after as per your logic , just for debug purpose i added .


2. UselessTasklet - We got exception , here because this is different thread created by Spring Batch , where the Request is not exposed by RequestContextListener.


You should have different logic to create and inject ReportBuilder instance to Spring Batch ( May Spring Batch Parameters and using Future<ReportBuilder> you can return for future reference)

Drifter answered 26/1, 2014 at 0:55 Comment(4)
I already have RequestContextListener in my web.xml. You can see this in the original question and source code of the example project.Bashemath
The listener that you have in the web.xml is ContextLoaderListener that loads the spring root context, and not RequestContextListener that binds the request data to the thread. Try to add RequestContextListener (and keep ContextLoaderListener), and remove the use @Async by passing parameters to the job, see my answer for further details. Can you tells us what happens if you add RequestContextListener to web.xml AND remove the use of Async ?Pullen
@jhadesdev It wont work even if we add the RequestContextListener. RequestContextListener will add the Request/Session attributes to the threadLocal of Original Thread ( which the Web request raised) . It wont put the attributes in Thread which is created by Spring Batch.Drifter
That is correct, adding RequestContextListener will only solve part of the problem, but it's necessary - please refer to my answer where i explain why and how to fix it. There where two problems that caused the 'request not bound to thread problem', and trying to access request data from a separate sprign batch thread was only one.Pullen
C
1

My answer refers to a special case of the general problem the OP describes, but I'll add it just in case it helps somebody out.

When using @EnableOAuth2Sso, Spring puts an OAuth2RestTemplate on the application context, and this component happens to assume thread-bound servlet-related stuff.

My code has a scheduled async method that uses an autowired RestTemplate. This isn't running inside DispatcherServlet, but Spring was injecting the OAuth2RestTemplate, which produced the error the OP describes.

The solution was to do name-based injection. In the Java config:

@Bean
public RestTemplate pingRestTemplate() {
    return new RestTemplate();
}

and in the class that uses it:

@Autowired
@Qualifier("pingRestTemplate")
private RestTemplate restTemplate;

Now Spring injects the intended, servlet-free RestTemplate.

Compliance answered 23/4, 2016 at 20:25 Comment(0)
Z
0

https://mcmap.net/q/275519/-using-a-request-scoped-bean-outside-of-an-actual-web-request

For This Issue check My answer at above given url

Using a request scoped bean outside of an actual web request

Zabaglione answered 4/6, 2015 at 9:48 Comment(1)
add some more details to your answerKentigerma
C
0

Had the same error when I had @Order annotation on a filter class. Even thou I added the filter through the HttpSecurity chain.

Removed the @Order and it worked.

Connate answered 30/10, 2020 at 8:51 Comment(0)
N
-1

You just need to define in your bean where you need a different scope than default singleton scope except prototype. For example:

<bean id="shoppingCart" 
   class="com.xxxxx.xxxx.ShoppingCartBean" scope="session">
   <aop:scoped-proxy/> 
</bean>
Niobic answered 30/7, 2017 at 2:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.