jruby/docs BETA
JRuby · intermediate

Manage Java dependencies with jar-dependencies

7 min read

Manage Java dependencies with jar-dependencies

Curling jars from Maven Central works fine for one or two files. Once a project
grows — multiple libraries, transitive dependencies, reproducible CI builds —
you want a proper manifest and automatic resolution. The jar-dependencies gem
gives you that without requiring a separate Maven installation.

The problem with manual jar management

Lucene’s query parser sits on top of several other Lucene modules. curl just
lucene-queryparser and the first java_import blows up with a NameError for
a class that lived in lucene-core, which you didn’t download:

curl -O .../lucene-queryparser-10.4.0.jar
# ...later: NameError: cannot load Java class org.apache.lucene.index.IndexWriter

You have to know the full dependency graph yourself and keep it in sync as
versions change. jar-dependencies delegates that to Maven.

Project setup

A minimal directory layout:

myapp/
├── Gemfile
├── myapp.gemspec
└── search.rb

Gemfile — load the local gemspec and pull in ruby-maven, which ships the
embedded Maven that does the resolving (no mvn on your PATH required):

source 'https://rubygems.org'
gemspec
gem 'ruby-maven', '~> 3.9'

myapp.gemspec — declare Java dependencies as requirements entries. Each is
a string of the form 'jar group:artifact, version':

Gem::Specification.new do |s|
  s.name    = 'myapp'
  s.version = '0.1.0'
  s.summary = 'Lucene search demo'
  s.authors = ['you']
  s.files   = []
  s.add_runtime_dependency 'jar-dependencies', '~> 0.4'

  s.requirements << 'jar org.apache.lucene:lucene-queryparser, 10.4.0'
end

Declaring only lucene-queryparser is enough — Maven resolves lucene-core
(where StandardAnalyzer lives) and the rest of the graph as transitive
dependencies.

Install the gems:

bundle install

Then resolve the jar graph and write a lockfile:

bundle exec lock_jars

lock_jars reads the requirements from your gemspec, invokes the embedded
Maven runtime to resolve the full transitive graph, downloads the jars into your
local Maven cache (~/.m2/repository), and writes Jars.lock:

org.apache.lucene:lucene-queryparser:10.4.0:compile:
org.apache.lucene:lucene-core:10.4.0:compile:
org.apache.lucene:lucene-queries:10.4.0:compile:
org.apache.lucene:lucene-sandbox:10.4.0:compile:
org.apache.lucene:lucene-facet:10.4.0:compile:

One declared dependency, five resolved jars.

Using the jars

search.rb:

require 'jar_dependencies'
Jars.require_jars_lock!
require 'java'

java_import 'org.apache.lucene.store.ByteBuffersDirectory'
java_import 'org.apache.lucene.analysis.standard.StandardAnalyzer'
java_import 'org.apache.lucene.index.IndexWriter'
java_import 'org.apache.lucene.index.IndexWriterConfig'
java_import 'org.apache.lucene.document.Document'
java_import 'org.apache.lucene.document.TextField'
java_import 'org.apache.lucene.document.Field'
java_import 'org.apache.lucene.index.DirectoryReader'
java_import 'org.apache.lucene.search.IndexSearcher'
java_import 'org.apache.lucene.queryparser.classic.QueryParser'

analyzer = StandardAnalyzer.new
dir      = ByteBuffersDirectory.new
writer   = IndexWriter.new(dir, IndexWriterConfig.new(analyzer))

doc = Document.new
doc.add(TextField.new('body', 'JRuby runs on the JVM with access to every Java library', Field::Store::YES))
writer.add_document(doc)
writer.close

searcher = IndexSearcher.new(DirectoryReader.open(dir))
hits     = searcher.search(QueryParser.new('body', analyzer).parse('JVM library'), 10)
puts searcher.storedFields.document(hits.scoreDocs.first.doc).get('body')

Run it:

bundle exec jruby search.rb

Jars.require_jars_lock! reads Jars.lock and puts every jar it lists on the
JVM classpath — straight from your Maven cache — before any Java class is loaded.

Why bother

  • Transitive resolution — declare one top-level dependency and Maven fetches
    the whole graph automatically.
  • Single source of truth — commit Jars.lock so everyone resolves the same
    versions. lock_jars downloads them into each machine’s Maven cache on first
    run; re-run it whenever you change the requirements.
  • Shareable — packaged as a java-platform gem (with jar-dependencies as a
    runtime dependency), jar-dependencies can vendor the jars for consumers at
    gem install time, so they get them without any curl scripts.
  • Version upgrades — bump the version in the gemspec, re-run lock_jars,
    and the entire resolved graph refreshes.

Next steps

  • Add more s.requirements << lines to the gemspec for additional Java
    libraries; lock_jars resolves the combined dependency graph in one pass.
  • Commit Jars.lock so a fresh checkout resolves the exact same jars.
  • See the jar-dependencies README
    for advanced options: version ranges, exclusions, and scope declarations.
← All recipes