Build LLM-powered applications in Java with support for chatbots, agents, RAG, tools, and much more
SPI (Service Provider Interface) interfaces for customization and framework integration. Allows downstream frameworks like Quarkus and Spring Boot to provide custom implementations of core LangChain4j services.
LangChain4j uses Java's ServiceLoader mechanism to enable framework integrations without creating hard dependencies. SPIs allow:
Java's ServiceLoader discovers implementations at runtime:
SPIs for customizing AI service creation and configuration.
package dev.langchain4j.spi.services;
/**
* SPI factory interface for creating AI service contexts
* Allows frameworks to customize AI service context creation
*/
public interface AiServiceContextFactory {
/**
* Create AI service context
* @param config Configuration for the context
* @return AiServiceContext instance
*/
AiServiceContext create(AiServiceConfig config);
}
/**
* SPI factory interface for creating AI services
* Allows frameworks to customize how AI service implementations are created
*/
public interface AiServicesFactory {
/**
* Create AI service implementation
* @param context AI service context
* @return AI service implementation
*/
<T> T create(AiServiceContext context);
}
/**
* SPI adapter interface for token streams
* Allows frameworks to customize token stream handling
*/
public interface TokenStreamAdapter {
/**
* Adapt token stream to framework-specific implementation
* @param tokenStream Original token stream
* @return Adapted token stream
*/
TokenStream adapt(TokenStream tokenStream);
}Thread Safety:
Common Pitfalls:
Edge Cases:
Exception Handling:
Performance Notes:
Usage Example:
// Custom factory implementation
public class SpringAiServiceContextFactory implements AiServiceContextFactory {
@Override
public AiServiceContext create(AiServiceConfig config) {
// Integrate with Spring
return new SpringAwareAiServiceContext(config);
}
}
// Register in META-INF/services/dev.langchain4j.spi.services.AiServiceContextFactory:
// com.mycompany.SpringAiServiceContextFactoryIntegration Benefits:
Related APIs:
AiServices - Main API that uses these SPIsAiServiceContext - Context object passed to servicesAiServiceConfig - Configuration for service creationSPI for customizing guardrail service creation.
package dev.langchain4j.service.guardrail.spi;
/**
* SPI factory interface for creating guardrail service builders
* Allows frameworks to provide custom guardrail implementations
*/
public interface GuardrailServiceBuilderFactory {
/**
* Create guardrail service builder
* @return GuardrailServiceBuilder instance
*/
GuardrailServiceBuilder create();
}Thread Safety:
Common Pitfalls:
Edge Cases:
Exception Handling:
Performance Notes:
Usage Example:
// Custom guardrail factory
public class CustomGuardrailFactory implements GuardrailServiceBuilderFactory {
@Override
public GuardrailServiceBuilder create() {
return new CustomGuardrailBuilder();
}
}
// Register in META-INF/services/dev.langchain4j.service.guardrail.spi.GuardrailServiceBuilderFactory:
// com.mycompany.CustomGuardrailFactoryUse Cases:
Related APIs:
@Moderate - Annotation that triggers guardrailsGuardrailService - Service interface for guardrailsModerationException - Exception thrown by guardrailsSPI for customizing embedding store JSON serialization.
package dev.langchain4j.spi.store.embedding.inmemory;
/**
* SPI factory interface for creating JSON codecs for in-memory embedding store
* Allows custom serialization/deserialization implementations
*/
public interface InMemoryEmbeddingStoreJsonCodecFactory {
/**
* Create JSON codec instance
* @return InMemoryEmbeddingStoreJsonCodec instance
*/
InMemoryEmbeddingStoreJsonCodec create();
}Thread Safety:
Common Pitfalls:
Edge Cases:
Exception Handling:
Performance Notes:
Usage Example:
// Custom codec using Gson
public class GsonCodecFactory implements InMemoryEmbeddingStoreJsonCodecFactory {
@Override
public InMemoryEmbeddingStoreJsonCodec create() {
return new GsonEmbeddingStoreCodec();
}
}
class GsonEmbeddingStoreCodec implements InMemoryEmbeddingStoreJsonCodec {
private final Gson gson = new GsonBuilder()
.setPrettyPrinting()
.create();
@Override
public String toJson(InMemoryEmbeddingStore<?> store) {
return gson.toJson(store);
}
@Override
public InMemoryEmbeddingStore<?> fromJson(String json) {
return gson.fromJson(json, InMemoryEmbeddingStore.class);
}
}
// Register in META-INF/services/dev.langchain4j.spi.store.embedding.inmemory.InMemoryEmbeddingStoreJsonCodecFactory:
// com.mycompany.GsonCodecFactoryAlternative Serialization Formats:
Related APIs:
InMemoryEmbeddingStore - Store being serializedEmbedding - Vector data in storeTextSegment - Metadata in storeSPIs for class metadata and reflection customization.
package dev.langchain4j.classloading;
/**
* Utility class for returning metadata about a class and its methods
* Allows downstream frameworks (Quarkus, Spring) to use their own mechanisms
* for providing this information
*/
public class ClassMetadataProvider {
/**
* Retrieves implementation of ClassMetadataProviderFactory via ServiceLoader
* @return ClassMetadataProviderFactory instance
*/
public static <MethodKey> ClassMetadataProviderFactory<MethodKey> getClassMetadataProviderFactory();
}
/**
* Implementation of ClassMetadataProviderFactory using Java Reflection
* Default implementation for retrieving annotations and method metadata
*/
public class ReflectionBasedClassMetadataProviderFactory {
/**
* Get annotation from method
* @param method Method to inspect
* @param annotationClass Annotation class to find
* @return Optional containing annotation if present
*/
public <T extends Annotation> Optional<T> getAnnotation(
Method method,
Class<T> annotationClass
);
/**
* Get annotation from class
* @param clazz Class to inspect
* @param annotationClass Annotation class to find
* @return Optional containing annotation if present
*/
public <T extends Annotation> Optional<T> getAnnotation(
Class<?> clazz,
Class<T> annotationClass
);
/**
* Get all non-static methods on class
* @param clazz Class to inspect
* @return Iterable of methods
*/
public Iterable<Method> getNonStaticMethodsOnClass(Class<?> clazz);
}Thread Safety:
Common Pitfalls:
Edge Cases:
Exception Handling:
Performance Notes:
Build-Time vs Runtime Reflection:
Runtime (Default):
Build-Time (Quarkus):
Usage Example:
// Quarkus build-time metadata provider
public class QuarkusClassMetadataProviderFactory
implements ClassMetadataProviderFactory<String> {
private final Map<String, MethodMetadata> methodCache;
public QuarkusClassMetadataProviderFactory(
Map<String, MethodMetadata> buildTimeMetadata
) {
this.methodCache = buildTimeMetadata;
}
@Override
public <T extends Annotation> Optional<T> getAnnotation(
String methodKey,
Class<T> annotationClass
) {
MethodMetadata metadata = methodCache.get(methodKey);
if (metadata == null) {
return Optional.empty();
}
return metadata.getAnnotation(annotationClass);
}
@Override
public <T extends Annotation> Optional<T> getAnnotation(
Class<?> clazz,
Class<T> annotationClass
) {
// Look up from build-time metadata
return buildTimeMetadata.getClassAnnotation(clazz, annotationClass);
}
@Override
public Iterable<String> getNonStaticMethodsOnClass(Class<?> clazz) {
// Return method keys from build-time scan
return buildTimeMetadata.getMethodKeys(clazz);
}
}
// Register in META-INF/services/dev.langchain4j.classloading.ClassMetadataProviderFactory:
// io.quarkus.langchain4j.QuarkusClassMetadataProviderFactoryGraalVM Native Image Support:
// At build time, register reflection metadata
RuntimeReflection.register(MyAiService.class);
for (Method method : MyAiService.class.getDeclaredMethods()) {
RuntimeReflection.register(method);
}
// Custom provider uses pre-registered metadata
public class NativeImageMetadataProvider
implements ClassMetadataProviderFactory<Method> {
// Implementation uses registered metadata
}Related APIs:
AiServices - Uses metadata provider to inspect interfaces@UserMessage, @SystemMessage, etc. - Annotations to discover@Tool - Tool discovery via reflectionimport dev.langchain4j.spi.services.AiServiceContextFactory;
import dev.langchain4j.service.AiServiceContext;
// Custom factory implementation
public class MyCustomAiServiceContextFactory implements AiServiceContextFactory {
@Override
public AiServiceContext create(AiServiceConfig config) {
// Custom context creation logic
// Can integrate with framework-specific features
return new CustomAiServiceContext(config);
}
}To register the SPI implementation, create a file:
META-INF/services/dev.langchain4j.spi.services.AiServiceContextFactory
With content:
com.mycompany.MyCustomAiServiceContextFactoryTesting SPI Registration:
@Test
void testSpiDiscovery() {
ServiceLoader<AiServiceContextFactory> loader =
ServiceLoader.load(AiServiceContextFactory.class);
boolean found = false;
for (AiServiceContextFactory factory : loader) {
if (factory instanceof MyCustomAiServiceContextFactory) {
found = true;
break;
}
}
assertTrue(found, "Custom SPI implementation not discovered");
}import dev.langchain4j.service.guardrail.spi.GuardrailServiceBuilderFactory;
// Custom guardrail factory for framework integration
public class SpringGuardrailServiceBuilderFactory implements GuardrailServiceBuilderFactory {
@Override
public GuardrailServiceBuilder create() {
// Create builder that can inject Spring beans
return new SpringAwareGuardrailServiceBuilder();
}
}Register via:
META-INF/services/dev.langchain4j.service.guardrail.spi.GuardrailServiceBuilderFactory
Spring Integration Example:
public class SpringAwareGuardrailServiceBuilder implements GuardrailServiceBuilder {
@Autowired
private ApplicationContext applicationContext;
@Override
public GuardrailService build() {
// Create guardrail that uses Spring beans
return new GuardrailService() {
@Override
public void validate(String input) {
// Use Spring-managed moderation service
ModerationService moderationService =
applicationContext.getBean(ModerationService.class);
moderationService.check(input);
}
};
}
}import dev.langchain4j.spi.store.embedding.inmemory.InMemoryEmbeddingStoreJsonCodecFactory;
import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStoreJsonCodec;
// Custom JSON codec using a different JSON library
public class GsonEmbeddingStoreCodecFactory implements InMemoryEmbeddingStoreJsonCodecFactory {
@Override
public InMemoryEmbeddingStoreJsonCodec create() {
return new GsonInMemoryEmbeddingStoreJsonCodec();
}
}
class GsonInMemoryEmbeddingStoreJsonCodec implements InMemoryEmbeddingStoreJsonCodec {
private final Gson gson = new Gson();
@Override
public String toJson(InMemoryEmbeddingStore<?> store) {
// Serialize using Gson
return gson.toJson(store);
}
@Override
public InMemoryEmbeddingStore<?> fromJson(String json) {
// Deserialize using Gson
return gson.fromJson(json, InMemoryEmbeddingStore.class);
}
}Register via:
META-INF/services/dev.langchain4j.spi.store.embedding.inmemory.InMemoryEmbeddingStoreJsonCodecFactory
Testing Codec:
@Test
void testCodecRoundTrip() {
InMemoryEmbeddingStore<TextSegment> store = new InMemoryEmbeddingStore<>();
store.add(embedding, textSegment);
// Serialize
String json = codec.toJson(store);
// Deserialize
InMemoryEmbeddingStore<?> restored = codec.fromJson(json);
// Verify
List<EmbeddingMatch<TextSegment>> results =
restored.findRelevant(embedding, 1);
assertEquals(1, results.size());
}import dev.langchain4j.classloading.ClassMetadataProvider;
// Custom implementation for build-time reflection (e.g., Quarkus)
public class QuarkusClassMetadataProviderFactory implements ClassMetadataProviderFactory<String> {
@Override
public <T extends Annotation> Optional<T> getAnnotation(
String methodKey,
Class<T> annotationClass
) {
// Use Quarkus build-time metadata
return quarkusRecorder.getMethodAnnotation(methodKey, annotationClass);
}
@Override
public <T extends Annotation> Optional<T> getAnnotation(
Class<?> clazz,
Class<T> annotationClass
) {
// Use Quarkus build-time metadata
return quarkusRecorder.getClassAnnotation(clazz, annotationClass);
}
@Override
public Iterable<String> getNonStaticMethodsOnClass(Class<?> clazz) {
// Return method keys from build-time metadata
return quarkusRecorder.getMethodKeys(clazz);
}
}Quarkus Build Step:
@BuildStep
public void processAiServices(
BuildProducer<ReflectiveClassBuildItem> reflectiveClass,
BuildProducer<ServiceProviderBuildItem> serviceProvider
) {
// Register AI service interfaces for reflection
reflectiveClass.produce(
ReflectiveClassBuildItem.builder(MyAiService.class)
.methods(true)
.build()
);
// Register custom metadata provider
serviceProvider.produce(
ServiceProviderBuildItem.allProvidersFromClassPath(
ClassMetadataProviderFactory.class.getName()
)
);
}import dev.langchain4j.spi.services.AiServicesFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
// Spring Boot integration using SPI
public class SpringAiServicesFactory implements AiServicesFactory {
@Autowired
private ApplicationContext applicationContext;
@Override
public <T> T create(AiServiceContext context) {
// Create AI service with Spring bean injection
// Tools can be Spring beans, automatically injected
return createSpringManagedAiService(context);
}
private <T> T createSpringManagedAiService(AiServiceContext context) {
// Implementation that integrates with Spring
// - Inject Spring beans as tools
// - Use Spring's transaction management
// - Apply Spring AOP aspects
// - Use Spring's event system
return (T) Proxy.newProxyInstance(
context.aiServiceClass().getClassLoader(),
new Class[]{context.aiServiceClass()},
new SpringAwareInvocationHandler(context, applicationContext)
);
}
}Spring Configuration:
@Configuration
@ConditionalOnClass(AiServices.class)
public class LangChain4jAutoConfiguration {
@Bean
public AiServicesFactory springAiServicesFactory(ApplicationContext context) {
return new SpringAiServicesFactory(context);
}
@Bean
public ChatModel chatModel(@Value("${langchain4j.api-key}") String apiKey) {
return OpenAiChatModel.builder()
.apiKey(apiKey)
.build();
}
// Auto-discover @Tool beans
@Bean
public List<Object> toolBeans(ApplicationContext context) {
return context.getBeansWithAnnotation(Tool.class).values()
.stream()
.collect(Collectors.toList());
}
}import dev.langchain4j.spi.services.TokenStreamAdapter;
import dev.langchain4j.service.TokenStream;
// Adapter for framework-specific reactive streams
public class ReactorTokenStreamAdapter implements TokenStreamAdapter {
@Override
public TokenStream adapt(TokenStream tokenStream) {
// Adapt to Project Reactor Flux or other reactive type
return new ReactorBackedTokenStream(tokenStream);
}
}
class ReactorBackedTokenStream implements TokenStream {
private final TokenStream delegate;
public ReactorBackedTokenStream(TokenStream delegate) {
this.delegate = delegate;
}
@Override
public void onNext(String token) {
// Forward to Reactor Flux
flux.next(token);
}
@Override
public void onComplete(Response<AiMessage> response) {
// Complete Flux
flux.complete();
}
@Override
public void onError(Throwable error) {
// Error Flux
flux.error(error);
}
}Reactive Streams Integration:
// Use adapted token stream with reactive frameworks
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> streamResponse(@RequestParam String message) {
return Flux.create(sink -> {
TokenStream tokenStream = new TokenStream() {
@Override
public void onNext(String token) {
sink.next(token);
}
@Override
public void onComplete(Response<AiMessage> response) {
sink.complete();
}
@Override
public void onError(Throwable error) {
sink.error(error);
}
};
assistant.chat(message, tokenStream);
});
}LangChain4j uses Java's ServiceLoader mechanism to discover SPI implementations:
META-INF/services/ directoryExample structure:
src/main/resources/
META-INF/
services/
dev.langchain4j.spi.services.AiServiceContextFactory
dev.langchain4j.spi.services.AiServicesFactory
dev.langchain4j.service.guardrail.spi.GuardrailServiceBuilderFactoryFile Content Format:
# Comment lines start with #
com.mycompany.MyFactoryImpl
com.mycompany.AlternativeFactoryImpl # Multiple implementations allowedServiceLoader Discovery Process:
Multiple Implementations:
// When multiple implementations exist
ServiceLoader<MyFactory> loader = ServiceLoader.load(MyFactory.class);
for (MyFactory factory : loader) {
// Iterate all implementations
// Usually, first one found is used
}
// LangChain4j pattern: use first found
Optional<MyFactory> factory = ServiceLoader.load(MyFactory.class)
.findFirst();ClassLoader Considerations:
// Use context class loader (default)
ServiceLoader<MyFactory> loader = ServiceLoader.load(MyFactory.class);
// Use specific class loader
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
ServiceLoader<MyFactory> loader = ServiceLoader.load(MyFactory.class, classLoader);
// In modular applications (Java 9+), module must declare "uses"
module my.module {
uses dev.langchain4j.spi.services.AiServicesFactory;
provides dev.langchain4j.spi.services.AiServicesFactory
with com.mycompany.MyFactoryImpl;
}Quarkus can provide:
// Quarkus provides custom ClassMetadataProvider for build-time processing
public class QuarkusLangChain4jExtension {
@BuildStep
public void registerAiServices(BuildProducer<ServiceProviderBuildItem> serviceProvider) {
serviceProvider.produce(
ServiceProviderBuildItem.allProvidersFromClassPath(
AiServiceContextFactory.class.getName()
)
);
}
}Quarkus Build-Time Advantages:
@BuildStep
public void processAiServiceInterfaces(
CombinedIndexBuildItem index,
BuildProducer<UnremovableBeanBuildItem> unremovable,
BuildProducer<AdditionalBeanBuildItem> additionalBeans,
BuildProducer<ReflectiveClassBuildItem> reflective
) {
// Scan for AI service interfaces at build time
Collection<ClassInfo> aiServiceInterfaces = index.getIndex()
.getKnownDirectImplementors(DotName.createSimple(AiService.class.getName()));
for (ClassInfo classInfo : aiServiceInterfaces) {
// Register for reflection (native image)
reflective.produce(
ReflectiveClassBuildItem.builder(classInfo.name().toString())
.methods(true)
.fields(true)
.build()
);
// Process @Tool annotations at build time
processToolAnnotations(classInfo);
}
}Benefits:
Spring Boot can provide:
@Configuration
public class LangChain4jAutoConfiguration {
@Bean
public AiServicesFactory springAiServicesFactory(ApplicationContext context) {
return new SpringAiServicesFactory(context);
}
}Spring Integration Benefits:
@Configuration
public class AiServiceConfiguration {
// Tools as Spring beans
@Bean
public WeatherService weatherService() {
return new WeatherService();
}
@Bean
public Assistant assistant(
ChatModel chatModel,
@Qualifier("weatherService") WeatherService weatherService
) {
return AiServices.builder(Assistant.class)
.chatModel(chatModel)
.tools(weatherService) // Spring bean injected as tool
.build();
}
// Transactional tools
@Service
public class DatabaseTool {
@Tool("Save data to database")
@Transactional // Spring manages transaction
public String saveData(@P("Data to save") String data) {
repository.save(data);
return "Saved";
}
}
// AOP for logging/metrics
@Aspect
@Component
public class AiServiceAspect {
@Around("@annotation(Tool)")
public Object logToolExecution(ProceedingJoinPoint pjp) throws Throwable {
log.info("Executing tool: {}", pjp.getSignature().getName());
long start = System.currentTimeMillis();
try {
return pjp.proceed();
} finally {
log.info("Tool executed in {} ms",
System.currentTimeMillis() - start);
}
}
}
}Spring Boot Starter Example:
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-spring-boot-starter</artifactId>
<version>${langchain4j.version}</version>
</dependency># application.yml
langchain4j:
open-ai:
api-key: ${OPENAI_API_KEY}
model-name: gpt-4
temperature: 0.7
chat-memory:
max-messages: 10Micronaut can provide:
@Factory
public class LangChain4jFactory {
@Singleton
public AiServicesFactory micronautAiServicesFactory(BeanContext beanContext) {
return new MicronautAiServicesFactory(beanContext);
}
}Micronaut Advantages:
// Compile-time DI (no reflection)
@Singleton
public class MyAiService {
private final ChatModel chatModel;
private final WeatherTool weatherTool;
// Constructor injection (compile-time)
public MyAiService(ChatModel chatModel, WeatherTool weatherTool) {
this.chatModel = chatModel;
this.weatherTool = weatherTool;
}
}
// Bean validation integration
public class ValidatedTool {
@Tool("Process validated input")
public String process(
@NotNull @Size(min = 1, max = 100) String input
) {
// Micronaut validates before method execution
return "Processed: " + input;
}
}
// Reactive streams
public class ReactiveAssistant {
@Post("/chat")
public Flux<String> chat(@Body String message) {
return Flux.from(aiService.chatStream(message));
}
}// Generic framework integration pattern
public class CustomFrameworkIntegration {
// 1. Implement SPI factories
public class CustomAiServicesFactory implements AiServicesFactory {
@Override
public <T> T create(AiServiceContext context) {
return createWithCustomFeatures(context);
}
}
// 2. Register via ServiceLoader
// META-INF/services/dev.langchain4j.spi.services.AiServicesFactory
// 3. Provide framework-specific features
private <T> T createWithCustomFeatures(AiServiceContext context) {
return (T) Proxy.newProxyInstance(
context.aiServiceClass().getClassLoader(),
new Class[]{context.aiServiceClass()},
new CustomInvocationHandler(context)
);
}
// 4. Custom invocation handler
private class CustomInvocationHandler implements InvocationHandler {
private final AiServiceContext context;
public CustomInvocationHandler(AiServiceContext context) {
this.context = context;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
// Add custom behavior:
// - Dependency injection
// - Transaction management
// - Security checks
// - Metrics/logging
// - Error handling
return invokeWithCustomBehavior(method, args);
}
}
}Use ServiceLoader: Follow Java's standard SPI pattern for discovery
Document Requirements: Clearly document what your SPI implementation provides
Version Compatibility: Ensure SPI implementations are compatible with LangChain4j version
Fallback Behavior: Provide sensible defaults if custom SPI is not available
Testing: Test SPI implementations thoroughly with ServiceLoader
Thread Safety: Ensure SPI implementations are thread-safe
Performance: Be mindful of performance impact, especially for frequently called SPIs
Error Handling: Provide clear error messages when SPI implementation fails
// When multiple implementations exist, control priority
@Priority(100) // Lower number = higher priority
public class HighPriorityFactory implements AiServicesFactory {
// This will be selected over lower priority factories
}
@Priority(500)
public class LowPriorityFactory implements AiServicesFactory {
// Fallback implementation
}// Load SPI only if condition met
public class ConditionalFactory implements AiServicesFactory {
public ConditionalFactory() {
// Check if conditions met
if (!isEnvironmentReady()) {
throw new UnsupportedOperationException(
"Required environment not available"
);
}
}
private boolean isEnvironmentReady() {
// Check for required classes, system properties, etc.
try {
Class.forName("com.required.Dependency");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
}// SPI that reads configuration
public class ConfigurableFactory implements AiServicesFactory {
private final Config config;
public ConfigurableFactory() {
// Load configuration
this.config = loadConfig();
}
private Config loadConfig() {
// From system properties
String prop = System.getProperty("langchain4j.factory.config");
// From environment
String env = System.getenv("LANGCHAIN4J_CONFIG");
// From config file
// ...
return new Config(prop, env);
}
@Override
public <T> T create(AiServiceContext context) {
// Use configuration
return createConfigured(context, config);
}
}@Test
public void testSpiDiscovery() {
// Test that SPI is discovered
ServiceLoader<AiServicesFactory> loader =
ServiceLoader.load(AiServicesFactory.class);
boolean found = false;
for (AiServicesFactory factory : loader) {
if (factory instanceof MyCustomFactory) {
found = true;
break;
}
}
assertTrue(found, "Custom factory not discovered");
}
@Test
public void testSpiImplementation() {
// Test SPI behavior
MyCustomFactory factory = new MyCustomFactory();
AiServiceConfig config = createTestConfig();
AiServiceContext context = factory.create(config);
assertNotNull(context);
// Test custom behavior
assertTrue(context.hasCustomFeature());
}
@Test
public void testSpiFallback() {
// Test fallback when SPI not available
// Remove SPI from classpath temporarily
// Verify default implementation is used
}
@Test
public void testSpiThreadSafety() throws Exception {
MyCustomFactory factory = new MyCustomFactory();
// Concurrent access test
int threadCount = 10;
CountDownLatch latch = new CountDownLatch(threadCount);
List<Throwable> errors = new CopyOnWriteArrayList<>();
for (int i = 0; i < threadCount; i++) {
new Thread(() -> {
try {
factory.create(createTestConfig());
} catch (Throwable t) {
errors.add(t);
} finally {
latch.countDown();
}
}).start();
}
latch.await();
assertTrue(errors.isEmpty(), "Thread safety issues: " + errors);
}| SPI Interface | Purpose | Package |
|---|---|---|
AiServiceContextFactory | Custom AI service context creation | dev.langchain4j.spi.services |
AiServicesFactory | Custom AI service implementation | dev.langchain4j.spi.services |
TokenStreamAdapter | Custom token stream handling | dev.langchain4j.spi.services |
GuardrailServiceBuilderFactory | Custom guardrail services | dev.langchain4j.service.guardrail.spi |
InMemoryEmbeddingStoreJsonCodecFactory | Custom embedding store serialization | dev.langchain4j.spi.store.embedding.inmemory |
ClassMetadataProvider | Custom class metadata retrieval | dev.langchain4j.classloading |
These SPIs enable deep integration with frameworks while keeping the core LangChain4j library framework-agnostic.
This comprehensive SPI documentation provides production-grade details for coding agents to implement custom integrations with LangChain4j.
Install with Tessl CLI
npx tessl i tessl/maven-dev-langchain4j--langchain4j