The OtelSpan.error(Throwable throwable) method in micrometer-tracing-bridge-otel causes a NullPointerException when the throwable's message is null, which violates the OpenTelemetry API contract for non-nullable parameters.
The current implementation in io.micrometer.tracing.otel.bridge.OtelSpan (lines 172-177):
@Override
public Span error(Throwable throwable) {
this.delegate.recordException(throwable);
this.delegate.setStatus(StatusCode.ERROR, throwable.getMessage());
return this;
}
Calls throwable.getMessage() which can return null, but passes it to setStatus(StatusCode, String description) where the description parameter is specified as non-null in the OpenTelemetry API signature:
Span setStatus(StatusCode statusCode, String description);
Impact:
This causes runtime failures when:
- Working with exceptions that have null messages (e.g.,
ReadTimeoutException, custom exceptions without messages)
- Using Kotlin implementations of the OpenTelemetry
Span interface, which enforce null-safety at runtime
- Custom span wrappers that delegate to multiple spans
Example Failure:
java.lang.NullPointerException: Parameter specified as non-null is null: method com.test.observability.CompositeSpan.setStatus, parameter description
at com.test.observability.CompositeSpan.setStatus(CompositeSpan.kt)
at io.micrometer.tracing.otel.bridge.OtelSpan.error(OtelSpan.java:175)
at io.micrometer.tracing.handler.PropagatingSenderTracingObservationHandler.onError(PropagatingSenderTracingObservationHandler.java:94)
This occurs when handling exceptions like io.netty.handler.timeout.ReadTimeoutException, which has a null message.
Proposed Fix:
Use a fallback value when throwable.getMessage() is null:
@Override
public Span error(Throwable throwable) {
this.delegate.recordException(throwable);
this.delegate.setStatus(StatusCode.ERROR,
throwable.getMessage() != null ? throwable.getMessage() : throwable.getClass().getName());
return this;
}
Alternative Solutions:
- Use empty string as fallback:
""
- Use exception class simple name:
throwable.getClass().getSimpleName()
Using the exception class name seems most informative for observability purposes.
Reproducibility:
Create any exception without a message and pass it to the error() method when using a Kotlin-based Span implementation or any implementation that validates non-null parameters.
The
OtelSpan.error(Throwable throwable)method inmicrometer-tracing-bridge-otelcauses aNullPointerExceptionwhen the throwable's message is null, which violates the OpenTelemetry API contract for non-nullable parameters.The current implementation in
io.micrometer.tracing.otel.bridge.OtelSpan(lines 172-177):Calls
throwable.getMessage()which can returnnull, but passes it tosetStatus(StatusCode, String description)where thedescriptionparameter is specified as non-null in the OpenTelemetry API signature:Impact:
This causes runtime failures when:
ReadTimeoutException, custom exceptions without messages)Spaninterface, which enforce null-safety at runtimeExample Failure:
This occurs when handling exceptions like
io.netty.handler.timeout.ReadTimeoutException, which has a null message.Proposed Fix:
Use a fallback value when
throwable.getMessage()is null:Alternative Solutions:
""throwable.getClass().getSimpleName()Using the exception class name seems most informative for observability purposes.
Reproducibility:
Create any exception without a message and pass it to the
error()method when using a Kotlin-based Span implementation or any implementation that validates non-null parameters.