Deviations

genhttp-kt keeps the API and the behavior of GenHTTP. Where the JVM, Kotlin or a library requires a different solution, the port says so - this page lists every such place, grouped by the area you will meet it in. The complete log is DEVIATIONS.md in the repository.

The one you are most likely to trip over: Kotlin does not allow annotations or default values on lambda parameters. A functional handler that needs @FromBody or an optional parameter is written as a function reference (post(::create)), see Coming from GenHTTP.

Naming and API Shape

DeviationReason
Interfaces drop the I prefix (Handler, Request). Static factories that would clash with an interface become companion extensions (Resource.fromString(...)), implementations that would clash get a descriptive prefix (HttpRequest, AbstractServerHost, MapPropertyBag).Kotlin conventions
The Async suffix is dropped, such methods are suspend functions.Kotlin coroutines
IHandlerBuilder<TBuilder> is HandlerBuilder.WithConcerns<TBuilder>.Kotlin cannot overload an interface name by its number of type parameters
Forwarding.For is Forwarding.forAddress.for is a keyword in Kotlin
Annotations that would clash with API types get an Api prefix: @ApiRequestBody, @ApiRequestHeader, @ApiResponse, @ApiResponseHeader. DocumentationAttribute is the meta-annotation @Documentation.Clashes with RequestBody, Response, …; annotations cannot inherit
Optional annotation parameters use "" or Nothing::class instead of null.Annotation parameters cannot be nullable
ResponseStatus carries its numeric code, ResponseStatus.fromCode(Int) replaces casts. HttpProtocols offers hasFlag, plus and minus.Kotlin enums have no implicit integer value or flag arithmetic
SslProtocols, SslPolicyErrors, X509RevocationMode and CompressionLevel are small enums of the API, flags are a Set.There is no equivalent in the JDK
IServer.DisposeAsync() is suspend fun close(), an EndPoint is AutoCloseable.Kotlin lifecycle conventions
IPropertyBag.TryGet<T>(key, out T) is tryGet(key, KClass<T>): T?, with a reified tryGet<T>(key).No out parameters
Comparing a MemoryView with raw memory uses contentEquals(ByteBuffer).== in Kotlin only compares with Any?
MemoryView.copyTo(ByteBuffer) copies the content of a view.bytes returns a new buffer on every access, upstream exposes the memory directly
The views (ByteString, ContentType, …) are classes that compute their hash on first use.The JVM has no user-defined value types
ResponseBuilder.apply(modifications) keeps its name from GenHTTP and so shadows Kotlin’s apply { } for response builders.The upstream name is kept

Engines and Hosting

DeviationReason
Kestrel is not ported, the counterpart of ioxide is the Netty engine (genhttp-core-netty, org.genhttp.engine.netty.Host); ServerEngine is Internal, Netty or Custom.Kestrel and ioxide are specific to .NET; Netty is the JVM counterpart of ioxide
The Netty engine serves HTTP/1.0 and HTTP/1.1 only (plain and TLS, including websockets), there is no native file path.Scope of its first version
The EngineOptions of the Netty engine tune Netty: EventLoopOptions(count, transport) replaces ReactorOptions, TcpTransportOptions keeps the handshake timeout, the cipher options, the listen backlog and the idle and send timeouts and adds tlsProvider; there are no kTLS, write slab, pool, zero copy, QUIC and HTTP/3 options; onReactorStart is onEventLoopStart.The options of ioxide tune its io_uring runtime
The Netty engine receives request bodies completely before the handler runs (at most 512 MB, larger ones are answered with 413), unterminated request headers above 1 MB are answered with 431.RequestBody.asStream() is a blocking stream, which cannot wait for data on the event loop that has to deliver it
The Netty engine terminates TLS with OpenSSL (BoringSSL) or the JDK; every CertificateValidator works, cipherSuites (TLS 1.3) only applies to the JDK provider; certificates are not reloaded by a ReloadCertificates call, the provider is asked for every handshake.The TLS providers Netty offers
The Netty engine reports GenHTTP/<version> in the Server header (ioxide: ioxide-genhttp), the state of connections is pooled per event loop.The engines share their response handling
The read timeouts of the internal engine are checked once per second, so they fire up to a second late.A timer per read would cost more than the deadline it enforces
The Date header is rendered once per second by a background thread.Reading the clock costs about 30 ns per response on virtualized hosts
There is no ASP.NET Core adapter.ASP.NET Core does not exist on the JVM
An IPv6 endpoint with dualStack = false accepts IPv4 connections but closes them immediately.The JDK cannot set IPV6_V6ONLY
FileAssetsBuilder.refreshInterval(...) is accepted but has no effect.It only affects the Ioxide engine in GenHTTP

I/O and Memory

