Spring Boot Basics in java

Spring Boot makes it easy to create stand-alone, production-grade Spring-based applications that you can "just run".

1. Spring Boot Project Structure

Typical Project Structure

my-spring-boot-app/
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/example/app/
│   │   │       ├── MySpringBootApp.java
│   │   │       ├── controller/
│   │   │       ├── service/
│   │   │       ├── repository/
│   │   │       └── model/
│   │   └── resources/
│   │       ├── application.properties
│   │       ├── static/
│   │       └── templates/
│   └── test/
│       └── java/
├── pom.xml (Maven)
└── build.gradle (Gradle)

2. Creating a Spring Boot Application

Main Application Class

package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MySpringBootApp {
public static void main(String[] args) {
SpringApplication.run(MySpringBootApp.class, args);
}
}

Using SpringApplicationBuilder for Advanced Configuration

package com.example.demo;
import org.springframework.boot.Banner;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
@SpringBootApplication
public class MySpringBootApp {
public static void main(String[] args) {
new SpringApplicationBuilder(MySpringBootApp.class)
.bannerMode(Banner.Mode.OFF) // Turn off banner
.logStartupInfo(false)       // Disable startup info logging
.run(args);
}
}

3. Spring Boot Starters

Common Starters in pom.xml (Maven)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.0</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>my-spring-boot-app</artifactId>
<version>1.0.0</version>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<!-- Web Starter for REST APIs -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Data JPA Starter for Database -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- Security Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Test Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- DevTools for development -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- Database Driver -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

4. Spring Boot Configuration

application.properties

# Server Configuration
server.port=8080
server.servlet.context-path=/api
# Database Configuration
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=root
spring.datasource.password=password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# JPA/Hibernate Configuration
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
spring.jpa.properties.hibernate.format_sql=true
# Logging Configuration
logging.level.com.example.demo=DEBUG
logging.level.org.springframework.web=INFO
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} - %msg%n
# Custom Application Properties
app.name=My Spring Boot App
app.version=1.0.0
app.description=Demo Spring Boot Application

application.yml (Alternative)

server:
port: 8080
servlet:
context-path: /api
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb
username: root
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update
show-sql: true
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL8Dialect
format_sql: true
logging:
level:
com.example.demo: DEBUG
org.springframework.web: INFO
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} - %msg%n"
app:
name: "My Spring Boot App"
version: "1.0.0"
description: "Demo Spring Boot Application"

Configuration Properties Class

