← Spring Boot®: applications, contracts and operations
01 / 10 · 60 MIN

Application context and dependencies

Interpret how the context finds, creates and selects dependencies; distinguish auto-configuration, explicit composition and shared state.

An explicit baseline for reading the application

This path uses Spring Boot 4.1.1, Spring Framework 7.0.9 and Java 25. System requirements admit Java 17 through 26 for this Boot version; choosing Java 25 retains the connection to the language path. Questions assess code interpretation and decisions. Examples require neither tool installation nor program execution to answer.

Spring Boot coordinates configuration and integrations over Spring. @SpringBootApplication combines application configuration, auto-configuration and component scanning. It does not itself define every external-service contract or activate every profile. An application that started still needs to satisfy requirements of the operations it offers.

Classpath and scanning are different boundaries

A class can exist on the classpath without being registered as a component. Implicit scanning starts in the entry class’s package and traverses its scope. If the entry class is in com.acme.orders and a service in com.acme.billing, that sibling package is not included merely because it belongs to the same repository.

Choose an intentional application root package or import specific module configurations. Placing the entry class in the default package can cause classes from all jars to be read and make scope unpredictable. A missing-bean correction should explain why the definition becomes included without hiding the required dependency.

The constructor presents the composition contract

A service requiring Inventory should receive that dependency in its constructor. For a managed component with one constructor, @Autowired is not required. A final field can retain the reference selected at construction. This does not make the referenced object immutable or guarantee that external calls never fail.

Explicit composition helps explain missing startup dependencies and permits supplying a controlled alternative in review or testing. Calling new directly creates a Java instance; it does not automatically turn it into the managed bean of the same class. When comparing behaviors, identify which instance was used and where its dependency came from.

Select a candidate without changing every consumer

When several beans match a requested type, the application needs a coherent selection rule. A qualifier on the parameter narrows that set for the specific consumer. In the example, bulk identifies the intended candidate; the other Channel remains available to consumers with different requirements.

@Primary can define a general preference when appropriate. Changing that preference to fix one consumer can change existing unqualified consumers. Before changing global configuration, distinguish a new local requirement from a global decision about the default adapter. Qualifiers still operate within type-compatible candidates.

Auto-configuration is a conditional decision

Auto-configuration considers available dependencies and other conditions, including existing bean definitions. An embedded database may be configured when appropriate dependencies exist and no other DataSource was supplied. Adding a custom DataSource can make that part of default configuration back off. This does not mean disabling all auto-configuration.

Use the conditions report to explain what was applied and why. --debug provides that report during ordinary diagnosis, but producing the report does not repair composition. Customize through documented extension points; an auto-configuration class’s public name can support exclusion without making its internal methods a stable API.

Shared instances do not isolate requests

Singleton scope is per context and bean definition. Consumers of the same bean share the instance. This does not promise one instance of the class across all JVM applications or supply automatic call serialization. A field retaining the current tenant may be replaced by another request before the first decision finishes.

Keep each request’s identity and data in arguments or local variables when they need not be shared. If state genuinely is shared, define its concurrency and validity policy. Making a field volatile may improve visibility but does not make a multi-operation business sequence exclusive.

Guided reading of composition

The example presents three files under the same root package. DispatchApplication defines two Channel factories. DispatchPlanner is discovered as a service and requests bulk in its single constructor. Calling describe with an identifier valid for its consumer produces a description using archive; no external delivery occurs in that method.

Trace what would change if the service moved to a sibling package outside scanning, or if the qualifier were removed without another selection rule. These are different problems: one lacks consumer discovery, the other lacks dependency selection. The final field stores only Channel. Each call’s identifier remains local to the method.

// Separate files in package com.acme.dispatch.
// Spring Boot 4.1.1; Spring Framework 7.0.9; Java 25.
// DispatchApplication.java
package com.acme.dispatch;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class DispatchApplication {
  @Bean("interactive")
  Channel interactive() { return () -> "screen"; }
  @Bean("bulk")
  Channel bulk() { return () -> "archive"; }
  public static void main(String[] args) {
    SpringApplication.run(DispatchApplication.class, args);
  }
}

// Channel.java
package com.acme.dispatch;
public interface Channel { String destination(); }

// DispatchPlanner.java
package com.acme.dispatch;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
@Service
public class DispatchPlanner {
  private final Channel channel;
  public DispatchPlanner(@Qualifier("bulk") Channel channel) {
    this.channel = channel;
  }
  public String describe(String id) {
    return channel.destination() + ":" + id;
  }
}
IN PRACTICE

Read the three files as separate units. Both factories produce Channel; the qualified parameter selects bulk. describe only constructs text and does not deliver data to a real destination. The service stores no request state.

Common pitfalls

Confusing classpath with scan scope; resolving a missing dependency with null; changing a global Primary for a local requirement; retaining request identity in mutable singleton state.

Related topics: Configuration, binding and profiles · Test scope and integration evidence · Packaging, delivery and operational acceptance

Take this idea with you

The context manages defined objects under conditions and selection rules. Make scope, dependencies and state ownership explicit.

Create account

Reference: System requirements · Spring Boot 4.1.1 / Spring Framework 7.0.9 / Java 25