Library / SDK
deepjavalibrary/djl avatar
deepjavalibrary/djl

DJL: An Engine-Agnostic Deep Learning Framework for Java

An Engine-Agnostic Deep Learning Framework in Java

4,856 stars759 forksJavaApache-2.0

At a glance

What is it?
Deep Java Library (DJL) lets Java teams load, train and serve models without committing to one deep learning engine. The abstraction is the selling point and also the main thing to verify before you build on it.
Who is it for?
Adopt DJL if your team is Java-first, wants to load models from a model zoo behind a typed Criteria API, and values being able to swap the underlying engine without rewriting application code. Do not adopt it if your pipeline is Python-centric, if you need training features that only exist in a specific engine's own Python API, or if you are not prepared to manage native engine artifacts alongside your JVM dependencies.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly Java, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

The gap DJL fills for JVM teams

Serving a model from a Java service usually means one of two paths. You either export the model and call a separate Python process over HTTP or gRPC, or you bind directly to a native inference library and write the glue yourself. The first adds a network hop and a second runtime to operate. The second ties your code to one engine's C or C++ API and its own notion of tensors, sessions and memory.

DJL targets the second path while trying to remove the engine lock-in. The README describes it as an "open-source, high-level, engine-agnostic Java framework for deep learning" and positions it for developers who, in its words, do not have to be a machine learning expert to get started. The topics list on the repository names the engines it spans: PyTorch, MXNet, TensorFlow and ONNX Runtime, alongside autograd and neural network support.

The intended audience is a Java engineer who already knows how to build and ship a service and now needs inference or training inside it. The framework's pitch is that you use your existing Java expertise, your existing IDE, and your existing build tooling, rather than standing up a Python stack next to your application.

Criteria, Predictor and the model zoo

The core inference mechanism is a builder called Criteria. You describe what you want rather than which engine provides it: an application type such as Application.CV.OBJECT_DETECTION, the input and output Java classes, and optional filters such as a backbone name. Criteria.loadModel() then resolves a model, and the README's example wraps both the model and a Predictor in try-with-resources, which tells you these hold native resources that must be released.

The typed pair is the part worth noticing. setTypes(Image.class, Classifications.class) means the framework is responsible for converting a Java Image into whatever tensor layout the selected engine expects, and converting the raw output back into a Classifications object. That conversion layer is where DJL earns its keep, and it is also where surprises live: the types you declare constrain which models can satisfy the criteria.

Training follows a parallel shape. You construct a Block, in the README's case a new Mlp(28 * 28, 10, new int[] {128, 64}), attach it to a Model created with Model.newInstance("mlp"), build a Trainer from a TrainingConfig, and call trainer.initialize(new Shape(1, 28 * 28)). The comment in the example is explicit that the first axis is the batch axis and that a value of 1 is acceptable for initialization. After that, EasyTrain.fit runs the loop over a training and a validation dataset. The datasets in the example come from Mnist.Builder with Usage.TRAIN and Usage.TEST.

So the architecture is three layers: a Java-facing API of Criteria, Model, Trainer and Predictor; a dataset and transform layer; and one or more engine bindings underneath. The engine-agnostic claim rests on that bottom layer being replaceable.

Getting a first model running

The README does not give a Maven or Gradle dependency block, so the exact coordinates have to come from the documentation site rather than this file. What the repository does show is the source build path, which is Gradle-based. On Linux or macOS you run ./gradlew build, and on Windows gradlew build. To skip unit tests and shorten the cycle, the README gives ./gradlew build -x test. For Eclipse users there is ./gradlew eclipse followed by file, import, gradle, existing gradle project, with a note to set the workspace text encoding to UTF-8.

On the application side, the keys that matter are the ones inside the Criteria builder: optApplication, setTypes, optFilter and build. If you are training instead of inferring, the keys move to the model and trainer setup: Model.newInstance with a name, setBlock, newTrainer with a TrainingConfig, trainer.initialize with a Shape, and model.save(modelDir, "mlp") to persist. The README also shows trainer.close() and model.close() as explicit cleanup, separate from the try-with-resources used in the inference example. Two different lifecycle styles in one README is a small signal that resource management is something you have to think about rather than inherit.

Because no dependency snippet appears in the material, treat the first setup step as reading docs/quick_start.md, which the README links under Getting Started, and confirming which engine artifact your platform pulls in.

Where the abstraction costs you

Engine-agnostic frameworks pay for portability at the boundary. DJL's own example already shows one constraint: trainer.initialize takes a Shape, and the comment explains that the batch axis is first and can be set to 1 for initialization. Get that shape wrong and the failure surfaces at initialization or on the first batch, not at compile time.

The larger cost is the lowest common denominator problem. An API that spans PyTorch, MXNet, TensorFlow and ONNX Runtime can only expose operations that all of them can express, or it must expose engine-specific escape hatches. The README does not describe those escape hatches, and the topics list mentions autograd without showing how custom gradients are written. If your work depends on a feature that exists in one engine's native API and not in the others, the abstraction is working against you rather than for you.

The second cost is the native layer. The README's inference example uses try-with-resources for ZooModel and Predictor, and the training example closes the trainer and model explicitly. That pattern exists because these objects hold memory outside the JVM heap. A Java service with a fixed container memory limit will need to account for native allocations that the JVM's own heap flags do not govern.

A third, quieter issue: the README lists releases 0.36.0, 0.35.1, 0.33.0, 0.32.0, 0.31.1 and 0.30.0, with a link to 29 further releases, and the version numbering sits below 1.0. That is a statement about API stability expectations, not about quality. Plan for the possibility of breaking changes on upgrade.

DJL against a Python sidecar

The most common alternative is not another Java framework. It is keeping PyTorch or TensorFlow in Python and exposing it to your Java service as a sidecar over HTTP or gRPC, typically with a model server in front.

The difference in approach is where the abstraction lives. With a sidecar, the Python process owns the model, the preprocessing and the postprocessing, and your Java code owns a client stub and a serialization format. You get the full engine API, including any operator or training trick that only exists upstream, because you are using the engine directly. You pay for it with a second runtime to deploy, a network hop on every prediction, and two languages in the same system.

DJL inverts that. The model runs in-process, the conversion between Java types and tensors is the framework's job, and there is no serialization boundary to design. You give up direct access to the engine's native surface and you accept the framework's release cadence as your own. For a team that is already Java-only and serving standard architectures, that trade is usually favorable. For a team doing research-adjacent work where the model changes weekly, the sidecar keeps the experimentation loop in the language the ecosystem is built around.

ONNX Runtime deserves a separate mention because it appears in this repository's own topics. Using it directly is a narrower commitment: you get one engine, no engine-switching story, but also a smaller dependency surface and no framework layer between you and the runtime.

Maintenance, releases and the licence

The repository is not archived, and the last push recorded is 2026-09-10. Releases v0.38.0 and v0.37.0 are dated within a day of each other in September 2026, while v0.36.0 is dated 2025-12-16. That clustering suggests the project can ship rapidly when it needs to, but it also means the gap between 0.36.0 and 0.37.0 was roughly nine months. If you pin a version, check what accumulated in that window before upgrading.

The README points to several maintenance surfaces you inherit: a documentation site, a JavaDoc API reference, a demos index, and a book, Dive into Deep Learning, in a Java version. The community section lists a Slack channel, an X account and a Zhihu column. Those are the channels where engine-binding questions get answered, and they matter more here than in a single-engine library because a problem may live in DJL or in the engine underneath it.

On licensing: the repository states it is licensed under Apache-2.0, with a LICENSE file at the root. That is a permissive licence, but DJL resolves to native engine artifacts, and those artifacts carry their own licences, which may differ from Apache-2.0. The README does not enumerate them. If you are shipping a product, check the licences of the specific engine artifacts your build pulls in, not just the licence of DJL itself. This is a description of what the material shows, not legal advice.

Upgrade cost is dominated by the native layer rather than by Java source changes. A version bump can change which engine build you get, and that is the thing to test against your own inputs before rolling it out.

What to check before you commit

Start with the model zoo. The README's inference example uses optApplication and optFilter to select a model by task and by backbone, which means the model you want has to be present with matching input and output types. Confirm that for your specific architecture rather than assuming the filter will match something close.

Next, confirm the engine resolution. Because DJL is engine-agnostic, the artifact set depends on your target platform and on which engine you intend to run. The README does not document this, so the answer lives in docs/quick_start.md and the documentation site. Check it for your deployment target, including whether GPU support is what you expect.

Then look at the lifecycle in your own code. The README shows try-with-resources for ZooModel and Predictor, and explicit close calls for Trainer and Model. Predictor is not documented as thread-safe in this material, and no statement about concurrency appears at all. If you plan to share one predictor across request threads, verify that against the JavaDoc before you design around it.

Finally, decide what your fallback is. If the abstraction turns out not to cover a model you need, you either drop to the engine's native API or move that model to a sidecar. Knowing which one you would pick, before you have written the service, is cheaper than discovering it in production.

Editorial conclusion

Adopt DJL if your team is Java-first, wants to load models from a model zoo behind a typed Criteria API, and values being able to swap the underlying engine without rewriting application code. Do not adopt it if your pipeline is Python-centric, if you need training features that only exist in a specific engine's own Python API, or if you are not prepared to manage native engine artifacts alongside your JVM dependencies. Before committing, verify three things against the version you plan to pin: which engine artifacts your target platform resolves to, whether the model you intend to serve is actually present in the model zoo with the input and output types you need, and how the NDArray and Predictor lifecycle interacts with your existing thread pool.

Official sources

  1. deepjavalibrary/djl on GitHub
  2. License: Apache-2.0
  3. Project website
  4. README
  5. Releases
Community notes

Community notes