5.9 KiB
Style Guide
This project follows the Google Java Style Guide.
Code Formatting
Auto-formatting
The build will fail if source code is not formatted according to Google Java Style.
Run the following command to reformat all files:
./gradlew spotlessApply
For IntelliJ users, an .editorconfig file is provided that IntelliJ will automatically use to
adjust code formatting settings. However, it does not support all required rules, so you may still
need to run ./gradlew spotlessApply periodically.
Static imports
Consider statically importing the following commonly used methods and constants:
- Test methods
io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.*org.assertj.core.api.Assertions.*org.mockito.Mockito.*org.mockito.ArgumentMatchers.*
- Utility methods
io.opentelemetry.api.common.AttributeKey.*java.util.Arrays- asList, streamjava.util.Collections- singleton*, empty*, unmodifiable*, synchronized*, checked*java.util.Objects- requireNonNulljava.util.function.Function- identityjava.util.stream.Collectors.*
- Utility constants
java.util.Locale.*java.util.concurrent.TimeUnit.*java.util.logging.Level.*java.nio.charset.StandardCharsets.*
- OpenTelemetry semantic convention constants
- All constants under
io.opentelemetry.semconv.**, except forio.opentelemetry.semconv.SchemaUrls.*constants.
- All constants under
Class organization
Prefer this order:
- Static fields (final before non-final)
- Instance fields (final before non-final)
- Constructors
- Methods
- Nested classes
Method ordering: Place calling methods above the methods they call. For example, place private methods below the non-private methods that use them.
Static utility classes: Place the private constructor (used to prevent instantiation) after all methods.
Java Language Conventions
Visibility modifiers
Follow the principle of minimal necessary visibility. Use the most restrictive access modifier that still allows the code to function correctly.
Internal packages
Classes in .internal packages are not considered public API and may change without notice. These
packages contain implementation details that should not be used by external consumers.
- Use
.internalpackages for implementation classes that need to be public within the module but should not be used externally - Try to avoid referencing
.internalclasses from other modules
final keyword usage
Public non-internal non-test classes should be declared final where possible.
Methods should only be declared final if they are in public non-internal non-test non-final classes.
Fields should be declared final where possible.
Method parameters and local variables should never be declared final.
@Nullable annotation usage
Note: This section is aspirational and may not reflect the current codebase.
Annotate all parameters and fields that can be null with @Nullable (specifically
javax.annotation.Nullable, which is included by the otel.java-conventions Gradle plugin as a
compileOnly dependency).
@NonNull is unnecessary as it is the default.
Defensive programming: Public APIs should still check for null parameters even if not
annotated with @Nullable. Internal APIs do not need these checks.
Optional usage
Following the reasoning from
Writing a Java library with better experience (slide 12),
java.util.Optional usage is kept to a minimum.
Guidelines:
Optionalshouldn't appear in public API signatures- Avoid
Optionalon the hot path (instrumentation code), unless the instrumented library uses it
Tooling conventions
AssertJ
Prefer AssertJ assertions over JUnit assertions (assertEquals, assertTrue, etc.) for better error messages.
JUnit
Test classes and test methods should generally be package-protected (no explicit visibility
modifier) rather than public. This follows the principle of minimal necessary visibility and is
sufficient for JUnit to discover and execute tests.
Gradle
- Use Kotlin instead of Groovy for build scripts
- Plugin versions should be specified in
settings.gradle.kts, not in individual modules - All modules should use
plugins { id("otel.java-conventions") } - Set module names with
otelJava.moduleName.set("io.opentelemetry.contrib.mymodule")
Configuration
- Use
otel.prefix for all configuration property keys - Read configuration via the
ConfigPropertiesinterface - Provide sensible defaults and document all options
- Validate configuration early with clear error messages
Performance
Avoid allocations on the hot path (instrumentation code) whenever possible. This includes Iterator
allocations from collections; note that for (SomeType t : plainJavaArray) does not allocate an
iterator object.
Non-allocating Stream API usage on the hot path is acceptable but may not fit the surrounding code
style; this is a judgment call. Some Stream APIs make efficient allocation difficult (e.g.,
collect with pre-sized sink data structures involves convoluted Supplier code, or lambdas passed
to forEach may be capturing/allocating lambdas).
Documentation
Component README files
- Include a component owners section in each module's README
- Document configuration options with examples
Deprecation and breaking changes
Breaking changes are allowed in unstable modules (published with -alpha version suffix).
- Mark APIs with
@Deprecatedand a removal timeline (there must be at least one release with the API marked as deprecated before removing it) - Document the replacement in Javadoc with
@deprecatedtag - Note the migration path for breaking changes under a "Migration notes" section of CHANGELOG.md (create this section at the top of the Unreleased section if not already present)