Tuesday, 24 June 2025

Spring Boot Actuator

 Spring Boot Actuator is a powerful module that provides production-ready features to help you monitor and manage your Spring Boot application.


📌 What is Actuator?

Spring Boot Actuator exposes several built-in endpoints that give insights into your application:

  • Health

  • Metrics

  • Environment

  • Beans

  • Thread dumps

  • HTTP trace

  • Custom endpoints


✅ Step-by-Step Setup

🧱 1. Add Dependency

Maven:

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

Gradle:

groovy
implementation 'org.springframework.boot:spring-boot-starter-actuator'

⚙️ 2. Configuration (application.yml or .properties)

application.yml (Recommended)

yaml
management: endpoints: web: exposure: include: "*" # Expose all endpoints endpoint: health: show-details: always # Show health details (disk, db, etc.) info: env: enabled: true

If using .properties

properties
management.endpoints.web.exposure.include=* management.endpoint.health.show-details=always

🚀 3. Access Endpoints

Once Actuator is enabled and running (on default port 8080), you can hit these endpoints:

EndpointDescriptionURL Example
/actuatorList all enabled endpointshttp://localhost:8080/actuator
/actuator/healthApplication health statushttp://localhost:8080/actuator/health
/actuator/metricsPerformance metrics (memory, etc.)http://localhost:8080/actuator/metrics
/actuator/beansAll Spring beanshttp://localhost:8080/actuator/beans
/actuator/envAll environment variableshttp://localhost:8080/actuator/env
/actuator/infoProject info (from info.*)http://localhost:8080/actuator/info
/actuator/loggersLog levels (change at runtime)http://localhost:8080/actuator/loggers

🔐 4. Security with Spring Security (Optional)

By default, Actuator endpoints are secured if Spring Security is on the classpath.

To allow all endpoints without auth (not for production):

yaml
management: endpoints: web: exposure: include: "*" spring: security: user: name: admin password: admin123

Login using admin/admin123 in browser when accessing endpoints.


➕ Custom Info Endpoint

Add this to application.yml:

yaml
info: app: name: MyApp description: Custom Spring Boot app version: 1.0.0

Then check: http://localhost:8080/actuator/info


➕ Custom Health Indicator

