⚡ Spring Boot Development Series - Part 5: Advanced Features and Best Practices
Explore testing, configuration, and deployment to wrap up your Spring Boot mastery.
Spring Boot Development Series - Part 5: Advanced Features and Best Practices
Welcome to the final part! By now, you know how to create a basic Spring Boot app. In this part, we’ll add professional features to make your app production-ready.
What we’ll cover:
- Testing to ensure code quality
- Configuration management for different environments
- Monitoring tools to keep your app healthy
- Deployment options to share your app
- Best practices for maintainable code
Let’s make your Spring Boot app enterprise-ready!
Testing Your Application
Testing ensures your code works correctly and helps catch bugs early. Spring Boot integrates easily with testing tools.
Why Test?
- Catch bugs early: Tests find issues before users do.
- Refactor safely: Change code without breaking features.
- Document behavior: Tests show what your code should do.
Unit Testing with JUnit 5
Spring Boot includes testing tools automatically. In your pom.xml, you should see:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> This includes JUnit 5, Mockito, and other testing libraries.
Simple Test Example
Let’s test our TaskService. First, create a test class in src/test/java:
@SpringBootTest // Loads the full Spring context class TaskServiceTest { @Autowired // Inject the real service private TaskService taskService; @MockBean // Replace with a fake version private TaskRepository taskRepository; @Test void createTask_shouldReturnSavedTask() { // Arrange: Set up test data Task mockTask = new Task("Learn Spring", "Study Spring Boot"); when(taskRepository.save(any(Task.class))).thenReturn(mockTask); // Act: Call the method Task result = taskService.createTask("Learn Spring", "Study Spring Boot"); // Assert: Check the result assertNotNull(result); // Should not be null assertEquals("Learn Spring", result.getTitle()); // Title matches verify(taskRepository).save(any(Task.class)); // Repository was called } } How it works:
@SpringBootTeststarts Spring Boot for testing.@MockBeancreates a fakeTaskRepositoryso we don’t need a real database.when(...).thenReturn(...)tells the fake what to return.assertEqualschecks if the result is what we expect.verifyensures the repository’s save method was called.
Run tests with: mvn test
Integration Test Example
Test the full API without mocking:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class TaskControllerIntegrationTest { @Autowired private TestRestTemplate restTemplate; @Test void getAllTasks_shouldReturnTasks() { // This would test the actual API endpoint ResponseEntity<Task[]> response = restTemplate.getForEntity("/tasks", Task[].class); assertEquals(HttpStatus.OK, response.getStatusCode()); assertNotNull(response.getBody()); } } This tests the real controller, service, and repository together.
Configuration Management
Instead of hardcoding values in code, store them in configuration files. This lets you change settings without rebuilding the app.
Basic Properties File
Create src/main/resources/application.properties:
# Server settings server.port=8081 server.servlet.context-path=/api # App information app.name=My Task App app.version=1.0.0 app.admin.email=admin@myapp.com # Database settings spring.datasource.url=jdbc:h2:mem:testdb spring.datasource.username=sa spring.datasource.password= Using Properties in Code
Method 1: @Value Annotation
Inject single values directly:
@Service public class EmailService { @Value("${app.admin.email}") private String adminEmail; @Value("${app.name}") private String appName; public void sendWelcomeEmail(String userEmail) { String subject = "Welcome to " + appName; String body = "Contact admin at: " + adminEmail; // Send email logic here } } When to use @Value:
- Single properties
- Simple values
Method 2: @ConfigurationProperties (Better for Groups)
For related settings, create a config class:
@Configuration @ConfigurationProperties(prefix = "app") public class AppConfig { private String name; private String version; private Admin admin; // Constructor, getters, setters public static class Admin { private String email; private String phone; // getters and setters } // Methods to use the config public String getFullAppInfo() { return name + " v" + version; } } In your service:
@Service public class AppService { private final AppConfig config; public AppService(AppConfig config) { this.config = config; } public void printInfo() { System.out.println("App: " + config.getFullAppInfo()); System.out.println("Admin: " + config.getAdmin().getEmail()); } } Why @ConfigurationProperties is better:
- Type safety (no String conversion needed)
- Validation support
- IDE autocomplete
- Groups related settings
Environment-Specific Configuration
Use profiles for different environments:
application-dev.properties:
server.port=8080 app.database.url=jdbc:h2:mem:devdb logging.level.com.example=DEBUG application-prod.properties:
server.port=8080 app.database.url=jdbc:postgresql://prod-db:5432/myapp logging.level.com.example=INFO Activate a profile in application.properties:
spring.profiles.active=dev Or when running:
java -jar app.jar --spring.profiles.active=prod Example: In dev, use H2 database (in-memory). In prod, use PostgreSQL.
Monitoring with Spring Boot Actuator
Actuator provides built-in tools to monitor your app’s health, performance, and more. It’s like a dashboard for your application.
Adding Actuator
Add to pom.xml:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> Enabling Endpoints
In application.properties, expose the endpoints:
management.endpoints.web.exposure.include=health,info,metrics management.endpoint.health.show-details=always Useful Endpoints
- Health Check:
http://localhost:8080/actuator/health- Shows if app is “UP” or “DOWN”
- Includes database, disk space status
- Application Info:
http://localhost:8080/actuator/info- Custom info you can add
- Metrics:
http://localhost:8080/actuator/metrics- JVM memory, HTTP requests, etc.
Example: Visit /actuator/health in your browser. If everything is working, you’ll see:
{ "status": "UP", "components": { "db": { "status": "UP", "details": { "database": "H2", "validationQuery": "isValid()" } }, "diskSpace": { "status": "UP", "details": { "total": 500068036608, "free": 200000000000, "threshold": 10485760 } } } } This helps you know if your app and its dependencies are healthy.
Deployment Strategies
Once your app is ready, deploy it so others can use it.
1. Building a JAR File
Spring Boot packages everything into a single JAR file.
Run this command:
mvn clean package This creates target/your-app-0.0.1-SNAPSHOT.jar.
What happens:
- Compiles your code
- Runs tests
- Packages classes, dependencies, and resources into one JAR
2. Running the JAR
To run locally or on a server:
java -jar target/your-app-0.0.1-SNAPSHOT.jar Your app starts on the configured port (default 8080).
3. Containerization with Docker
Docker packages your app with its environment.
Create a Dockerfile in your project root:
# Use Java 17 FROM eclipse-temurin:17-jdk-alpine # Set working directory WORKDIR /app # Copy the JAR file ARG JAR_FILE=target/*.jar COPY ${JAR_FILE} app.jar # Expose port 8080 EXPOSE 8080 # Run the app ENTRYPOINT ["java", "-jar", "/app.jar"] Step-by-step:
- Build the JAR:
mvn clean package - Build Docker image:
docker build -t my-task-app . - Run container:
docker run -p 8080:8080 my-task-app
What this does:
- Uses a lightweight Java image
- Copies your JAR into the container
- Exposes port 8080
- Runs your Spring Boot app
Example: After running, visit http://localhost:8080 to use your API.
Best Practices Recap
Follow these guidelines for better Spring Boot apps:
- Layered Architecture
- Controller: Handles HTTP requests/responses
- Service: Business logic
- Repository: Data access
Example:
@RestController public class TaskController { private final TaskService service; public TaskController(TaskService service) { this.service = service; } } - Constructor Injection
@Service public class TaskService { private final TaskRepository repo; public TaskService(TaskRepository repo) { // Preferred this.repo = repo; } }Why? Easier to test, immutable dependencies.
- External Configuration
- Use
application.propertiesfor settings - Profiles for different environments
- Use
- Write Tests
- Unit tests for logic
- Integration tests for full flow
- Proper HTTP Status Codes
@PostMapping public ResponseEntity<Task> createTask(@RequestBody Task task) { Task saved = service.create(task); return ResponseEntity.status(HttpStatus.CREATED).body(saved); } - Monitor with Actuator
- Health checks
- Metrics
- Custom endpoints
- Security Basics
- Validate input
- Use HTTPS in production
- Handle errors gracefully
- Logging
@Service public class TaskService { private static final Logger log = LoggerFactory.getLogger(TaskService.class); public Task createTask(String title, String desc) { log.info("Creating task: {}", title); // ... logic } } - Version Your API
@RestController @RequestMapping("/api/v1/tasks") public class TaskController { ... } - Handle Exceptions
@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(TaskNotFoundException.class) public ResponseEntity<String> handleNotFound(TaskNotFoundException e) { return ResponseEntity.status(404).body(e.getMessage()); } }
Series Conclusion
Congratulations! You’ve learned the fundamentals of Spring Boot:
- Maven process for building and dependency management
- Core annotations for wiring your application
- Dependency Injection (especially constructor injection)
- REST API development with Spring MVC
- Production-ready features like testing, configuration, and monitoring
What’s Next?
To continue your journey:
- Learn about Spring Data JPA for real database integration
- Explore Spring Security for authentication and authorization
- Master Microservices architecture with Spring Cloud
- Build a frontend using React, Angular, or Thymeleaf
Keep coding and building! Spring Boot is a vast ecosystem, and you now have the solid foundation to explore it further.
This concludes our Spring Boot Development Series. Thank you for following along!