package com.example.demo.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "app")
public class AppConfig {
private String name;
private String version;
private String description;
// Getters and Setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getVersion() { return version; }
public void setVersion(String version) { this.version = version; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
@Override
public String toString() {
return String.format("AppConfig{name='%s', version='%s', description='%s'}", 
name, version, description);
}
}

5. Spring Boot Controllers

Basic REST Controller

package com.example.demo.controller;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/users")
public class UserController {
@GetMapping
public String getAllUsers() {
return "Getting all users";
}
@GetMapping("/{id}")
public String getUserById(@PathVariable Long id) {
return "Getting user with id: " + id;
}
@PostMapping
public String createUser(@RequestBody User user) {
return "Creating user: " + user;
}
@PutMapping("/{id}")
public String updateUser(@PathVariable Long id, @RequestBody User user) {
return "Updating user with id: " + id + " with data: " + user;
}
@DeleteMapping("/{id}")
public String deleteUser(@PathVariable Long id) {
return "Deleting user with id: " + id;
}
}
// DTO (Data Transfer Object)
class User {
private Long id;
private String name;
private String email;
// Constructors
public User() {}
public User(Long id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
// Getters and Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
@Override
public String toString() {
return String.format("User{id=%d, name='%s', email='%s'}", id, name, email);
}
}

Advanced Controller with ResponseEntity

package com.example.demo.controller;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
@RestController
@RequestMapping("/api/products")
public class ProductController {
private List<Product> products = new ArrayList<>();
private Long nextId = 1L;
public ProductController() {
// Initialize with some sample data
products.add(new Product(nextId++, "Laptop", 999.99));
products.add(new Product(nextId++, "Mouse", 29.99));
}
@GetMapping
public ResponseEntity<List<Product>> getAllProducts() {
return ResponseEntity.ok(products);
}
@GetMapping("/{id}")
public ResponseEntity<?> getProductById(@PathVariable Long id) {
Optional<Product> product = products.stream()
.filter(p -> p.getId().equals(id))
.findFirst();
if (product.isPresent()) {
return ResponseEntity.ok(product.get());
} else {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(new ErrorResponse("Product not found with id: " + id));
}
}
@PostMapping
public ResponseEntity<Product> createProduct(@RequestBody Product product) {
product.setId(nextId++);
products.add(product);
return ResponseEntity.status(HttpStatus.CREATED).body(product);
}
@PutMapping("/{id}")
public ResponseEntity<?> updateProduct(@PathVariable Long id, @RequestBody Product updatedProduct) {
for (int i = 0; i < products.size(); i++) {
if (products.get(i).getId().equals(id)) {
updatedProduct.setId(id);
products.set(i, updatedProduct);
return ResponseEntity.ok(updatedProduct);
}
}
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(new ErrorResponse("Product not found with id: " + id));
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteProduct(@PathVariable Long id) {
boolean removed = products.removeIf(product -> product.getId().equals(id));
if (removed) {
return ResponseEntity.noContent().build();
} else {
return ResponseEntity.notFound().build();
}
}
// Query parameters example
@GetMapping("/search")
public ResponseEntity<List<Product>> searchProducts(
@RequestParam(required = false) String name,
@RequestParam(required = false) Double minPrice,
@RequestParam(required = false) Double maxPrice) {
List<Product> result = products;
if (name != null) {
result = result.stream()
.filter(p -> p.getName().toLowerCase().contains(name.toLowerCase()))
.toList();
}
if (minPrice != null) {
result = result.stream()
.filter(p -> p.getPrice() >= minPrice)
.toList();
}
if (maxPrice != null) {
result = result.stream()
.filter(p -> p.getPrice() <= maxPrice)
.toList();
}
return ResponseEntity.ok(result);
}
}
class Product {
private Long id;
private String name;
private Double price;
// Constructors, getters, setters
public Product() {}
public Product(Long id, String name, Double price) {
this.id = id;
this.name = name;
this.price = price;
}
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Double getPrice() { return price; }
public void setPrice(Double price) { this.price = price; }
@Override
public String toString() {
return String.format("Product{id=%d, name='%s', price=%.2f}", id, name, price);
}
}
class ErrorResponse {
private String message;
private Date timestamp;
public ErrorResponse(String message) {
this.message = message;
this.timestamp = new Date();
}
// Getters and setters
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
public Date getTimestamp() { return timestamp; }
public void setTimestamp(Date timestamp) { this.timestamp = timestamp; }
}

6. Spring Boot Services

Service Layer with Business Logic

package com.example.demo.service;
import org.springframework.stereotype.Service;
import java.util.*;
@Service
public class UserService {
private Map<Long, User> users = new HashMap<>();
private Long nextId = 1L;
public List<User> getAllUsers() {
return new ArrayList<>(users.values());
}
public Optional<User> getUserById(Long id) {
return Optional.ofNullable(users.get(id));
}
public User createUser(User user) {
user.setId(nextId++);
users.put(user.getId(), user);
return user;
}
public Optional<User> updateUser(Long id, User updatedUser) {
if (users.containsKey(id)) {
updatedUser.setId(id);
users.put(id, updatedUser);
return Optional.of(updatedUser);
}
return Optional.empty();
}
public boolean deleteUser(Long id) {
return users.remove(id) != null;
}
public List<User> searchUsers(String name, String email) {
return users.values().stream()
.filter(user -> 
(name == null || user.getName().toLowerCase().contains(name.toLowerCase())) &&
(email == null || user.getEmail().toLowerCase().contains(email.toLowerCase())))
.toList();
}
}
// User entity
class User {
private Long id;
private String name;
private String email;
private Date createdAt;
public User() {
this.createdAt = new Date();
}
public User(String name, String email) {
this();
this.name = name;
this.email = email;
}
// Getters and setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public Date getCreatedAt() { return createdAt; }
public void setCreatedAt(Date createdAt) { this.createdAt = createdAt; }
@Override
public String toString() {
return String.format("User{id=%d, name='%s', email='%s', createdAt=%s}", 
id, name, email, createdAt);
}
}

7. Spring Data JPA Integration

Entity and Repository

package com.example.demo.entity;
import jakarta.persistence.*;
import java.time.LocalDateTime;
@Entity
@Table(name = "employees")
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "first_name", nullable = false, length = 50)
private String firstName;
@Column(name = "last_name", nullable = false, length = 50)
private String lastName;
@Column(unique = true, nullable = false)
private String email;
private Double salary;
@Column(name = "department")
private String department;
@Column(name = "created_at")
private LocalDateTime createdAt;
@Column(name = "updated_at")
private LocalDateTime updatedAt;
// Pre-persist and pre-update callbacks
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
updatedAt = LocalDateTime.now();
}
@PreUpdate
protected void onUpdate() {
updatedAt = LocalDateTime.now();
}
// Constructors
public Employee() {}
public Employee(String firstName, String lastName, String email, Double salary, String department) {
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
this.salary = salary;
this.department = department;
}
// Getters and setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getFirstName() { return firstName; }
public void setFirstName(String firstName) { this.firstName = firstName; }
public String getLastName() { return lastName; }
public void setLastName(String lastName) { this.lastName = lastName; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public Double getSalary() { return salary; }
public void setSalary(Double salary) { this.salary = salary; }
public String getDepartment() { return department; }
public void setDepartment(String department) { this.department = department; }
public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
public LocalDateTime getUpdatedAt() { return updatedAt; }
public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
@Override
public String toString() {
return String.format("Employee{id=%d, firstName='%s', lastName='%s', email='%s', department='%s'}", 
id, firstName, lastName, email, department);
}
}

Spring Data JPA Repository

package com.example.demo.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import com.example.demo.entity.Employee;
import java.util.List;
import java.util.Optional;
@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
// Custom query methods
Optional<Employee> findByEmail(String email);
List<Employee> findByDepartment(String department);
List<Employee> findBySalaryGreaterThan(Double salary);
List<Employee> findByDepartmentAndSalaryGreaterThan(String department, Double salary);
// Custom query with @Query
@Query("SELECT e FROM Employee e WHERE e.firstName LIKE %:name% OR e.lastName LIKE %:name%")
List<Employee> findByNameContaining(@Param("name") String name);
@Query("SELECT e FROM Employee e WHERE e.salary BETWEEN :minSalary AND :maxSalary")
List<Employee> findBySalaryRange(@Param("minSalary") Double minSalary, 
@Param("maxSalary") Double maxSalary);
@Query("SELECT e.department, AVG(e.salary) FROM Employee e GROUP BY e.department")
List<Object[]> findAverageSalaryByDepartment();
// Native SQL query
@Query(value = "SELECT * FROM employees e WHERE e.department = :dept ORDER BY e.salary DESC LIMIT 5", 
nativeQuery = true)
List<Employee> findTop5ByDepartment(@Param("dept") String department);
}