DeviationReason
Stream is InputStream or OutputStream, IBufferWriter<byte> is a WritableByteChannel, the PipeReader of an upgraded connection an InputStream.The JVM’s standard types, usable from coroutines because connections run on virtual threads
On the Netty engine, the stream of an upgraded connection is also a SuspendingInput (an addition to the API): content that reads it with readSuspending on its dispatcher - such as the websockets module - runs on the event loop.A stream of the JVM blocks, which would halt the event loop
ReadOnlyMemory<byte> is a read-only ByteBuffer whose content is position until limit.The JDK’s standard memory view
WritingStream is a write-only OutputStream.Streams of the JVM are not bidirectional
Checksums of stream content require markSupported(), the length of StreamContent is only the known length.Streams of the JVM cannot seek
FormattableContent<T> is FormattableContent(value: Any).No overloading by generic arity
The sync and async overloads of Handler.from and Concern.from are one suspend lambda.A suspend lambda accepts both
A directory container rejects paths that escape its directory, e.g. via %2F.Hardening

Reflection and Frameworks

DeviationReason
Operations are analyzed and invoked with kotlin-reflect instead of Roslyn code generation; inline handlers take suspend lambdas or function references.See Execution Modes
Default values of parameters cannot be read: an optional parameter is left out of the call so that Kotlin’s own default applies.A limitation of kotlin-reflect
The types of lambda parameters are converted with KType.toJavaType() for serialization.kotlin-reflect cannot map them to Java types directly
Dependency injection uses Koin instead of Microsoft.Extensions.DependencyInjection: IServiceProvider is Koin, scoped services are registered in scope(RequestScope) { scoped { } }.Koin is the established container for Kotlin

Serialization and Templates

DeviationReason
JSON escapes HTML-sensitive and non-ASCII characters, enums with explicit values (ResponseStatus) are written as their value, whole-number floating point values without fraction (42).Mirrors the defaults of System.Text.Json
XML element names are written in PascalCase and read case-insensitively.Mirrors XmlSerializer and the property names of .NET
Templates of Renderer.from(...) and of the API browsers use Pebble syntax ({{ title }}) instead of Cottle.There is no Cottle for the JVM

Security

DeviationReason
A certificate with its private key is a KeyStore.PrivateKeyEntry instead of an X509Certificate2.The JDK keeps keys and certificates apart
Bearer tokens are com.nimbusds.jwt.SignedJWT, keys are java.security.Key; unsigned (alg: none) and encrypted tokens are rejected.Nimbus JOSE + JWT replaces System.IdentityModel; hardening
Lambda overloads of authentication(...) with two parameters need explicit parameter types.Kotlin does not resolve overloads by the lambda body

Localization

DeviationReason
The selected culture is read with CurrentCulture.culture() and uiCulture() (a coroutine context element) instead of CultureInfo.CurrentCulture.The JVM has no culture per thread, and coroutines may switch threads
Overloads of fromRequest are told apart by the return type of their lambda; the culture variant is fromRequestCultures.Kotlin cannot overload lambdas by return type otherwise
Language tags without a region resolve to a default region (de to de-DE).Mirrors CultureInfo.CreateSpecificCulture

Real Time

DeviationReason
A WebsocketConnection is not disposable; cancellation throws CancellationException instead of a Canceled frame error; segmented payloads are linearized into a copy.close() would clash with the close frame, and the engine owns the input
Status codes of close frames are Int (0 to 65535), opcodes Byte.No unsigned types in the public API
An EventStream rethrows CancellationException instead of sending a retry.Structured concurrency
On the Netty engine, the message pump of a websocket runs on the event loop of its connection, so websocket handlers must not block.Websockets cost as little CPU per message as on the internal engine

Proxies and Load Balancing

DeviationReason
The JDK HttpClient replaces .NET’s: adjustHandler takes (HttpClient.Builder) -> Unit, adjustClient runs on the built client, the read timeout applies per request.The JDK client is immutable and has no handler type
Expect, Transfer-Encoding and Content-Length request headers are not copied; the JDK client adds a User-Agent if the client sent none.Restricted headers of the JDK client
WebsocketTunnelContent takes the request as a second argument; the tunnel ends when either direction ends.The client side is read from request.upgrade()
Status codes unknown to ResponseStatus are answered with 502, Content-* request headers are matched case-insensitively.Avoids invalid responses for unusual upstreams
PriorityEvaluation is a type alias for (Request) -> Priority.Delegates map to function types

Parser

DeviationReason
The Glyph11 parser reads contiguous ByteBuffers only.The engine always provides a contiguous buffer
The views on the fields of a parsed request are recycled for the next request of the connection.Views are objects on the JVM, recycling them keeps parsing allocation-free

A few tests could not be ported literally for the same reasons - they are listed in the log in the repository, too. None of them is weakened: each checks the same behavior in the way the platform allows.