Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.
Get started with mocking and improve your application tests using our Mockito guide:
Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.
Get started with understanding multi-threaded applications with our Java Concurrency guide:
Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:
Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.
But these can also be overused and fall into some common pitfalls.
To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:
Get started with Spring and Spring Boot, through the Learn Spring course:
>> LEARN SPRINGExplore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:
Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.
I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.
You can explore the course here:
Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.
Get started with Spring Data JPA through the guided reference course:
Refactor Java code safely β and automatically β with OpenRewrite.
Refactoring big codebases by hand is slow, risky, and easy to put off. Thatβs where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.
Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions β one for newcomers and one for experienced users. Youβll see how recipes work, how to apply them across projects, and how to modernize code with confidence.
Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.
1. Overview
In this article, weβll take a look at Spring Cloud Sleuth and see how we can use it for tracing in Spring Boot. It adds useful, extra information to our logs and makes it easier to debug actions by adding unique identifiers to them. These actions are called traces in Sleuth terminology. They can consist of several steps, called spans.
For example, a trace can be a GET request that is querying data from our application. When our application processes the request, it can be split up into smaller steps: user authorization, executing the database query, transforming the response. Each of these steps is a unique span belonging to the same trace.
In some cases, we might want to get the ID of the current trace or span. For instance, we could send these to the development team when there is an incident. Then they can use this to debug and fix the problem.
2. Application Setup
Letβs start by creating a Spring Boot project and adding the spring-cloud-starter-sleuth dependency:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
<version>3.1.0</version>
</dependency>
This starter dependency integrates well with Spring Boot and provides the necessary configuration to start using Spring Cloud Sleuth.
However, there is one extra step we can take. Letβs set the name of our application in the application.properties file, this way weβll see this in the logs along with the trace and span IDs:
spring.application.name=Baeldung Sleuth Tutorial
Now we need an entry point to our application. Letβs create a REST controller with a single GET endpoint:
@RestController
public class SleuthTraceIdController {
@GetMapping("/traceid")
public String getSleuthTraceId() {
return "Hello from Sleuth";
}
}
Letβs visit our API endpoint at http://localhost:8080/traceid. We should see βHello from Sleuthβ in the response.
3. Logging
Letβs add a log statement to the getSleuthTraceId method. First, we need a Logger for our class. Then we can log the message:
private static final Logger logger = LoggerFactory.getLogger(SleuthTraceIdController.class);
@GetMapping("/traceid")
public String getSleuthTraceId() {
logger.info("Hello with Sleuth");
return "Hello from Sleuth";
}
Letβs call our API endpoint again and check the logs. We should find something similar to this:
INFO [Baeldung Sleuth Tutorial,e48f140a63bb9fbb,e48f140a63bb9fbb] 9208 --- [nio-8080-exec-1] c.b.s.traceid.SleuthTraceIdController : Hello with Sleuth
Notice that the application name is inside the brackets at the beginning. These brackets are added by Sleuth. They represent the application name, trace ID, and span ID.
4. Current Trace and Span
We can use the above example to debug issues in our application, but it might be challenging to determine what caused it and which trace to follow. Thatβs why weβll get the current trace programmatically, and then we can use it for any further investigations.
In our implementation, weβll simplify this use case, and weβll just log the trace IDs to the console.
Firstly, we need to get an instance of a Tracer object. Letβs inject it into our controller and get the current span:
@Autowired
private Tracer tracer;
@GetMapping("/traceid")
public String getSleuthTraceId() {
logger.info("Hello with Sleuth");
Span span = tracer.currentSpan();
return "Hello from Sleuth";
}
Notice that the currentSpan method can return null if there is no active span at the moment. Therefore we have to perform an additional check to see if we can proceed and use this Span object without getting NullPointerException. Letβs implement this check and log the current trace and span IDs:
Span span = tracer.currentSpan();
if (span != null) {
logger.info("Trace ID {}", span.context().traceIdString());
logger.info("Span ID {}", span.context().spanIdString());
}
Letβs run the application and look for these messages when we visit our API endpoint. They should contain the same IDs as the brackets added by Sleuth.
5. Trace and Span ID as Decimal Numbers
There is another way to get the span ID with the spanId method instead of spanIdString. The difference between them is that the latter one returns the hexadecimal representation of the value while the first one returns a decimal number. Letβs compare them in action and log the decimal value as well:
Span span = tracer.currentSpan();
if (span != null) {
logger.info("Span ID hex {}", span.context().spanIdString());
logger.info("Span ID decimal {}", span.context().spanId());
}
The two values represent the same number, and the output should look similar to this:
INFO [Baeldung Sleuth Tutorial,0de46b6fcbc8da83,0de46b6fcbc8da83] 8648 --- [nio-8080-exec-3] c.b.s.traceid.SleuthTraceIdController : Span ID hex 0de46b6fcbc8da83
INFO [Baeldung Sleuth Tutorial,0de46b6fcbc8da83,0de46b6fcbc8da83] 8648 --- [nio-8080-exec-3] c.b.s.traceid.SleuthTraceIdController : Span ID decimal 1001043145087572611
Similarly, this applies to trace IDs as well. Instead of traceIdString, we can use the traceId method. traceIdString returns a hexadecimal value while traceId returns a decimal value:
logger.info("Trace ID hex {}", span.context().traceIdString());
logger.info("Trace ID decimal {}", span.context().traceId());
The output is very similar to the previous one. It contains the trace ID in hexadecimal first then in decimal:
INFO [Baeldung Sleuth Tutorial,34ec0b8ac9d65e91,34ec0b8ac9d65e91] 7384 --- [nio-8080-exec-1] c.b.s.traceid.SleuthTraceIdController : Trace ID hex 34ec0b8ac9d65e91
INFO [Baeldung Sleuth Tutorial,34ec0b8ac9d65e91,34ec0b8ac9d65e91] 7384 --- [nio-8080-exec-1] c.b.s.traceid.SleuthTraceIdController : Trace ID decimal 3813435675195629201
6. Conclusion
In this article, we discussed how Spring Cloud Sleuth could help debug and trace events in Spring Boot. First, we used the Tracer object to reference the current span and the TraceContext. After that, we were able to get the ID of the current trace and span. In addition, we saw how different methods return the ID in different number systems.
