I posted the content of this article also on StackOverflow.

To understand the difference between application scope and singleton scope you need to understand what ServletContext and ApplicationContext are.

A ServletContext is shared between all servlets living on the same servlet container (e.g. Tomcat). This is a Java EE class (it belongs to the package javax.servlet). Beans annotated with @ApplicationScope are bounded to the ServletContext.

An ApplicationContext represents a Spring IoC container, so it is a Spring-specific class (it belongs to the package org.springframework.context). Singleton scoped beans are bounded to the ApplicationContext.

You can have multiple IoC container in the same servlet container, so you can have multiple singleton beans of the same type but only one application scoped bean of each type.

I provide an example using Spring Boot and Spring MVC.

We need to introduce also the DispatcherServlet. The DispatcherServlet receives HTTP requests and forwards them to the appropriate controller. An ApplicationContext is associated to each DispatcherServlet. It is possible to configure Spring to create multiple DispatcherServlets and associate a different ApplicationContext to each of them.

Notice that in a standard configuration there is only one DispatcherServlet, so it is usually not possible to distinguish a singleton scoped bean from an application scoped bean. I would also like to point out that I see no practical use for the custom configuration I’m going to show you, other than to provide a concrete example of the difference between these two scopes.

The following diagram shows the relations between all the elements of the example:

Let’s look at the code. Please, pay attention to the package names!

In the package com.example.demo.beans we create the 2 beans. A random number is generated during beans initialization, so that we can distinguish them.

@ApplicationScope
@Component
public class MyApplicationScopeBean {

    private final double id = Math.random();

    public double getId() {
        return id;
    }
}
// Singleton is the default scope
@Component
public class MySingletonBean {

    private final double id = Math.random();

    public double getId() {
        return id;
    }
}

The startup class is put in com.example.demo:

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        // The first ApplicationContext is instantiated here
        // (it is also returned by the method).
        SpringApplication.run(DemoApplication.class, args);
    }

    /**
     * A new DispatcherServlet is instantiated and registered.
     */
    @Bean
    public ServletRegistrationBean mvc2() {

        // A new ApplicationContext is created, using a dedicated Configuration class.
        AnnotationConfigWebApplicationContext secondApplicationContext = new AnnotationConfigWebApplicationContext();
        secondApplicationContext.register(Mvc2Config.class);

        DispatcherServlet dispatcherServlet = new DispatcherServlet();
        dispatcherServlet.setApplicationContext(secondApplicationContext);

        DispatcherServletRegistrationBean servletRegistrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/second/*");
        servletRegistrationBean.setName("second");

        return servletRegistrationBean;
    }
}

In the same package we put also the controller that will be bounded to the default DispatcherServlet generated by Spring Boot:

@RestController
public class FirstController {

    @Autowired
    private MyApplicationScopeBean applicationScopeBean;

    @Autowired
    private MySingletonBean singletonBean;

    @GetMapping("/first/demo")
    public String output() {
        return "applicationScope=" + applicationScopeBean.getId() + ", singleton=" + singletonBean.getId();
    }
}

In the package com.example.demo.mvc2 we put the second configuration class:

@Configuration
@ComponentScan(basePackages = {"com.example.demo.mvc2", "com.example.demo.beans"})
@EnableWebMvc
public class Mvc2Config {
}

And the controller bounded to the second DispatcherServlet:

@RestController
public class SecondController {

    @Autowired
    private MyApplicationScopeBean applicationScopeBean;

    @Autowired
    private MySingletonBean singletonBean;

    @GetMapping("/demo")
    public String output() {
        return "applicationScope=" + applicationScopeBean.getId() + ", singleton=" + singletonBean.getId();
    }
}

If you run the code you can see that the id of the application scoped bean is the same in both the controllers, while the id of the singleton beans changes:

http://localhost:8080/first/demo

applicationScope=0.8685117272969953, singleton=0.23475401462261436

http://localhost:8080/second/demo

applicationScope=0.8685117272969953, singleton=0.8390865330171554