Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Apache Tika Bundle Laravel Package

ezsystems/apache-tika-bundle

Symfony bundle that integrates the php-apache-tika library, letting you extract text and metadata from documents through a simple apache_tika.client service. Configure either a remote tika-server host and port or a local tika-app JAR path.

View on GitHub
Deep Wiki
Context7

Getting Started

First, the mismatch. ezsystems/apache-tika-bundle (the README is actually jolicode/apache-tika-bundle) is a Symfony 2 bundle, not a Laravel package. There is no service provider, no facade, no config publishing, and the ApacheTikaBundle class extends Symfony\Component\HttpKernel\Bundle\Bundle. Composer will still pull it into a Laravel app, but php artisan will never register or boot it, and the service definitions in Resources/config/services.xml are invisible to Laravel's container. Don't install it hoping for Tika::getText($file).

What you actually want is the underlying library. Everything the bundle does is wire vaites/php-apache-tika into a DI container with a host/port. In Laravel you reproduce that in ~15 lines.

composer require vaites/php-apache-tika

You need a JRE on the machine. php-apache-tika talks to a Tika server over HTTP (default 127.0.0.1:9998). The library can auto-download and spawn tika-app.jar for you, or you can point it at an already-running server.

Where to look first: read vendor/vaites/php-apache-tika/src/Client.php — that's the whole API surface (getText, getHTML, getXML, getMetadata, getLanguage, getMimeType, detect). Skip the bundle's source entirely; it's four lines of XML and a compiler pass.

Minimal wiring in a Laravel app:

// config/apache-tika.php
return [
    'host'    => env('TIKA_HOST', '127.0.0.1'),
    'port'    => env('TIKA_PORT', 9998),
    'jar'     => env('TIKA_JAR'),          // optional: path to tika-app.jar
    'timeout' => env('TIKA_TIMEOUT', 60),
];
// app/Providers/TikaServiceProvider.php
use Vaites\ApacheTika\Client;

public function register(): void
{
    $this->app->singleton(Client::class, function () {
        $client = config('apache-tika.jar')
            ? Client::make(config('apache-tika.jar'))
            : Client::make(config('apache-tika.host'), (int) config('apache-tika.port'));

        $client->setTimeout((int) config('apache-tika.timeout'));

        return $client;
    });
}

First real use case: a user uploads a PDF/DOCX, you extract plain text into a documents.extracted_text column and feed it into Meilisearch/Scout. That single flow justifies the dependency.

$text = app(Client::class)->getText(Storage::path($path));

Implementation Patterns

Singleton client, injected everywhere

The Client is stateless apart from timeout/OCR settings and cheap to hold, but each Client::make($jar) can trigger a JVM spawn — bind it as a singleton and never call Client::make() inside a loop.

public function __construct(private Client $tika) {}

Wrap it behind your own interface

Tika is a Java process on the same box. When it's down (OOM, deploy without JRE, port conflict) you want one place to swap in a fallback (S3 Textract, a hosted Tika, pdftotext).

interface TextExtractor {
    public function text(string $absolutePath): string;
    public function metadata(string $absolutePath): array;
    public function mimeType(string $absolutePath): string;
}

Bind TikaTextExtractor to TextExtractor in the provider and let the rest of the app stay ignorant of both Tika and the abandoned bundle.

Always extract in a queued job

Tika round-trips are 200ms for a DOCX and 30s+ for a scanned 200-page PDF. Never in the HTTP request.

class ExtractDocumentText implements ShouldQueue
{
    public int $timeout = 120;
    public int $tries = 3;
    public array $backoff = [10, 60, 300];

    public function __construct(public int $documentId) {}

    public function handle(TextExtractor $extractor): void
    {
        $doc = Document::findOrFail($this->documentId);
        $path = Storage::path($doc->path);

        $doc->update([
            'mime_type'      => $extractor->mimeType($path),
            'extracted_text' => $extractor->text($path),
            'metadata'       => $extractor->metadata($path),
            'extracted_at'   => now(),
        ]);
    }
}

Note tika-app.jar handles one request per HTTP call but the server is long-lived; a queue worker is the correct place for this because the JVM stays warm across jobs.

Store the artifact, not just the text

