Enterprise Java

Intelligent Document Processing with Apache Camel, Docling & LangChain4j

Enterprise applications often need to process large numbers of documents such as PDFs, invoices, manuals, reports, and policy documents. Extracting text from these documents is only the first step; the content must also be structured, processed, and made available for intelligent question answering. A useful architecture combines Apache Camel for document-processing orchestration, Docling for converting documents into clean Markdown, and LangChain4j for interacting with an LLM and building question-and-answer capabilities. In this architecture, Apache Camel orchestrates the document-processing workflow, Docling converts PDF documents into structured Markdown, and LangChain4j passes the processed content to an LLM to provide intelligent question-answering capabilities.

1. Understanding Apache Camel, LangChain4j and Docling

1.1 Apache Camel

Apache Camel is an integration framework based on enterprise integration patterns that provides a simple way to define how data moves between applications, files, APIs, message brokers, databases, and other systems. It uses routes to describe the sequence of processing steps, allowing developers to connect different systems without tightly coupling their implementations. Camel supports a large number of components and protocols, making it suitable for integrating file systems, REST APIs, messaging platforms, databases, and external services. In a document-processing workflow, Camel can detect a new PDF, send it to a document-processing service, receive the generated Markdown, pass the content to an AI processing component, and store or expose the resulting information. Camel can also handle concerns such as routing, transformation, error handling, retries, logging, and message delivery. This makes Camel responsible for workflow and integration, while the actual document understanding and processing are handled by specialized components such as Docling.

1.2 Docling

Docling is an open-source document processing toolkit designed to understand documents and convert them into structured representations. It can process formats such as PDF while preserving useful document structures such as headings, paragraphs, tables, lists, and other layout information. This is important because simply extracting text from a PDF can lose relationships between different parts of the document and make the resulting content less useful for downstream AI processing. Docling can convert the document into formats such as Markdown, providing a lightweight representation that retains much of the original document structure. For LLM applications, Markdown is particularly useful because it is both human-readable and retains structural context that can help the model understand the content. Instead of sending raw PDF content directly to an LLM, an application can use Docling to convert the PDF into structured Markdown, providing cleaner and more meaningful input for downstream processing, retrieval, and question-answering workflows. Docling can therefore act as the document-understanding layer between the original document and an AI application.

1.3 LangChain4j

LangChain4j provides Java abstractions for building applications powered by large language models. It integrates LLM capabilities into Java applications while providing higher-level abstractions for common AI application patterns such as chat models, embeddings, retrieval, AI services, and Retrieval-Augmented Generation (RAG). Rather than implementing model-specific communication directly, an application can use LangChain4j APIs to interact with supported language models and build reusable AI services. It can also help manage prompts, conversation context, document retrieval, and structured interactions with models. In this example, LangChain4j receives the Markdown generated by Docling and uses it as document context when interacting with the LLM to answer questions about the original document. For larger document collections, the same approach can be extended by splitting Markdown into smaller chunks, generating embeddings, storing them in a vector database, and retrieving only the relevant content for each question. This allows LangChain4j to form the AI-processing layer of the document pipeline while Apache Camel manages the surrounding integration workflow and Docling handles document conversion.

2. Code Example

Let’s build a small Spring Boot application that processes a PDF and provides question answering over the generated Markdown.

For this example, we will use travel-policy.pdf as the input document. Apache Camel will process this PDF through Docling and generate the corresponding travel-policy.pdf.md file in the output directory. The Q&A API will then retrieve this generated Markdown file and pass its contents to LangChain4j.

2.1 Project Dependencies

The example uses Spring Boot, Apache Camel, and LangChain4j. The exact LangChain4j model integration can be changed depending on the LLM provider being used.

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.apache.camel.springboot</groupId>
        <artifactId>camel-spring-boot-starter</artifactId>
        <version>stable__jar__version</version>
    </dependency>
    <dependency>
        <groupId>org.apache.camel.springboot</groupId>
        <artifactId>camel-file-starter</artifactId>
        <version>stable__jar__version</version>
    </dependency>
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j</artifactId>
        <version>stable__jar__version</version>
    </dependency>
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-open-ai</artifactId>
        <version>stable__jar__version</version>
    </dependency>
</dependencies>

