Quarkus extension deployment module for integrating Ollama LLM models with Quarkus applications through the LangChain4j framework
The Quarkus LangChain4j Ollama Deployment module configures GraalVM native image compilation support through the nativeSupport build step. This ensures that all necessary classes, service providers, and reflection configurations are properly registered for native image compilation.
package io.quarkiverse.langchain4j.ollama.deployment;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveHierarchyBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem;
import io.smallrye.config.ConfigSourceInterceptor;
public class OllamaProcessor {
@BuildStep
void nativeSupport(
BuildProducer<ServiceProviderBuildItem> serviceProviderProducer,
BuildProducer<ReflectiveClassBuildItem> reflectiveClassProducer,
BuildProducer<ReflectiveHierarchyBuildItem> reflectiveHierarchyProducer
) {
// Register all ConfigSourceInterceptor service providers
serviceProviderProducer.produce(
ServiceProviderBuildItem.allProvidersFromClassPath(
ConfigSourceInterceptor.class.getName()
)
);
// Register OllamaChatRequest for reflection
reflectiveHierarchyProducer.produce(
ReflectiveHierarchyBuildItem.builder(
"dev.langchain4j.model.ollama.OllamaChatRequest"
)
.source(getClass().getSimpleName())
.build()
);
// Register OllamaChatResponse for reflection
reflectiveHierarchyProducer.produce(
ReflectiveHierarchyBuildItem.builder(
"dev.langchain4j.model.ollama.OllamaChatResponse"
)
.source(getClass().getSimpleName())
.ignoreNested(false)
.build()
);
// Register serializers/deserializers for reflection
reflectiveClassProducer.produce(
ReflectiveClassBuildItem.builder(
"dev.langchain4j.model.ollama.FormatSerializer",
"dev.langchain4j.model.ollama.OllamaDateDeserializer"
)
.constructors()
.methods(false)
.fields(false)
.build()
);
}
}Purpose: Registers classes, service providers, and hierarchies for GraalVM native image compilation.
Produces:
ServiceProviderBuildItem - Service provider registrationsReflectiveClassBuildItem - Individual class registrationsReflectiveHierarchyBuildItem - Class hierarchy registrationsserviceProviderProducer.produce(
ServiceProviderBuildItem.allProvidersFromClassPath(
ConfigSourceInterceptor.class.getName()
)
);Target Interface: io.smallrye.config.ConfigSourceInterceptor
Purpose: Registers all implementations of ConfigSourceInterceptor found on the classpath.
Description: This registration ensures that all configuration source interceptors are available in the native image. Configuration source interceptors are used by SmallRye Config to intercept and modify configuration values during runtime. Without this registration, custom configuration interceptors would not be discovered in native images.
Why Needed: Service providers in native images must be explicitly registered because native image compilation does not support classpath scanning. This registration makes the service provider mechanism work in native mode.
reflectiveHierarchyProducer.produce(
ReflectiveHierarchyBuildItem.builder(
"dev.langchain4j.model.ollama.OllamaChatRequest"
)
.source(getClass().getSimpleName())
.build()
);Class: dev.langchain4j.model.ollama.OllamaChatRequest
Hierarchy: Full class hierarchy (all fields, methods, constructors)
Nested Types: Default behavior (nested types included)
Purpose: Enables reflection access to the Ollama chat request class and its hierarchy.
Description: The OllamaChatRequest class represents the request payload sent to the Ollama API. It needs reflection support because:
Why Full Hierarchy: The request class contains nested objects and complex type hierarchies that must be accessible via reflection for proper serialization.
reflectiveHierarchyProducer.produce(
ReflectiveHierarchyBuildItem.builder(
"dev.langchain4j.model.ollama.OllamaChatResponse"
)
.source(getClass().getSimpleName())
.ignoreNested(false)
.build()
);Class: dev.langchain4j.model.ollama.OllamaChatResponse
Hierarchy: Full class hierarchy (all fields, methods, constructors)
Nested Types: Explicitly included (ignoreNested(false))
Purpose: Enables reflection access to the Ollama chat response class and all nested types.
Description: The OllamaChatResponse class represents the response payload received from the Ollama API. It needs reflection support because:
Nested Types Handling: The explicit ignoreNested(false) ensures that all nested classes within the response hierarchy are also registered for reflection. This is critical because API responses often contain nested data structures.
reflectiveClassProducer.produce(
ReflectiveClassBuildItem.builder(
"dev.langchain4j.model.ollama.FormatSerializer",
"dev.langchain4j.model.ollama.OllamaDateDeserializer"
)
.constructors()
.methods(false)
.fields(false)
.build()
);Classes:
dev.langchain4j.model.ollama.FormatSerializerdev.langchain4j.model.ollama.OllamaDateDeserializerReflection Scope:
Purpose: Enables reflection access to Jackson custom serializers and deserializers.
Description: These classes provide custom serialization/deserialization logic for specific fields in Ollama request/response objects:
format field in requestsWhy Constructor-Only: These classes are instantiated by Jackson's serialization framework, which needs to call their constructors via reflection. However, the actual serialization/deserialization logic is called directly (not via reflection), so method and field access is not needed.
// Produced by nativeSupport build step
ServiceProviderBuildItem.allProvidersFromClassPath(String serviceInterfaceName)Purpose: Registers service provider implementations for native image.
Parameters:
serviceInterfaceName - Fully qualified name of the service interfaceEffect: All implementations found via META-INF/services/ files are registered in the native image.
// Builder pattern for reflective class registration
ReflectiveClassBuildItem.builder(String... classNames)
.constructors() // Enable constructor reflection
.methods(boolean) // Enable/disable method reflection
.fields(boolean) // Enable/disable field reflection
.build()Purpose: Registers specific classes for reflection with fine-grained control.
Options:
constructors() - Enable constructor reflectionmethods(boolean) - Enable/disable method reflectionfields(boolean) - Enable/disable field reflectionUse Case: For classes that need selective reflection support (e.g., only constructors).
// Builder pattern for reflective hierarchy registration
ReflectiveHierarchyBuildItem.builder(String className)
.source(String) // Source identifier for debugging
.ignoreNested(boolean) // Include/exclude nested types
.build()Purpose: Registers a class and its entire hierarchy for reflection.
Options:
source(String) - Identifies the source of the registration for debuggingignoreNested(boolean) - Controls whether nested types are includedUse Case: For complex data classes with nested objects and type hierarchies.
The build items produced by the nativeSupport build step result in the following native image reflection configuration:
[
{
"name": "dev.langchain4j.model.ollama.OllamaChatRequest",
"allDeclaredConstructors": true,
"allDeclaredMethods": true,
"allDeclaredFields": true,
"allPublicConstructors": true,
"allPublicMethods": true,
"allPublicFields": true
},
{
"name": "dev.langchain4j.model.ollama.OllamaChatResponse",
"allDeclaredConstructors": true,
"allDeclaredMethods": true,
"allDeclaredFields": true,
"allPublicConstructors": true,
"allPublicMethods": true,
"allPublicFields": true
}
][
{
"name": "dev.langchain4j.model.ollama.FormatSerializer",
"allDeclaredConstructors": true,
"allPublicConstructors": true
},
{
"name": "dev.langchain4j.model.ollama.OllamaDateDeserializer",
"allDeclaredConstructors": true,
"allPublicConstructors": true
}
]{
"services": [
{
"interface": "io.smallrye.config.ConfigSourceInterceptor",
"providers": [
"... all implementations found on classpath ..."
]
}
]
}GraalVM native image compilation performs static analysis at build time to determine which classes and methods are reachable. Reflection usage breaks this static analysis because:
Class.forName() are not statically visibleMethod.invoke() are not statically visibleField.get/set() are not statically visibleJackson (the JSON library used by Ollama REST client) heavily uses reflection for:
Without proper reflection configuration, Jackson would fail at runtime in native images.
The Java Service Provider Interface (SPI) uses META-INF/services/ files to discover implementations. In native images:
To verify native image support works correctly:
mvn clean package -Pnative./target/my-app-1.0.0-runnerTest Ollama functionality:
Check for reflection errors:
ClassNotFoundException at runtimeNoSuchMethodException at runtimeIllegalAccessException at runtimeSymptom: ClassNotFoundException or NoSuchMethodException at runtime in native image
Solution: Add the missing class to the reflection configuration in the nativeSupport build step
Example:
reflectiveClassProducer.produce(
ReflectiveClassBuildItem.builder("com.example.MissingClass")
.constructors()
.methods()
.fields()
.build()
);Symptom: Service provider implementations not discovered in native image
Solution: Ensure service providers are registered via ServiceProviderBuildItem
Example:
serviceProviderProducer.produce(
ServiceProviderBuildItem.allProvidersFromClassPath(
"com.example.ServiceInterface"
)
);Symptom: IllegalAccessException for nested classes in native image
Solution: Use ReflectiveHierarchyBuildItem with ignoreNested(false)
Example:
reflectiveHierarchyProducer.produce(
ReflectiveHierarchyBuildItem.builder("com.example.ParentClass")
.ignoreNested(false)
.build()
);source() parameter helps debug which extension registered a class for reflectionInstall with Tessl CLI
npx tessl i tessl/maven-io-quarkiverse-langchain4j--quarkus-langchain4j-ollama-deployment