Service using Repository

package com.example.demo.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.example.demo.entity.Employee;
import com.example.demo.repository.EmployeeRepository;
import java.util.List;
import java.util.Optional;
@Service
public class EmployeeService {
@Autowired
private EmployeeRepository employeeRepository;
public List<Employee> getAllEmployees() {
return employeeRepository.findAll();
}
public Optional<Employee> getEmployeeById(Long id) {
return employeeRepository.findById(id);
}
public Optional<Employee> getEmployeeByEmail(String email) {
return employeeRepository.findByEmail(email);
}
public Employee createEmployee(Employee employee) {
return employeeRepository.save(employee);
}
public Optional<Employee> updateEmployee(Long id, Employee employeeDetails) {
return employeeRepository.findById(id)
.map(employee -> {
employee.setFirstName(employeeDetails.getFirstName());
employee.setLastName(employeeDetails.getLastName());
employee.setEmail(employeeDetails.getEmail());
employee.setSalary(employeeDetails.getSalary());
employee.setDepartment(employeeDetails.getDepartment());
return employeeRepository.save(employee);
});
}
public boolean deleteEmployee(Long id) {
if (employeeRepository.existsById(id)) {
employeeRepository.deleteById(id);
return true;
}
return false;
}
public List<Employee> getEmployeesByDepartment(String department) {
return employeeRepository.findByDepartment(department);
}
public List<Employee> getHighSalaryEmployees(Double minSalary) {
return employeeRepository.findBySalaryGreaterThan(minSalary);
}
public List<Employee> searchEmployeesByName(String name) {
return employeeRepository.findByNameContaining(name);
}
public List<Object[]> getAverageSalaryByDepartment() {
return employeeRepository.findAverageSalaryByDepartment();
}
}

8. Exception Handling

Global Exception Handler

