MyServiceBusDocs

.NET and Java optimization · Work in progress

Native deployment status for .NET and Java.

The current preview can compile an application using MyServiceBus as a .NET NativeAOT or GraalVM Native Image executable. Generated consumer catalogs make that path practical in C# and Java—and can reduce startup work even when the application continues to use its managed runtime.

Current support boundary

Native execution is an MVP proof of concept, not a blanket compatibility promise. The generated mediator path is tested as a native executable in both runtimes. Serialization, interface-message proxies, third-party libraries, and custom extensions can still require application-specific AOT configuration.

Decide whether AOT fits the application

AOT is a deployment tradeoff, not an automatic performance upgrade. It is worth considering when startup time, cold starts, memory use, or shipping a native executable matter. A managed runtime can be the better choice for a long-running service where JIT optimization, dynamic behavior, simpler diagnostics, and mature framework integration matter more.

Measure the complete application under a representative workload before choosing. AOT analyzes a closed program, so code reached through reflection may need generated metadata or explicit preservation. That responsibility includes the serializer, dependency-injection container, and other application dependencies—not only MyServiceBus.

Native AOT is easiest when the application is designed for it from the beginning. Retrofitting a large system with many libraries can require substantial work when those dependencies rely on dynamic code or do not provide AOT metadata. In that situation, keeping the managed runtime and using generated registration may deliver the useful startup optimization without forcing the entire dependency graph onto the native path.

Optimize within your application platform

This is not guidance for choosing .NET over Java or Java over .NET. MyServiceBus keeps the messaging model familiar across both so a team can use the platform appropriate to each service. The useful comparison is local to that platform: which registration, serializer, and runtime mode gives an existing C# or Java application the best result for the metric it actually values?

C# and .NET

.NET NativeAOT publishes IL as a platform-specific, self-contained executable without a runtime JIT. Evaluate it for startup, cold-start consistency, memory footprint, deployment shape, and environments where runtime code generation is unavailable. NativeAOT also requires trimming and whole-application analysis; dynamic assembly loading and runtime code generation are not available. Start with Microsoft's Native AOT deployment overview and trimming guidance for libraries .

Generated consumer registration and source-generated JSON metadata can both improve the managed CoreCLR path; neither requires the application to publish with NativeAOT.

Compare reflection-capable defaults, generated registration, and source-generated JSON on CoreCLR, then compare the same statically described application under NativeAOT. CoreCLR may lead warmed throughput while NativeAOT may lead startup or memory. The matrix keeps those dimensions separate instead of naming one universal winner.

Java and GraalVM

Java applications may compile the ordinary MyServiceBus API with GraalVM Native Image. Native Image applies closed-world reachability analysis and emits an executable instead of starting on HotSpot. Evaluate startup, memory, and deployment benefits against warmed JIT throughput. Reflection, resources, proxies, serializers, and frameworks can require explicit metadata. See GraalVM's official Native Image reference and reachability metadata guide .

Compare reflection-based and generated registration on the JVM, application-configured Jackson, and the same statically described application as a Native Image executable. A warmed JVM and Native Image optimize for different outcomes, so startup, resident memory, peak throughput, allocation, image size, and build time belong in distinct columns.

Start with generated registration

Source generation and native compilation are separate choices. A generated catalog removes runtime consumer discovery and reflective registration work from startup. For many applications, adopting that optimization while keeping JIT compilation may be the right stopping point.

Use generated registration in .NET

Add the analyzer package to the application project, import the generated namespace, and register the catalog once during bus configuration.

Shell
Loading code…
C#
Loading code…

The generator discovers accessible IConsumer<TMessage> implementations and attributed consumer methods in the application. Consumer-method behavior is documented separately because it is useful in managed applications too.

Write the equivalent catalog by hand

Source generation improves the experience; it is not a separate runtime model. A hand-written catalog has the same dispatch and AOT characteristics.

C#
Loading code…

Provide generated JSON metadata separately

Consumer catalogs and JSON contracts solve different problems. The MyServiceBus generator owns consumer registration; the application owns its System.Text.Json policy. Pass the application context to the built-in serializer factory without exposing JSON metadata in the portable serializer contract.