The pom.xml defines the dependencies required to build the document-processing application. The spring-boot-starter dependency provides the core Spring Boot functionality required to run the application. The camel-spring-boot-starter integrates Apache Camel with Spring Boot, allowing Camel routes to be defined and managed as part of the Spring application, while camel-file-starter adds Camel’s file component for monitoring directories and processing PDF files. The langchain4j dependency provides the core LangChain4j APIs for working with LLM-based applications, and langchain4j-open-ai adds the OpenAI integration used to connect the application with an OpenAI-compatible language model. The stable__jar__version placeholder should be replaced with the appropriate stable version of each dependency.

2.2 Application Configuration

The application can use an OpenAI-compatible model through LangChain4j:

spring:
  application:
    name: document-ai

openai:
  api-key: ${OPENAI_API_KEY}

camel:
  main:
    name: document-processing

The application.yml file contains the basic configuration for the document-processing application. The spring.application.name property sets the Spring Boot application name to document-ai. The openai.api-key property reads the OpenAI API key from the OPENAI_API_KEY environment variable, keeping the credential outside the source code and configuration file. The camel.main.name property sets the name of the Apache Camel context to document-processing, making the Camel runtime identifiable when the application starts. This configuration keeps application settings separate from the Java implementation and allows sensitive credentials and environment-specific values to be supplied at runtime.

2.3 Calling Docling

Docling can be exposed as a separate document-processing service. This is useful because the Java application does not need to embed the Python-based document-processing runtime. The following service invokes a Docling endpoint and receives Markdown as the response:

package com.example.documentai;

import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient;

@Service
public class DoclingService {

    private final RestClient restClient;

    public DoclingService(RestClient.Builder builder) {
        this.restClient = builder
                .baseUrl("http://localhost:5001")
                .build();
    }

    public String convertToMarkdown(byte[] pdf) {
        return restClient.post()
                .uri("/convert")
                .body(pdf)
                .retrieve()
                .body(String.class);
    }
}

The DoclingService class encapsulates communication between the Spring Boot application and the Docling document-processing service. The @Service annotation registers the class as a Spring-managed service, while RestClient is used to make HTTP requests to the Docling server. The constructor receives a RestClient.Builder and configures the base URL as http://localhost:5001, which represents the Docling service in this example. The convertToMarkdown() method accepts the PDF as a byte array and sends it to the /convert endpoint using an HTTP POST request. The generated Markdown is returned as a Java String. This keeps the Docling-specific HTTP communication in a dedicated service instead of placing it directly inside the Apache Camel route.

2.4 Processing the PDF with Apache Camel

Apache Camel can monitor an input directory and trigger the document-processing pipeline whenever a PDF is added.

package com.example.documentai;

import org.apache.camel.builder.RouteBuilder;
import org.springframework.stereotype.Component;

@Component
public class DocumentRoute extends RouteBuilder {

    private final DoclingService doclingService;

    public DocumentRoute(DoclingService doclingService) {
        this.doclingService = doclingService;
    }

    @Override
    public void configure() {
        from("file:input?include=.*\\.pdf")
            .routeId("pdf-to-markdown")
            .log("Processing document: ${header.CamelFileName}")
            .process(exchange -> {

                byte[] pdf = exchange.getIn().getBody(byte[].class);

                String markdown =
                        doclingService.convertToMarkdown(pdf);

                exchange.getIn().setBody(markdown);
                exchange.getIn().setHeader(
                        "documentName",
                        exchange.getIn().getHeader("CamelFileName"));
            })
            .to("file:output?fileName=${header.documentName}.md")
            .log("Markdown generated for ${header.documentName}");
    }
}

The DocumentRoute class defines the Apache Camel route responsible for converting PDF files into Markdown. The from("file:input?include=.*\.pdf") endpoint monitors the input directory and processes PDF files. The PDF is read from the Camel exchange and passed to the DoclingService, which converts it into Markdown. The generated Markdown replaces the original PDF content in the exchange body, while the original filename is stored in the documentName header. Finally, the file:output endpoint writes the Markdown to the output directory using the original document name with a .md extension.

For the travel-policy.pdf document used in this example, Camel therefore generates:

output/travel-policy.pdf.md

This generated file becomes the document source for the Q&A API in the next step.

2.5 Passing Markdown to LangChain4j

Next, the generated Markdown can be supplied to a LangChain4j chat model.

package com.example.documentai;

import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import org.springframework.stereotype.Service;

@Service
public class DocumentQaService {

    private final ChatModel chatModel;

    public DocumentQaService() {
        this.chatModel = OpenAiChatModel.builder()
                .apiKey(System.getenv("OPENAI_API_KEY"))
                .modelName("gpt-4.1-mini")
                .build();
    }