package com.example.demo.exception;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import java.util.Date;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorDetails> handleResourceNotFoundException(
ResourceNotFoundException ex, WebRequest request) {
ErrorDetails errorDetails = new ErrorDetails(
new Date(),
ex.getMessage(),
request.getDescription(false));
return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorDetails> handleGlobalException(
Exception ex, WebRequest request) {
ErrorDetails errorDetails = new ErrorDetails(
new Date(),
ex.getMessage(),
request.getDescription(false));
return new ResponseEntity<>(errorDetails, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
// Custom Exception
class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String message) {
super(message);
}
}
// Error Details DTO
class ErrorDetails {
private Date timestamp;
private String message;
private String details;
public ErrorDetails(Date timestamp, String message, String details) {
this.timestamp = timestamp;
this.message = message;
this.details = details;
}
// Getters and setters
public Date getTimestamp() { return timestamp; }
public void setTimestamp(Date timestamp) { this.timestamp = timestamp; }
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
public String getDetails() { return details; }
public void setDetails(String details) { this.details = details; }
}

9. Testing in Spring Boot

Unit Tests and Integration Tests

package com.example.demo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import com.example.demo.service.EmployeeService;
import com.example.demo.entity.Employee;
import com.example.demo.repository.EmployeeRepository;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@SpringBootTest
class MySpringBootAppTests {
@Test
void contextLoads() {
// Test that the application context loads successfully
}
}
// Service Unit Test
package com.example.demo.service;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import com.example.demo.entity.Employee;
import com.example.demo.repository.EmployeeRepository;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class EmployeeServiceTest {
@Mock
private EmployeeRepository employeeRepository;
@InjectMocks
private EmployeeService employeeService;
@Test
void testGetAllEmployees() {
// Given
Employee emp1 = new Employee("John", "Doe", "[email protected]", 50000.0, "IT");
Employee emp2 = new Employee("Jane", "Smith", "[email protected]", 60000.0, "HR");
List<Employee> employees = Arrays.asList(emp1, emp2);
when(employeeRepository.findAll()).thenReturn(employees);
// When
List<Employee> result = employeeService.getAllEmployees();
// Then
assertEquals(2, result.size());
verify(employeeRepository, times(1)).findAll();
}
@Test
void testGetEmployeeById() {
// Given
Long employeeId = 1L;
Employee employee = new Employee("John", "Doe", "[email protected]", 50000.0, "IT");
employee.setId(employeeId);
when(employeeRepository.findById(employeeId)).thenReturn(Optional.of(employee));
// When
Optional<Employee> result = employeeService.getEmployeeById(employeeId);
// Then
assertTrue(result.isPresent());
assertEquals("John", result.get().getFirstName());
verify(employeeRepository, times(1)).findById(employeeId);
}
@Test
void testCreateEmployee() {
// Given
Employee employee = new Employee("John", "Doe", "[email protected]", 50000.0, "IT");
Employee savedEmployee = new Employee("John", "Doe", "[email protected]", 50000.0, "IT");
savedEmployee.setId(1L);
when(employeeRepository.save(any(Employee.class))).thenReturn(savedEmployee);
// When
Employee result = employeeService.createEmployee(employee);
// Then
assertNotNull(result.getId());
assertEquals("John", result.getFirstName());
verify(employeeRepository, times(1)).save(employee);
}
}

10. Spring Boot Actuator

Adding Actuator Dependencies and Configuration

<!-- In pom.xml -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

application.properties for Actuator

# Actuator Configuration
management.endpoints.web.exposure.include=health,info,metrics,beans,env
management.endpoint.health.show-details=always
management.endpoint.health.show-components=always
# Custom Info endpoint
info.app.name=My Spring Boot App
info.app.version=1.0.0
info.app.description=Demo Application with Actuator

Custom Health Check

package com.example.demo.health;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
@Component
public class CustomHealthIndicator implements HealthIndicator {
@Override
public Health health() {
// Custom health check logic
boolean isHealthy = checkSystemHealth();
if (isHealthy) {
return Health.up()
.withDetail("database", "Connected")
.withDetail("diskSpace", "Sufficient")
.build();
} else {
return Health.down()
.withDetail("database", "Disconnected")
.withDetail("error", "System not healthy")
.build();
}
}
private boolean checkSystemHealth() {
// Implement actual health check logic
return true; // Simplified for example
}
}

Key Spring Boot Features Summary

  1. Auto-configuration - Automatic setup based on dependencies
  2. Starter Dependencies - Pre-configured dependency sets
  3. Embedded Servers - Tomcat, Jetty, or Undertow
  4. Production-ready Features - Actuator for monitoring
  5. Spring Boot CLI - Command-line interface
  6. Externalized Configuration - Properties/YAML files
  7. Profiles - Environment-specific configurations

Spring Boot dramatically reduces the configuration overhead and lets you focus on business logic while providing enterprise-ready features out of the box.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper