A single instance of 'application' scoped bean lives within a ServletContext instance. That means it can be used between multiple servlet based applications running in the same ServletContext, e.g. two Spring's ApplicationContexts can use the same 'application' scoped bean.
The default 'singleton' bean lives only within a single ApplicationContext, whereas, an 'application' bean lives within ServletContext i.e. across multiple ApplicationContexts.
Spring stores 'application' scoped bean as a regular ServletContext attribute.
Example
In this example we will create two web based ApplicationContexts running under same ServletContext.
WebApplicationInitializer
We are going to create two DispatcherServlets with different bean configurations and different mappings:
package com.logicbig.example;
import com.logicbig.example.app1.MyWebConfig;
import com.logicbig.example.app2.MyWebConfig2;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRegistration;
public class MyWebInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
registerSpringContext(servletContext, MyWebConfig.class, "dispatcher1", "/app1/*");
registerSpringContext(servletContext, MyWebConfig2.class, "dispatcher2", "/app2/*");
}
private void registerSpringContext(ServletContext servletContext, Class<?> configClass,
String servletName, String mapping) {
AnnotationConfigWebApplicationContext ctx =
new AnnotationConfigWebApplicationContext();
ctx.register(configClass);
ctx.setServletContext(servletContext);
ServletRegistration.Dynamic servlet =
servletContext.addServlet(servletName, new DispatcherServlet(ctx));
servlet.setLoadOnStartup(1);
servlet.addMapping(mapping);
}
}
App1
package com.logicbig.example.app1;
import com.logicbig.example.AppLevelPreference;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@EnableWebMvc
@Configuration
public class MyWebConfig implements WebMvcConfigurer {
@Bean
public MyController myController() {
return new MyController();
}
@Bean
@Scope(value = WebApplicationContext.SCOPE_APPLICATION)
public AppLevelPreference appLevelPreference() {
return new AppLevelPreference();
}
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.jsp("/WEB-INF/views/", ".jsp");
}
}
package com.logicbig.example.app1;
import com.logicbig.example.AppLevelPreference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import jakarta.servlet.http.HttpServletRequest;
@Controller
public class MyController {
@Autowired
private AppLevelPreference appLevelPreference;
@RequestMapping(value = "/**", method = RequestMethod.GET)
public String appHandler(Model model, HttpServletRequest req) {
model.addAttribute("pref", appLevelPreference);
model.addAttribute("uri", req.getRequestURI());
model.addAttribute("msg", "response from app1");
return "app-page";
}
@RequestMapping(value = "/**", method = RequestMethod.POST)
public String appPostHandler(@RequestParam("fontSize") String fontSize,
@RequestParam("background") String background,
HttpServletRequest req) {
appLevelPreference.setBackground(background);
appLevelPreference.setFontSize(fontSize);
System.out.println(req.getRequestURI());
return "redirect:" + req.getRequestURI();//redirect to GET
}
}
App2
package com.logicbig.example.app2;
import com.logicbig.example.AppLevelPreference;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@EnableWebMvc
@Configuration
public class MyWebConfig2 implements WebMvcConfigurer {
@Bean
public MyController2 myController2() {
return new MyController2();
}
@Bean
@Scope(value = WebApplicationContext.SCOPE_APPLICATION)
public AppLevelPreference appLevelPreference() {
return new AppLevelPreference();
}
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.jsp("/WEB-INF/views/", ".jsp");
}
}
package com.logicbig.example.app2;
import com.logicbig.example.AppLevelPreference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import jakarta.servlet.http.HttpServletRequest;
@Controller
public class MyController2 {
@Autowired
private AppLevelPreference appLevelPreference;
@RequestMapping(value = "/**", method = RequestMethod.GET)
public String appHandler(Model model,
HttpServletRequest req) {
model.addAttribute("pref", appLevelPreference);
model.addAttribute("uri", req.getRequestURI());
model.addAttribute("msg", "response from app2");
return "app-page";
}
@RequestMapping(value = "/**", method = RequestMethod.POST)
public String appPostHandler(@RequestParam("fontSize") String fontSize,
@RequestParam("background") String background,
HttpServletRequest req) {
appLevelPreference.setBackground(background);
appLevelPreference.setFontSize(fontSize);
System.out.println(req.getRequestURI());
return "redirect:" + req.getRequestURI();//redirect to GET
}
}
The application scoped bean
package com.logicbig.example;
import java.io.Serializable;
import java.util.Objects;
public class AppLevelPreference implements Serializable {
private String background = "#fff";
private String fontSize = "14px";
public String getBackground() {
return background;
}
public void setBackground(String background) {
this.background = background;
}
public String getFontSize() {
return fontSize;
}
public void setFontSize(String fontSize) {
this.fontSize = fontSize;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof AppLevelPreference)) return false;
AppLevelPreference that = (AppLevelPreference) o;
return Objects.equals(background,
that.background) && Objects.equals(
fontSize,
that.fontSize);
}
@Override
public int hashCode() {
return Objects.hash(background, fontSize);
}
}
Note that both config classes MyWebConfig and MyWebConfig2 are using the same name for our application scoped bean (by naming same method name pref()), otherwise two different instances will be stored with two different ServletContext attribute names.
JSP page
src/main/webapp/WEB-INF/views/app-page.jsp<html>
<body style="background:${pref.background};font-size:${pref.fontSize}">
<h3>App</h3>
<form action="" method="post" >
Background <input type="text" name="background" value="${pref.background}"/>
Font size <input type="text" name="fontSize" value="${pref.fontSize}"/>
<input type="submit" value="Submit" />
<p>App content .... at ${uri}</p>
<p>${msg}</p>
</body>
</html>
Output
To try examples, run embedded Jetty (configured in pom.xml of example project below):
mvn jetty:run
Accessing http://localhost:8080/app1/
Now change the background value and submit the form:
Now access the other spring application at /app2/ :
Note that if you change the 'application' scope to the default 'singleton' scope in both config classes, then different AppLevelPreference instances will be used for each contexts.
Integration Test
package com.logicbig.example;
import com.logicbig.example.app1.MyWebConfig;
import com.logicbig.example.app2.MyWebConfig2;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockServletContext;
import org.springframework.test.web.servlet.assertj.MockMvcTester;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
public class ApplicationScopeIntegrationTest {
private MockMvcTester tester1;
private MockMvcTester tester2;
private AnnotationConfigWebApplicationContext ctx1;
private AnnotationConfigWebApplicationContext ctx2;
@BeforeEach
void setup() {
MockServletContext sharedServletContext = new MockServletContext();
// Setup App 1
ctx1 = new AnnotationConfigWebApplicationContext();
ctx1.setServletContext(sharedServletContext);
ctx1.register(MyWebConfig.class);
ctx1.refresh();
// Wrap the standard MockMvc in the new Tester
tester1 = MockMvcTester.create(MockMvcBuilders.webAppContextSetup(ctx1)
.build());
// Setup App 2
ctx2 = new AnnotationConfigWebApplicationContext();
ctx2.setServletContext(sharedServletContext);
ctx2.register(MyWebConfig2.class);
ctx2.refresh();
tester2 = MockMvcTester.create(MockMvcBuilders.webAppContextSetup(ctx2)
.build());
}
@AfterEach
void tearDown() {
if (ctx1 != null) ctx1.close();
if (ctx2 != null) ctx2.close();
}
@Test
void testAppScopedBean() {
// 1. POST new preferences to App 1
tester1.post().uri("/app1")
.param("background", "blue")
.param("fontSize", "18px")
.exchange() // Performs the request
.assertThat()
.hasStatus3xxRedirection()
.hasRedirectedUrl("/app1");
// 2. GET from App 1 - verify state change
tester1.get().uri("/app1")
.exchange()
.assertThat()
.hasStatusOk()
.model().containsKey("pref");
// 3. GET from App 2 - verify shared application scope
tester2.get().uri("/app2")
.exchange()
.assertThat()
.hasStatusOk()
.model().containsKey("pref");
// 4. Direct Bean Comparison
AppLevelPreference pref1 = ctx1.getBean(AppLevelPreference.class);
AppLevelPreference pref2 = ctx2.getBean(AppLevelPreference.class);
assertThat(pref1).isSameAs(pref2);
}
}
mvn clean test -Dtest="ApplicationScopeIntegrationTest" Output$ mvn clean test -Dtest="ApplicationScopeIntegrationTest" [INFO] Scanning for projects... [WARNING] [WARNING] Some problems were encountered while building the effective model for com.logicbig.example:application-scope-example:war:1.0-SNAPSHOT [WARNING] 'build.plugins.plugin.version' for org.apache.maven.plugins:maven-war-plugin is missing. @ line 43, column 21 [WARNING] [WARNING] It is highly recommended to fix these problems because they threaten the stability of your build. [WARNING] [WARNING] For this reason, future Maven versions might no longer support building such malformed projects. [WARNING] [INFO] [INFO] -----------< com.logicbig.example:application-scope-example >----------- [INFO] Building application-scope-example 1.0-SNAPSHOT [INFO] from pom.xml [INFO] --------------------------------[ war ]--------------------------------- [INFO] [INFO] --- clean:3.2.0:clean (default-clean) @ application-scope-example --- [INFO] Deleting D:\example-projects\spring-mvc\scopes\application-scope-example\target [INFO] [INFO] --- resources:3.3.1:resources (default-resources) @ application-scope-example --- [WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent! [INFO] skip non existing resourceDirectory D:\example-projects\spring-mvc\scopes\application-scope-example\src\main\resources [INFO] [INFO] --- compiler:3.7.0:compile (default-compile) @ application-scope-example --- [INFO] Changes detected - recompiling the module! [INFO] Compiling 6 source files to D:\example-projects\spring-mvc\scopes\application-scope-example\target\classes [INFO] [INFO] --- resources:3.3.1:testResources (default-testResources) @ application-scope-example --- [WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent! [INFO] skip non existing resourceDirectory D:\example-projects\spring-mvc\scopes\application-scope-example\src\test\resources [INFO] [INFO] --- compiler:3.7.0:testCompile (default-testCompile) @ application-scope-example --- [INFO] Changes detected - recompiling the module! [INFO] Compiling 1 source file to D:\example-projects\spring-mvc\scopes\application-scope-example\target\test-classes [INFO] [INFO] --- surefire:3.2.5:test (default-test) @ application-scope-example --- [INFO] Using auto detected provider org.apache.maven.surefire.junit4.JUnit4Provider [WARNING] file.encoding cannot be set as system property, use <argLine>-Dfile.encoding=...</argLine> instead [INFO] [INFO] ------------------------------------------------------- [INFO] T E S T S [INFO] ------------------------------------------------------- [INFO] Running com.logicbig.example.ApplicationScopeIntegrationTest /app1 [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.502 s -- in com.logicbig.example.ApplicationScopeIntegrationTest [INFO] [INFO] Results: [INFO] [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 [INFO] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 3.315 s [INFO] Finished at: 2026-03-28T23:56:54+08:00 [INFO] ------------------------------------------------------------------------ INFO: Refreshing WebApplicationContext for namespace '-servlet': startup date [Sat Mar 28 23:56:54 CST 2026]; root of context hierarchy INFO: Registering annotated classes: [class com.logicbig.example.app1.MyWebConfig] INFO: Refreshing WebApplicationContext for namespace '-servlet': startup date [Sat Mar 28 23:56:54 CST 2026]; root of context hierarchy INFO: Registering annotated classes: [class com.logicbig.example.app2.MyWebConfig2]
Example ProjectDependencies and Technologies Used: - spring-webmvc 7.0.6 (Spring Web MVC)
Version Compatibility: 3.2.3.RELEASE - 7.0.6 Version compatibilities of spring-webmvc with this example: Versions in green have been tested.
- spring-test 7.0.6 (Spring TestContext Framework)
- junit-jupiter-engine 6.0.3 (Module "junit-jupiter-engine" of JUnit)
- hamcrest 3.0 (Core API and libraries of hamcrest matcher framework)
- assertj-core 3.26.3 (Rich and fluent assertions for testing in Java)
- jakarta.servlet-api 6.1.0 (Jakarta Servlet API documentation)
- JDK 25
- Maven 3.9.11
|