java
import org.springframework.boot.actuate.health.*; import org.springframework.stereotype.Component; @Component public class CustomHealthIndicator implements HealthIndicator { @Override public Health health() { boolean appIsUp = true; // your custom logic if (appIsUp) { return Health.up().withDetail("custom", "Everything is OK!").build(); } return Health.down().withDetail("custom", "Something is wrong").build(); } }

🛠 Expose Actuator on a Different Port (Optional)

yaml
management: server: port: 9090

Now actuator is accessible at http://localhost:9090/actuator.


📦 Use Cases

Use CaseBenefit
Kubernetes liveness/readinessCheck app status automatically
Prometheus/GrafanaIntegrate metrics and monitoring
DebuggingView beans, thread dumps, env vars
DevOps FriendlyHealth, metrics, restart, shutdown

📌 Summary

FeatureDefault StatusConfigurable
/actuator/health✅ EnabledYes
/actuator/info✅ EnabledYes
Others like /metrics, /beans❌ Disabled by default✅ Enable via config

✅ 2. Expose Custom Metrics using Micrometer

📌 Custom Metric Example

java
import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.MeterRegistry; import org.springframework.stereotype.Component; @Component public class OrderMetrics { private final Counter orderCounter; public OrderMetrics(MeterRegistry registry) { this.orderCounter = registry.counter("orders.created.count"); } public void incrementOrderCount() { orderCounter.increment(); } }

Use orderMetrics.incrementOrderCount() inside your service when an order is created.


✅ 3. Write a Custom Actuator Endpoint

📦 Step-by-Step Custom Endpoint

a. Add a dependency for custom actuator endpoint support (optional)

No extra dependency needed unless you're using older Spring Boot (<2.0)


b. Custom Endpoint Code

import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.stereotype.Component; import java.util.Map; @Component @Endpoint(id = "customstatus") public class CustomStatusEndpoint { @ReadOperation public Map<String, String> customHealth() { return Map.of("app-status", "running", "uptime", "5h 23m"); } }

This exposes: http://localhost:8080/actuator/customstatus

You must explicitly expose it:

yaml
management: endpoints: web: exposure: include: health, prometheus, customstatus

✅ Bonus: Custom HealthIndicator

java
import org.springframework.boot.actuate.health.*; import org.springframework.stereotype.Component; @Component public class CustomHealthIndicator implements HealthIndicator { @Override public Health health() { boolean appHealthy = true; // your logic if (appHealthy) { return Health.up().withDetail("custom-check", "All good").build(); } else { return Health.down().withDetail("custom-check", "Something failed").build(); } } }

It becomes part of /actuator/health.

---------------------

2.Spring boot performance issue

What: Spring Boot performance issues typically arise due to excessive memory usage, slow startup times, inefficient database access, blocking I/O, or suboptimal configurations.

Common Causes & Solutions:

Problem AreaDescriptionSolution
Slow StartupCaused by loading too many beans, unnecessary classpath scanningUse @SpringBootApplication(exclude = {...}), reduce @ComponentScan scope
Memory LeaksFrom poorly managed resources or cachesUse tools like VisualVM or Eclipse MAT to identify leaks
Blocking CallsUsing blocking I/O in a reactive contextSwitch to reactive WebFlux or use @Async where appropriate
Large Fat JARsSlows down class loadingTrim dependencies, remove unused starters
Database LatencyInefficient queries, N+1 problemsUse pagination, optimize queries, enable query logging
Inefficient LoggingToo much logging (INFO/DEBUG)Reduce log level in application.properties

Performance Tuning Tips:
  • Enable actuator: /actuator/metrics, /actuator/heapdump

  • Use caching (@Cacheable)

  • Profile with JProfiler or Flight Recorder

  • Minimize autowiring unused beans

✅ Best Way to Handle "Lack of Records" Efficiently (High Performance)


🔹 1. Check Record Count Before Fetching Data

Don't load full records if there are none.

java
@GetMapping("/employees") public ResponseEntity<?> getAllEmployees() { long count = employeeRepository.count(); if (count == 0) { return ResponseEntity.status(HttpStatus.NO_CONTENT).body("No records found."); } List<Employee> list = employeeRepository.findAll(); return ResponseEntity.ok(list); }

⏱️ count() is faster because it just returns a number and can use an index.


🔹 2. Use Pagination Always

Never use findAll() directly in real applications — it loads the entire table, even if it’s huge.

java
@GetMapping("/employees") public ResponseEntity<?> getEmployees(@RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size) { Pageable pageable = PageRequest.of(page, size); Page<Employee> employeePage = employeeRepository.findAll(pageable); if (employeePage.isEmpty()) { return ResponseEntity.status(HttpStatus.NO_CONTENT).body("No records found."); } return ResponseEntity.ok(employeePage.getContent()); }

✅ Pagination avoids full memory load and keeps your service fast.


🔹 3. Use DTO Instead of Full Entity (avoid LazyInitialization)

Heavy JPA entities with relations (@OneToMany, etc.) can cause slow loading. Use DTO to fetch only required fields.

java
public interface EmployeeDTO { String getName(); String getEmail(); }
java
@Query("SELECT e.name AS name, e.email AS email FROM Employee e") List<EmployeeDTO> findAllBasicInfo();

🔹 4. Add Database Indexing

Ensure your database columns (especially those used in filters, joins, and sort) are indexed properly.

sql
CREATE INDEX idx_employee_status ON employee(status);

🔹 5. Use @Transactional with Read-Only Flag

This avoids locking and ensures better performance.

@Transactional(readOnly = true) public List<Employee> getAllEmployees() { return employeeRepository.findAll(); }

🔹 6. Use Caching for Static or Rarely Changing Data

For data that doesn’t change often, cache it using @Cacheable.

@Cacheable("employees") public List<Employee> getCachedEmployees() { return employeeRepository.findAll(); }

✅ Final Optimized Controller Example

@RestController @RequestMapping("/api/employees") public class EmployeeController { @Autowired private EmployeeRepository employeeRepository; @GetMapping public ResponseEntity<?> getEmployees(@RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size) { Pageable pageable = PageRequest.of(page, size); Page<Employee> resultPage = employeeRepository.findAll(pageable); if (resultPage.isEmpty()) { return ResponseEntity.status(HttpStatus.NO_CONTENT) .body("No employee records found."); } return ResponseEntity.ok(resultPage.getContent()); } }

✅ Summary: Best Practices Table

PracticeBenefit
count() checkQuick exit when no data
Pagination (Pageable)Avoids full table load
DTO projectionsAvoids heavy entity fetch
@Transactional(readOnly)Faster and safer DB access
Database indexingBoosts query performance
CachingSkips DB for repeat requests