Spring Boot: Setup, Auto-Configuration & Project Structure
Bootstrap a production Spring Boot application — understand auto-configuration, the application context, profiles, configuration properties, dependency injection, and structure a real-world multi-layer project.
What Is Spring Boot?
Spring Boot is an opinionated framework that makes it trivial to build production-ready Spring applications. It eliminates XML configuration and manual bean wiring by auto-configuring the application based on what's on the classpath.
Spring Framework (IoC container, AOP, data, security)
↑
Spring Boot (auto-config, embedded server, starter dependencies)
↑
Your application codeBootstrapping with Spring Initializr
The fastest way to start — use start.spring.io:
- Project: Maven or Gradle
- Language: Java
- Spring Boot: 3.x (requires Java 17+)
- Dependencies: Spring Web, Spring Data JPA, PostgreSQL Driver, Spring Security, Validation, Actuator
Or with the CLI:
curl https://start.spring.io/starter.zip \
-d type=maven-project \
-d language=java \
-d bootVersion=3.4.0 \
-d groupId=com.clinic \
-d artifactId=portal-api \
-d name=PortalApi \
-d javaVersion=21 \
-d dependencies=web,data-jpa,postgresql,security,validation,actuator \
-o portal-api.zip && unzip portal-api.zipProject Structure
A well-structured Spring Boot project follows package-by-feature, not package-by-layer:
src/main/java/com/clinic/portal/
├── PortalApiApplication.java ← entry point
├── appointment/
│ ├── Appointment.java ← JPA entity
│ ├── AppointmentRepository.java ← Spring Data repo
│ ├── AppointmentService.java ← business logic
│ ├── AppointmentController.java ← REST controller
│ └── dto/
│ ├── AppointmentRequest.java
│ └── AppointmentResponse.java
├── patient/
│ ├── Patient.java
│ ├── PatientRepository.java
│ ├── PatientService.java
│ └── PatientController.java
├── security/
│ ├── SecurityConfig.java
│ ├── JwtAuthFilter.java
│ └── JwtService.java
└── common/
├── exception/
│ └── GlobalExceptionHandler.java
└── config/
└── OpenApiConfig.javaThe Application Context & Dependency Injection
Spring's core: an IoC container that manages the lifecycle of your objects (called beans).
Declaring Beans
// @Component — generic bean
@Component
public class EmailService { ... }
// @Service — business logic layer (semantic alias for @Component)
@Service
public class AppointmentService { ... }
// @Repository — data access layer (adds exception translation)
@Repository
public class AppointmentRepository { ... }
// @Controller / @RestController — web layer
@RestController
public class AppointmentController { ... }
// @Configuration + @Bean — manual bean declaration
@Configuration
public class AppConfig {
@Bean
public ObjectMapper objectMapper() {
return new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.registerModule(new JavaTimeModule());
}
}Constructor Injection (preferred)
@Service
public class AppointmentService {
private final AppointmentRepository repository;
private final PatientService patientService;
private final EmailService emailService;
// Spring injects dependencies through the constructor
// No @Autowired needed when there's only one constructor (Spring 4.3+)
public AppointmentService(AppointmentRepository repository,
PatientService patientService,
EmailService emailService) {
this.repository = repository;
this.patientService = patientService;
this.emailService = emailService;
}
}Why constructor injection?
finalfields — immutable after construction- Easy to test — just pass mocks to the constructor
- Makes dependencies explicit
Configuration & Profiles
application.yml
# src/main/resources/application.yml
spring:
application:
name: portal-api
datasource:
url: jdbc:postgresql://${DB_HOST:localhost}:5432/${DB_NAME:portal_dev}
username: ${DB_USER:postgres}
password: ${DB_PASSWORD:postgres}
hikari:
maximum-pool-size: 20
connection-timeout: 30000
jpa:
hibernate:
ddl-auto: validate # never "create-drop" in prod
show-sql: false
properties:
hibernate.format_sql: true
security:
jwt:
secret: ${JWT_SECRET}
expiration-ms: 3600000 # 1 hour
server:
port: 8080
shutdown: graceful
error:
include-message: never # don't expose internals
management:
endpoints:
web:
exposure:
include: health, info, metrics, prometheusProfiles
Different configs for dev, test, production:
# application-dev.yml
spring:
jpa:
show-sql: true
hibernate:
ddl-auto: update
datasource:
url: jdbc:postgresql://localhost:5432/portal_dev
logging:
level:
com.clinic: DEBUG# application-prod.yml
spring:
jpa:
show-sql: false
hibernate:
ddl-auto: validateActivate profiles:
# Environment variable (recommended in production)
SPRING_PROFILES_ACTIVE=prod java -jar portal-api.jar
# Or as JVM arg
java -Dspring.profiles.active=prod -jar portal-api.jar@ConfigurationProperties — Type-Safe Config
@ConfigurationProperties(prefix = "spring.security.jwt")
public record JwtProperties(String secret, long expirationMs) {}
// Register it
@SpringBootApplication
@EnableConfigurationProperties(JwtProperties.class)
public class PortalApiApplication {
public static void main(String[] args) {
SpringApplication.run(PortalApiApplication.class, args);
}
}
// Inject and use
@Service
public class JwtService {
private final JwtProperties jwtProps;
public JwtService(JwtProperties jwtProps) {
this.jwtProps = jwtProps;
}
public String generateToken(String subject) {
return Jwts.builder()
.subject(subject)
.expiration(new Date(System.currentTimeMillis() + jwtProps.expirationMs()))
.signWith(Keys.hmacShaKeyFor(jwtProps.secret().getBytes()))
.compact();
}
}Auto-Configuration Explained
When you add spring-boot-starter-data-jpa to your classpath, Spring Boot automatically:
- Detects
javax.persistence.Entityon your classpath - Configures a
DataSourcefromspring.datasource.*properties - Creates a
JPA EntityManagerFactory - Creates a
PlatformTransactionManager - Enables Spring Data repository scanning
You can see what was configured:
java -jar portal-api.jar --debug 2>&1 | grep "AUTO-CONFIGURATION REPORT"Or set logging.level.org.springframework.boot.autoconfigure=DEBUG.
To exclude an auto-configuration:
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })Bean Scopes
| Scope | Lifetime | Default? |
|-------|----------|---------|
| singleton | One instance per application context | Yes |
| prototype | New instance every injection | No |
| request | One per HTTP request (web only) | No |
| session | One per HTTP session (web only) | No |
99% of your beans should be singleton. Use prototype only for stateful beans that can't be shared.
ApplicationRunner — Startup Logic
@Component
public class StartupRunner implements ApplicationRunner {
private final AppointmentRepository repo;
public StartupRunner(AppointmentRepository repo) {
this.repo = repo;
}
@Override
public void run(ApplicationArguments args) throws Exception {
long count = repo.count();
log.info("Application started. {} appointments in database.", count);
}
}Key Annotations Reference
| Annotation | Purpose |
|-----------|---------|
| @SpringBootApplication | @Configuration + @EnableAutoConfiguration + @ComponentScan |
| @Component | Generic bean |
| @Service | Business logic bean |
| @Repository | Data access bean |
| @RestController | Controller bean that writes JSON to the response body |
| @RequestMapping | Map URL path to a controller or method |
| @Autowired | Inject a bean (prefer constructor injection) |
| @Value("${prop}") | Inject a single config value |
| @ConfigurationProperties | Bind a config prefix to a typed object |
| @Profile("dev") | Only create this bean in the "dev" profile |
| @Conditional | Conditionally create a bean based on any logic |
Enjoyed this article?
Explore the Backend Systems learning path for more.
Found this helpful?
Leave a comment
Have a question, correction, or just found this helpful? Leave a note below.