C#
Loading code…

The same metadata handles application payloads on send and receive. MyServiceBus processes its envelope fields directly, so the context only needs application message contracts—not every closed envelope type. Omitting options keeps the reflection-capable managed default.

.NET JSON modeManaged runtimeNativeAOTComparison row
Default reflective metadataSupportedNot the strict pathEnvelope and Raw baseline
Application source-generated metadataSupportedVerified by native smokeEnvelope and Raw generated

Use generated registration in Java

Add the optional JSR 269 annotation processor, then register its catalog. It does not require a particular application framework or classpath scanning. Applications can write the equivalent explicit registrations when annotation processing is not desired.

Gradle
Loading code…
Java
Loading code…

Keep application dependencies AOT-compatible

In Java, ServiceCollection.createAot() selects the included factory-only container and avoids Guice activation. An application may instead adapt its existing container, provided that container and its registrations support the selected native-image path.

Serializer configuration remains an application choice. .NET applications may opt into source-generated System.Text.Json metadata through their serializer, and both clients accept explicit serializer factories. Check every custom filter, transport, serializer, and DI adapter used by the application rather than assuming the generated consumer catalog makes the entire dependency graph AOT-safe.

What the MVP proves

CI publishes and runs a .NET NativeAOT application and builds and runs a GraalVM Native Image application. Each uses a generated consumer method with the mediator and verifies message, context, cancellation, and service binding. This establishes application-level native execution for the supported path; it does not yet certify every transport and extension.

The .NET smoke also performs a source-generated JSON envelope round trip with no reflection resolver fallback before dispatch. That verifies the built-in envelope's application-metadata boundary in the published native executable.

Preparing for .NET 11 Runtime Async

.NET 11 introduces preview runtime-managed async with NativeAOT support. An experimental CI smoke now compiles an interface consumer and a generated named-union method consumer with the feature enabled, suspends them at an actual await, and resumes both union cases through generated dispatch. The same gate verifies that the union carrier does not enter topology and rebuilds the core abstractions and mediator runtime for .NET 11 with Runtime Async. The core NuGet packages include an experimental net11.0 asset alongside their stable net10.0 asset. A future stable target should compare async performance with the feature enabled and disabled. Runtime Async is an optimization, not a requirement for compiling today's async state machines with NativeAOT. See the official .NET 11 Runtime Async guidance .

Current measurements

C# and Java results are shown separately because their runtimes, native compilers, harnesses, and historical test conditions differ. Each table compares modes within one platform; neither is a C#-versus-Java ranking. The measurements exclude broker I/O and are proof-of-concept evidence, not production capacity estimates.

C# and .NET

Workload.NET 10 CoreCLR.NET NativeAOTObservation
Generated method invocation165.4M ops/s157.4M ops/sNative measured 5% lower

Typed registration cost on .NET 10

ModeTimeAllocationObservation
Reflection2.282 µs7.21 KBBaseline
Explicit typed1.626 µs6.68 KB29% lower time, 7% lower allocation

JSON metadata comparison

The committed .NET harness compares reflective and source-generated metadata independently for envelope/raw serialization and deserialization, with allocation diagnostics. Run the full benchmark before publishing numbers:

Shell
Loading code…

Warm throughput and allocation are only two columns. Cold startup, first-message cost, retained memory, and NativeAOT published size need process-level measurements and will be recorded separately rather than inferred from warmed microbenchmarks.

Java and GraalVM

WorkloadGraalVM 21 JITGraalVM Native ImageObservation
Generated mediator dispatch136,724 ops/s83,922 ops/sNative measured 39% lower

Typed registration cost on Java 21

ReflectionExplicit typedObservation
0.718 µs0.704 µs2% lower; confidence intervals overlap

The Java native result predates the factory-only container. Both platform paths still need representative startup, memory, binary-size, serialization, and broker-backed measurements.

Preview boundary

Prefer concrete message contracts on the current .NET native path. Anonymous interface message proxies are not yet covered. Treat broker transports, custom serialization, custom filters, and third-party DI integrations as application-specific validation points.

NextCompare runtime and language-tooling support
Platform parity →