Persist metadata (author, page count, created date) — it's free from getMetadata() and useful for admin search/filtering. Store mime_type from Tika rather than trusting the browser-supplied Content-Type, but don't treat it as a security boundary either.

Upload validation + extraction pipeline

$request->validate(['file' => ['required', 'file', 'max:51200']]);

$path = $request->file('file')->store('documents');
$mime = app(TextExtractor::class)->mimeType(Storage::path($path));

abort_unless(str_starts_with($mime, 'application/pdf'), 422, 'PDFs only.');

OCR for scans

For image-only PDFs, text extraction returns an empty string. Enable OCR explicitly on the client before extraction (this is a client-level setting, so if you flip it globally, every request pays the Tesseract cost):

$client->setOCRLanguage('eng');

Better: run a second pass job that enables OCR only when the first pass yields strlen(trim($text)) < 50.

Reprocessing command

class ReextractDocuments extends Command
{
    protected $signature = 'documents:reextract {--chunk=100}';
    // dispatch ExtractDocumentText for each id, chunked
}

Handy after a Tika upgrade or a jar change.

Testing

Don't hit a real server in tests. Bind a fake TextExtractor in TestCase::setUp(); keep one integration test (tagged @group tika) that runs against a live sidecar in CI.

Gotchas and Tips

The bundle is archived and Symfony-2 only. It declares old Symfony constraints, so composer require ezsystems/apache-tika-bundle in a modern Laravel app will likely fail dependency resolution or drag in symfony/http-kernel of an ancient major. Even if it installs, Laravel will not boot the bundle. Treat the repo as documentation of how to wire the library, then wire it yourself.

Note the vendor mismatch: the repo URL and README disagree (ezsystems vs jolicode). jolicode/apache-tika-bundle is the canonical Packagist name. Neither is maintained. vaites/php-apache-tika itself is also effectively dormant — pin it and expect to maintain the glue yourself.

Java is a hard dependency, and it's invisible until runtime. No JRE means a RuntimeException deep inside the client. Check for it in your deploy script (java -version) and fail fast. Prefer running Tika as a sidecar container or a systemd unit rather than letting PHP spawn it.

Jar auto-download is a production hazard. Client::make($jarPath) will download tika-app.jar (~80MB) on first use into the vendor or temp dir. That breaks on read-only filesystems, ephemeral containers, and air-gapped networks. Set TIKA_JAR to a baked-in path and point at a running server (Client::make($host, $port)) in production.

Timeouts compound. The library's HTTP timeout is separate from your queue worker's $timeout and from PHP's max_execution_time. Set the Tika client timeout below the job timeout so you get a clean exception rather than a killed worker. $client->setTimeout(seconds) is per-client, so configure it once in the provider.

Memory. Tika returns the whole extracted text as one response body. A 500-page PDF can be tens of MB of UTF-8. If you're on a memory-constrained worker, cap file size at upload and/or add --Xmx guidance for the JVM (TIKA_OPTS).

Tika server has zero authentication. Default 127.0.0.1:9998 is fine; binding it to 0.0.0.0 on a shared network exposes a file-processing endpoint that accepts arbitrary content. Keep it loopback-only or put it on a private network with a firewall rule.

Cold start. A freshly started tika-app.jar takes several seconds to accept requests. Health-check /version before routing jobs, otherwise the first job of every deploy fails and retries.

Debugging: bypass PHP entirely first.

curl -T /tmp/sample.pdf http://127.0.0.1:9998/tika
curl -T /tmp/sample.pdf http://127.0.0.1:9998/meta
curl http://127.0.0.1:9998/version

If curl works and the client doesn't, it's timeout, encoding, or a file-path issue — the client requires an absolute local path, not a stream or a remote URL. Download S3 objects to Storage::path() / tmpfile() first.

Empty text is ambiguous. It can mean "image-only PDF," "password-protected PDF," or "unsupported format." Distinguish by checking mimeType() and metadata (pdf:encrypted) rather than assuming OCR is needed.

Extension points that actually exist: Vaites\ApacheTika\Client::setTimeout(), setOCRLanguage(), setDownloadEngine(), and setRetry() (older versions). There is no event system, no pluggable transport. If you need custom behavior, decorate your own TextExtractor — don't subclass the client.

Sanitize before display. Extracted text can contain control characters and HTML from getHTML(). Strip or escape before persisting to a search index that renders snippets.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib