Manage Java dependencies with jar-dependencies
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.lockso everyone resolves the same
versions.lock_jarsdownloads them into each machine’s Maven cache on first
run; re-run it whenever you change therequirements. - Shareable — packaged as a
java-platform gem (withjar-dependenciesas a
runtime dependency), jar-dependencies can vendor the jars for consumers at
gem installtime, 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_jarsresolves the combined dependency graph in one pass. - Commit
Jars.lockso a fresh checkout resolves the exact same jars. - See the jar-dependencies README
for advanced options: version ranges, exclusions, and scope declarations.