Skip to content

CAMEL-22640: Add JSpecify null safety annotations to camel-api#22313

Draft
gnodet wants to merge 4 commits intoapache:mainfrom
gnodet:CAMEL-22640-jspecify-null-annotations
Draft

CAMEL-22640: Add JSpecify null safety annotations to camel-api#22313
gnodet wants to merge 4 commits intoapache:mainfrom
gnodet:CAMEL-22640-jspecify-null-annotations

Conversation

@gnodet
Copy link
Copy Markdown
Contributor

@gnodet gnodet commented Mar 29, 2026

Summary

Add JSpecify null safety annotations (@NullMarked / @Nullable) to the camel-api module, aligning with Spring Framework 7 / Spring Boot 4's adoption of JSpecify as the standard null safety annotation library.

Changes

  • JSpecify dependency: Added org.jspecify:jspecify:1.0.0 to parent/pom.xml (dependencyManagement) and core/camel-api/pom.xml
  • @NullMarked on all packages: Added package-info.java with @NullMarked to all 16 packages in camel-api, making non-null the default
  • @Nullable annotations: Comprehensive audit of all ~200+ Java files, marking parameters, return types, and fields that can be null. Annotations were verified by:
    1. Scanning implementations for return null
    2. Checking callers for != null / == null guards
    3. Checking field initializers for null defaults
  • Runtime enforcement: Added Objects.requireNonNull() checks on non-null reference parameters in all concrete classes, providing fail-fast behavior at API boundaries
  • Fixed false non-null promises: The runtime enforcement revealed several parameters that were missing @Nullable — these were fixed:
    • CamelExchangeException constructors: exchange param made @Nullable (field already nullable)
    • CamelExecutionException: exchange param made @Nullable (called with null from ExchangeHelper)
    • ExchangeTimedOutException: exchange param made @Nullable (consistent with parent)
    • RuntimeExchangeException: exchange param already @Nullable (verified)
    • ValidationException: exchange param already @Nullable (verified)
    • ResolveEndpointFailedException: uri param made @Nullable
    • ExpressionEvaluationException: expression/exchange params made @Nullable
    • Transformer.setName(): scheme/name params made @Nullable
    • Transformer.setFrom()/setTo(): params made @Nullable (fields already nullable)
    • JsseParameters.parsePropertyValue(): value param made @Nullable
    • EventClock.add() / ContextClock.add(): clock param made @Nullable

Approach

  1. Added class-level @NullMarked during review, then moved to package-level at the end
  2. Two-pass audit: Javadoc-based first pass, then implementation-verified second pass
  3. Runtime Objects.requireNonNull() enforcement on concrete classes caught 10+ missed @Nullable annotations

JIRA: CAMEL-22640

Add @NullMarked (JSpecify 1.0.0) to all packages in camel-api,
making non-null the default. Annotate ~200 API interfaces and
classes with @nullable where parameters or return values can
legitimately be null, covering the core API surface including
CamelContext, Exchange, Message, Component, Endpoint, TypeConverter,
and all SPI interfaces.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@gnodet gnodet requested review from davsclaus and oscerd March 29, 2026 21:03
@github-actions
Copy link
Copy Markdown
Contributor

🌟 Thank you for your contribution to the Apache Camel project! 🌟
🤖 CI automation will test this PR automatically.

🐫 Apache Camel Committers, please review the following items:

  • First-time contributors require MANUAL approval for the GitHub Actions to run
  • You can use the command /component-test (camel-)component-name1 (camel-)component-name2.. to request a test from the test bot although they are normally detected and executed by CI.
  • You can label PRs using build-all, build-dependents, skip-tests and test-dependents to fine-tune the checks executed by this PR.
  • Build and test logs are available in the summary page. Only Apache Camel committers have access to the summary.

⚠️ Be careful when sharing logs. Review their contents before sharing them publicly.

gnodet and others added 2 commits March 30, 2026 07:53
Scan actual implementations (not just Javadoc) for return null,
null field values, and caller null-checks. Adds missing @nullable
annotations and moves @NullMarked to package-level only.

Key additions found by implementation scanning:
- Exchange.getPattern() can be null (field uninitialized)
- Message.getExchange() can be null (field starts null)
- Route.getOnException/navigate() can return null
- DelegateProcessor.getProcessor() can be null (LazyStartProducer)
- Various transient exception fields nullable after deserialization
- ConsumerTemplate.receiveBody() without timeout can return null
- CamelContext.getComponent() overloads can return null
- ExtendedCamelContext.getErrorHandlerFactory() can be null
- Multiple SPI methods found nullable through impl analysis

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- CamelExchangeException: accept @nullable exchange (field already nullable)
- CamelExecutionException: accept @nullable exchange (called with null from ExchangeHelper)
- ExchangeTimedOutException: accept @nullable exchange (consistent with parent)
- Transformer: setFrom/setTo accept @nullable (fields already nullable, called with null from reifiers)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@davsclaus
Copy link
Copy Markdown
Contributor

This is interresting and when more and more open source projects adopt this.

Wonder what the situation is at Quarkus ?

…null-annotations

# Conflicts:
#	core/camel-api/src/main/java/org/apache/camel/spi/Transformer.java
#	core/camel-api/src/main/java/org/apache/camel/vault/AwsVaultConfiguration.java
#	core/camel-api/src/main/java/org/apache/camel/vault/AzureVaultConfiguration.java
#	core/camel-api/src/main/java/org/apache/camel/vault/CyberArkVaultConfiguration.java
#	core/camel-api/src/main/java/org/apache/camel/vault/GcpVaultConfiguration.java
#	core/camel-api/src/main/java/org/apache/camel/vault/HashicorpVaultConfiguration.java
#	core/camel-api/src/main/java/org/apache/camel/vault/IBMSecretsManagerVaultConfiguration.java
#	core/camel-api/src/main/java/org/apache/camel/vault/SpringCloudConfigConfiguration.java
#	parent/pom.xml
@gnodet
Copy link
Copy Markdown
Contributor Author

gnodet commented Apr 30, 2026

Yes, Quarkus is moving towards JSpecify as well. Their official account promoted it, and there's a detailed guide on using @NullMarked + NullAway for compile-time null safety in Quarkus REST services. There's also an active discussion in the JSpecify group about handling Quarkus CDI @ConfigProperty fields with NullAway.

It's not baked into the Quarkus framework itself yet (unlike Spring Framework 7 which switched its entire codebase to JSpecify), but the ecosystem is actively adopting it and Quarkus is encouraging its use.

Claude Code on behalf of Guillaume Nodet

@github-actions
Copy link
Copy Markdown
Contributor

🧪 CI tested the following changed modules:

  • core/camel-api
  • parent

ℹ️ Dependent modules were not tested because the total number of affected modules exceeded the threshold (50). Use the test-dependents label to force testing all dependents.

Build reactor — dependencies compiled but only changed modules were tested (2 modules)
  • Camel :: API
  • Camel :: Parent

⚙️ View full build and test results

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants