Course topics

By WebNest Studio

Spring Framework Tutorial

Your First Spring Application

In this lesson you will build a complete plain Spring Framework application (no Spring Boot) and run it. It is small on purpose: one service, one component that depends on it, one configuration class, and a main method that starts the container. But it demonstrates the two ideas that everything else in Spring builds on: the container creates the objects, and it injects dependencies for you.

Every file below was compiled and executed with Spring Framework 7.0.9 on Java 17, and the output shown is the real output.

What we are building

The application prints a greeting. A MessageService knows how to build the greeting text. A GreetingPrinter needs a MessageService and prints the result. A configuration class tells Spring where to look for these classes, and Main starts the container and asks it for the printer. Notice that no class ever writes new MessageService().

Step 1: the service bean

Annotate a class with @Service (or @Component, @Repository, @Controller) and Spring treats it as a bean when it finds it during component scanning. These four annotations behave the same way for the container. They differ only in meaning: Service marks business logic, Repository marks data access, and Controller marks web endpoints.

Step 2: constructor injection

GreetingPrinter declares MessageService in its constructor. When a class has only one constructor, Spring uses it automatically, and no @Autowired annotation is needed. Constructor injection is the recommended style: the field can be final, the object can never exist in a half-built state, and tests can simply pass a fake through the constructor.

Step 3: configuration and component scanning

@Configuration marks a class as a source of bean definitions, and @ComponentScan("com.webnest.first") tells Spring to scan that package for annotated classes. Without the scan, the container would start empty and getBean would fail.

Step 4: start the container

AnnotationConfigApplicationContext is an ApplicationContext that reads annotation-based configuration. Creating it starts the container: it scans, creates the singleton beans, injects dependencies, and calls lifecycle callbacks. Using it in a try-with-resources block closes the context and shuts the container down cleanly.

Reading the output

The first line is the greeting. The second line lists every bean name in the container. You wrote only three classes, but the container holds seven beans: your appConfig, greetingPrinter and messageService, plus four internal infrastructure processors that Spring registers itself to handle configuration classes, @Autowired, and event listeners. Bean names default to the class name with a lowercase first letter.

Run it

From the project root run mvn compile exec:java if you configured the exec plugin, or simply run the Main class from your IDE. Try the experiments in the exercise below to see the container fail in instructive ways.

Examples

MessageService.java and GreetingPrinter.java

Java
package com.webnest.first;

import org.springframework.stereotype.Service;

@Service
public class MessageService {

    public String greet(String name) {
        return "Hello, " + name + "! Welcome to Spring.";
    }
}

// ---------------------------------------------------------

package com.webnest.first;

import org.springframework.stereotype.Component;

@Component
public class GreetingPrinter {

    private final MessageService messageService;

    // Spring sees a single constructor and injects MessageService automatically.
    public GreetingPrinter(MessageService messageService) {
        this.messageService = messageService;
    }

    public void printFor(String name) {
        System.out.println(messageService.greet(name));
    }
}

AppConfig.java and Main.java (run this to start the container)

Java
package com.webnest.first;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("com.webnest.first")
public class AppConfig {
}

// ---------------------------------------------------------

package com.webnest.first;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {

    public static void main(String[] args) {
        try (var context = new AnnotationConfigApplicationContext(AppConfig.class)) {
            GreetingPrinter printer = context.getBean(GreetingPrinter.class);
            printer.printFor("Vansh");
            System.out.println("Beans in the container: "
                    + String.join(", ", context.getBeanDefinitionNames()));
        }
    }
}
Output
Hello, Vansh! Welcome to Spring.
Beans in the container: org.springframework.context.annotation.internalConfigurationAnnotationProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor, org.springframework.context.event.internalEventListenerProcessor, org.springframework.context.event.internalEventListenerFactory, appConfig, greetingPrinter, messageService

Common Mistakes

  • Forgetting @ComponentScan (or scanning the wrong package). Spring then throws NoSuchBeanDefinitionException when you call getBean.
  • Creating the object yourself with new GreetingPrinter(...). That object is not managed by Spring, so nothing is injected into it.
  • Putting the classes in a package outside the scanned base package.
  • Adding two constructors without marking one with @Autowired; Spring cannot tell which one to use.
  • Not closing the context. Use try-with-resources or call close() so destroy callbacks run.

Key Points to Remember

  • The container creates beans from classes found by component scanning and injects their dependencies.
  • A class with a single constructor needs no @Autowired; constructor injection is the preferred style.
  • AnnotationConfigApplicationContext starts a container from annotation-based configuration.
  • Default bean names are the class name with a lowercase first letter (messageService).

Practice the examples

Change an input, predict the result, then compare it with the output. Explain why the result changes.

Use your local JDK or project IDE for these examples. Codelab currently runs Python and HTML/CSS/JavaScript; framework examples may need project dependencies.