    public String ask(String markdown, String question) {

        String prompt = """
                Answer the question using only the document below.

                DOCUMENT:
                %s

                QUESTION:
                %s

                If the answer is not available in the document,
                say that the information is not available.
                """.formatted(markdown, question);

        return chatModel.chat(prompt);
    }
}

The DocumentQaService class provides the question-and-answer functionality using LangChain4j and an OpenAI language model. The ask() method accepts the Markdown generated from the PDF and the user’s question, builds a prompt containing the document content, and sends it to the configured LLM through LangChain4j. The model is instructed to use only the supplied document when generating the answer.

2.6 Building a Simple Q&A API

The REST controller can now retrieve the Markdown file generated by Apache Camel instead of using hardcoded document content:

package com.example.documentai;

import org.springframework.web.bind.annotation.*;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

@RestController
@RequestMapping("/documents")
public class DocumentController {

    private final DocumentQaService qaService;

    public DocumentController(DocumentQaService qaService) {
        this.qaService = qaService;
    }

    @PostMapping("/qa/{document}")
    public String ask(
            @PathVariable String document,
            @RequestParam String question) throws IOException {

        Path markdownFile =
                Path.of("output", document + ".md");

        String markdown = Files.readString(markdownFile);

        return qaService.ask(markdown, question);
    }
}

The DocumentController exposes the Q&A functionality through a REST endpoint and retrieves the actual Markdown file produced by the Camel and Docling pipeline. The @PostMapping("/qa/{document}") mapping makes the document name part of the request URL. For example, when the URL contains travel-policy.pdf, the controller constructs the corresponding Markdown path as output/travel-policy.pdf.md.

The Files.readString() call reads the generated Markdown from the output directory, eliminating the hardcoded document content used in the earlier version of the example. The retrieved Markdown is then passed to qaService.ask() together with the user’s question.

This also means that the same endpoint can work with other PDFs processed by the Camel route. For example, if Camel processes employee-handbook.pdf, the controller can retrieve output/employee-handbook.pdf.md simply by changing the document name in the request URL.

2.7 Code Run and Output

To run the example, start the Docling service on localhost:5001, set the OPENAI_API_KEY environment variable, and start the Spring Boot application.

Place the following PDF in the input directory:

travel-policy.pdf

Apache Camel detects the PDF and sends its content to the Docling service. Docling converts the PDF into Markdown, and Camel writes the generated Markdown to:

output/travel-policy.pdf.md

The Camel application log shows output similar to:

Processing document: travel-policy.pdf
Markdown generated for travel-policy.pdf

The generated Markdown file contains content such as:

# Employee Travel Policy

## Domestic Flights

Employees can book economy class for domestic flights.

## Business Class

Business class is allowed for flights longer than six hours.

## Expense Submission

Travel expenses must be submitted within 30 days.

The Q&A endpoint can then be called using the PDF filename in the URL:

POST /documents/qa/travel-policy.pdf?question=When%20must%20travel%20expenses%20be%20submitted?

The controller uses travel-policy.pdf from the URL to locate output/travel-policy.pdf.md, reads the generated Markdown, and passes it to LangChain4j.

The response is:

Travel expenses must be submitted within 30 days.

This demonstrates the complete flow without hardcoded Markdown: Apache Camel detects and orchestrates the PDF processing, Docling converts the document into structured Markdown, Camel stores the generated Markdown, the REST controller retrieves the Markdown associated with the requested PDF, and LangChain4j uses that content as context to generate the answer.

3. Conclusion

Apache Camel, Docling, and LangChain4j provide a practical combination for building intelligent document-processing applications in Java. Apache Camel provides the integration and orchestration layer, Docling converts complex documents into structured Markdown, and LangChain4j connects the processed content with an LLM for question answering. The workflow starts with a PDF, which Apache Camel sends to Docling for conversion into Markdown; LangChain4j then provides the processed content to an LLM for question answering. For production systems, this foundation can be extended with document storage, chunking, embeddings, vector databases, metadata filtering, RAG, document-level access control, and asynchronous processing. The core architecture remains relatively simple, with each component handling a specific responsibility within the document-processing workflow.

Yatin Batra

An experience full-stack engineer well versed with Core Java, Spring/Springboot, MVC, Security, AOP, Frontend (Angular & React), and cloud technologies (such as AWS, GCP, Jenkins, Docker, K8).
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Back to top button