Spring Boot Basics
Project Structure
A file-by-file explanation of the Spring Boot project generated by Spring Initializr.
Project Tree
backend/
pom.xml # Maven build definition
mvnw # Maven wrapper script
.mvn/wrapper/
maven-wrapper.properties # Maven wrapper config
src/
main/
java/com/example/taxfiling/
TaxfilingApplication.java # Application entry point
controller/
HealthController.java # REST controller (we added this)
resources/
application.properties # Spring Boot configuration
static/ # Static files (HTML, CSS, JS) served directly
templates/ # Server-side templates (Thymeleaf, etc.)
test/
java/com/example/taxfiling/
TaxfilingApplicationTests.java # Test class
target/ # Build output (gitignored)
pom.xml - The Project Definition
POM stands for “Project Object Model”. It tells Maven everything about your project.
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.0</version>
</parent>
Parent POM: Inherits from spring-boot-starter-parent, which pre-configures a lot of defaults (compiler settings, dependency versions, plugin configs). You don’t need to specify versions for most Spring dependencies because the parent manages them.
<properties>
<java.version>25</java.version>
</properties>
Java version: Tells the Maven compiler plugin to use Java 25.
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
Dependencies: “Starters” are curated dependency bundles:
spring-boot-starter-webpulls in Spring MVC, embedded Tomcat, Jackson (JSON), and everything needed for a REST API. This single line replaces dozens of individual dependency declarations.spring-boot-starter-testpulls in JUnit 5, Mockito, Spring Test, and other testing tools.<scope>test</scope>means it’s only available during testing, not in production.
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
Build plugin: Enables mvn spring-boot:run and packages the app into a fat JAR (a single .jar file containing your code + all dependencies + embedded Tomcat).
mvnw - Maven Wrapper
A shell script that downloads and runs a specific Maven version, so everyone on the team uses the same Maven version regardless of what’s installed locally. Use ./mvnw instead of mvn for reproducible builds. The version is configured in .mvn/wrapper/maven-wrapper.properties.
You already have Maven installed, so you can use either mvn or ./mvnw - they do the same thing.
TaxfilingApplication.java - Entry Point
@SpringBootApplication
public class TaxfilingApplication {
public static void main(String[] args) {
SpringApplication.run(TaxfilingApplication.class, args);
}
}
This is the entire application bootstrap. @SpringBootApplication is a shortcut that combines three annotations:
@Configuration- This class can define beans (objects managed by Spring)@EnableAutoConfiguration- Spring Boot automatically configures things based on your dependencies (e.g., it seesspring-boot-starter-weband sets up Tomcat + Spring MVC)@ComponentScan- Scans the current package and sub-packages for classes annotated with@Controller,@Service,@Repository, etc., and registers them automatically
SpringApplication.run() starts the embedded Tomcat server, initializes the Spring context, and your app is live.
HealthController.java - REST Controller
@RestController
public class HealthController {
@GetMapping("/")
public Map<String, String> hello() {
return Map.of("message", "Tax Filing API is running");
}
}
@RestController=@Controller+@ResponseBody. It tells Spring this class handles HTTP requests and return values are serialized directly to JSON (not rendered as a view template).@GetMapping("/")maps HTTP GET requests to/to this method.- The
Mapreturn value is automatically serialized to{"message":"Tax Filing API is running"}by Jackson (included viastarter-web).
Spring discovered this class automatically because it’s in a sub-package of com.example.taxfiling (where @ComponentScan starts).
application.properties - Configuration
spring.application.name=taxfiling
Currently minimal. This is where you configure database connections, server port, logging, etc. Spring Boot has sensible defaults for everything (e.g., port 8080), so you only override what you need. Can also be written as application.yml.
TaxfilingApplicationTests.java - Smoke Test
@SpringBootTest
class TaxfilingApplicationTests {
@Test
void contextLoads() {
}
}
@SpringBootTest starts the full application context. The contextLoads() test has no assertions - it passes as long as the application starts without errors. This catches configuration mistakes, missing beans, and circular dependencies early.
Run with: mvn test
target/ Directory
Generated by Maven when you build or run. Contains:
classes/- Compiled.classfiles and copied resourcestest-classes/- Compiled test classes*.jar- Packaged application (aftermvn package)
This directory is gitignored. Never commit it.
How It All Fits Together
- You run
mvn spring-boot:run - Maven compiles
src/main/java/**intotarget/classes/ - Maven calls
TaxfilingApplication.main() @SpringBootApplicationtriggers auto-configuration and component scanning- Spring finds
HealthControllervia@ComponentScan, registers it - Embedded Tomcat starts on port 8080
- HTTP GET
/hitsHealthController.hello(), returns JSON
Annotations
Annotations are the primary way you configure Spring Boot. Instead of XML config files, you put @Something on a class/method/field and Spring handles the rest.
How Annotations Work in Spring
Spring Boot starts up, scans your code for annotations, and builds an application context - a container of objects (called beans) that Spring manages. Annotations tell Spring:
- What objects to create
- How to wire them together
- How to map HTTP requests to code
Core Annotations
@SpringBootApplication
The root annotation on your main class. It’s a shortcut for three annotations:
// These two are equivalent:
@SpringBootApplication
public class TaxfilingApplication { }
@Configuration
@EnableAutoConfiguration
@ComponentScan
public class TaxfilingApplication { }
| Annotation | What it does |
|---|---|
@Configuration | Marks this class as a source of bean definitions |
@EnableAutoConfiguration | Spring Boot guesses what you need based on dependencies (e.g., sees starter-web -> sets up Tomcat) |
@ComponentScan | Scans current package + sub-packages for annotated classes |
@Component and Its Specializations
@Component tells Spring: “create an instance of this class and manage it.” Spring provides specialized versions that work the same way but express intent:
@Component <-- generic Spring-managed bean
|-- @Controller <-- handles HTTP requests (returns views)
|-- @RestController <-- handles HTTP requests (returns JSON/data)
|-- @Service <-- business logic layer
+-- @Repository <-- data access layer
They all do the same thing at the core (register a bean), but:
@Repositoryadds automatic exception translation for database errors@RestControlleradds@ResponseBodyso return values become JSON- Using the right one makes your code self-documenting
Example of a typical layered architecture:
@RestController // handles HTTP
public class TaxReturnController {
private final TaxReturnService service;
// ...
}
@Service // business logic
public class TaxReturnService {
private final TaxReturnRepository repo;
// ...
}
@Repository // database access
public interface TaxReturnRepository extends JpaRepository<TaxReturn, Long> {
// ...
}
Dependency Injection Annotations
@Autowired
Tells Spring to inject a dependency. Spring finds a matching bean and passes it in.
@Service
public class TaxReturnService {
@Autowired
private TaxReturnRepository repo; // field injection
}
Constructor injection (preferred - no @Autowired needed if there’s only one constructor):
@Service
public class TaxReturnService {
private final TaxReturnRepository repo;
// Spring automatically injects the repo here
public TaxReturnService(TaxReturnRepository repo) {
this.repo = repo;
}
}
Constructor injection is preferred because:
- Fields can be
final(immutable) - Dependencies are explicit
- Easier to test (just pass mocks to the constructor)
@Bean
Defines a bean manually in a @Configuration class. Use this when you need to configure a third-party object that you can’t annotate with @Component.
@Configuration
public class AppConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate(); // Spring manages this instance
}
}
@Value
Injects values from application.properties:
@Value("${spring.application.name}")
private String appName; // "taxfiling"
Web / REST Annotations
Request Mapping
@RestController
@RequestMapping("/api/returns") // base path for all methods in this class
public class TaxReturnController {
@GetMapping // GET /api/returns
public List<TaxReturn> list() { }
@GetMapping("/{id}") // GET /api/returns/42
public TaxReturn get(@PathVariable Long id) { }
@PostMapping // POST /api/returns
public TaxReturn create(@RequestBody TaxReturn taxReturn) { }
@PutMapping("/{id}") // PUT /api/returns/42
public TaxReturn update(@PathVariable Long id, @RequestBody TaxReturn taxReturn) { }
@DeleteMapping("/{id}") // DELETE /api/returns/42
public void delete(@PathVariable Long id) { }
}
Parameter Annotations
| Annotation | Source | Example |
|---|---|---|
@PathVariable | URL path | /returns/{id} -> @PathVariable Long id |
@RequestParam | Query string | /returns?status=filed -> @RequestParam String status |
@RequestBody | JSON request body | POST payload -> @RequestBody TaxReturn data |
@RequestHeader | HTTP header | Authorization: Bearer ... -> @RequestHeader String authorization |
Response Annotations
@PostMapping
@ResponseStatus(HttpStatus.CREATED) // returns 201 instead of default 200
public TaxReturn create(@RequestBody TaxReturn taxReturn) { }
JPA / Database Annotations
These map Java classes to database tables (you’ll use these when adding PostgreSQL):
@Entity // this class maps to a DB table
@Table(name = "tax_returns") // explicit table name
public class TaxReturn {
@Id // primary key
@GeneratedValue(strategy = GenerationType.IDENTITY) // auto-increment
private Long id;
@Column(nullable = false) // column constraint
private String taxpayerName;
@Enumerated(EnumType.STRING) // store enum as string, not ordinal
private FilingStatus status;
@CreationTimestamp // auto-set on insert
private LocalDateTime createdAt;
}
Validation Annotations
Used with spring-boot-starter-validation to validate request data:
public class TaxReturnRequest {
@NotBlank(message = "Name is required")
private String taxpayerName;
@Min(2020)
private int taxYear;
@Email
private String email;
}
// In controller - @Valid triggers validation
@PostMapping
public TaxReturn create(@Valid @RequestBody TaxReturnRequest request) { }
Testing Annotations
@SpringBootTest // start full application context
class TaxfilingApplicationTests {
@Test // marks a test method
void contextLoads() { }
}
@WebMvcTest(TaxReturnController.class) // test only the web layer
class TaxReturnControllerTest {
@Autowired
private MockMvc mockMvc; // simulates HTTP requests
@MockBean // replace real bean with a mock
private TaxReturnService service;
}
| Annotation | What it starts | Speed |
|---|---|---|
@SpringBootTest | Full application context | Slow |
@WebMvcTest | Only web layer (controllers) | Fast |
@DataJpaTest | Only JPA layer (repositories + DB) | Fast |
Annotation Quick Reference
Application: @SpringBootApplication
Beans: @Component, @Service, @Repository, @Controller, @RestController
Config: @Configuration, @Bean, @Value
Injection: @Autowired (or just constructor injection)
Web: @RequestMapping, @GetMapping, @PostMapping, @PutMapping, @DeleteMapping
Parameters: @PathVariable, @RequestParam, @RequestBody
Database: @Entity, @Table, @Id, @Column, @GeneratedValue
Validation: @Valid, @NotBlank, @Min, @Max, @Email
Testing: @SpringBootTest, @WebMvcTest, @DataJpaTest, @MockBean