Quick Start
SMILE is a comprehensive, high-performance machine learning engine for the JVM. SMILE v6 requires Java 25. Besides Java, SMILE also provides idiomatic APIs for Scala and Kotlin. The core module is self-contained (no native dependencies required for most algorithms). Optionally, native BLAS/LAPACK libraries can be used for accelerated linear algebra, and LibTorch (PyTorch C++) enables GPU-accelerated deep learning and LLM inference.
Installation
Add the relevant artifact(s) to your build tool. All artifacts are published to Maven Central.
<!-- Core ML algorithms (classification, regression, clustering, …) -->
<dependency>
<groupId>com.github.haifengl</groupId>
<artifactId>smile-core</artifactId>
<version>6.0.1</version>
</dependency>
<!-- Deep learning + LLMs (requires LibTorch native libraries) -->
<dependency>
<groupId>com.github.haifengl</groupId>
<artifactId>smile-deep</artifactId>
<version>6.0.1</version>
</dependency>
<!-- Natural language processing -->
<dependency>
<groupId>com.github.haifengl</groupId>
<artifactId>smile-nlp</artifactId>
<version>6.0.1</version>
</dependency>
<!-- Swing + Vega-Lite data visualization -->
<dependency>
<groupId>com.github.haifengl</groupId>
<artifactId>smile-plot</artifactId>
<version>6.0.1</version>
</dependency>
dependencies {
// Core ML algorithms
implementation("com.github.haifengl:smile-core:6.0.1")
// Idiomatic Kotlin API (wraps smile-core)
implementation("com.github.haifengl:smile-kotlin:6.0.1")
// Deep learning + LLMs
implementation("com.github.haifengl:smile-deep:6.0.1")
// NLP
implementation("com.github.haifengl:smile-nlp:6.0.1")
// Visualization
implementation("com.github.haifengl:smile-plot:6.0.1")
}
// Idiomatic Scala API (pulls in smile-core transitively)
libraryDependencies += "com.github.haifengl" %% "smile-scala" % "6.0.1"
// Deep learning
libraryDependencies += "com.github.haifengl" % "smile-deep" % "6.0.1"
Native BLAS / LAPACK (optional)
Several algorithms (manifold learning, Gaussian Process, MLP) benefit significantly from native BLAS/LAPACK. Install the system libraries:
sudo apt update
sudo apt install libopenblas-dev libarpack2
brew install arpack
# If macOS SIP strips DYLD_LIBRARY_PATH, copy the dylib to your working directory:
cp /opt/homebrew/lib/libarpack.dylib .
Pre-built DLLs are included in the bin/ directory of the
release package.
Add that directory to your PATH.
GPU / LibTorch (for Deep Learning & LLMs)
To use smile-deep, the LibTorch native libraries must be on
java.library.path. For GPU support, make sure your Bytedeco
pytorch classifier matches your CUDA version, e.g.
linux-x86_64-gpu-cuda12.4. CPU-only inference works out of the box.
Download the Release Package
For the interactive shell and CLI tools, download the self-contained release package from the releases page. It works on macOS, Linux, and Windows.
# Extract and set up native dependencies
cd bin
./setup # installs required native libraries
./smile # launches SMILE Studio (desktop notebook IDE)
Build from Source
Clone the repository and build with the Gradle wrapper (requires Java 25):
git clone https://github.com/haifengl/smile.git
cd smile
./gradlew build -x test # build all modules, skip tests
./gradlew :core:build -x test # build only the core module
To run the tests (requires LibTorch on the library path for smile-deep):
./gradlew :core:test
./gradlew :deep:test -DexcludeTags=integration
Interactive Shell
SMILE integrates with JShell (available since Java 9). In the home directory of the release package, run:
$ ./smile shell
All SMILE classes are pre-imported. To pass more heap memory:
$ ./smile shell -R-Xmx30G
When the shell starts you will see the SMILE banner followed by the JShell prompt:
| Welcome to Smile -- Version 6.0.1
===============================================================================
| Welcome to JShell -- Version 25
| For an introduction type: /help intro
smile>
All SMILE definitions are pre-imported. Type /exit to leave.
You can run any valid Java expression:
smile> 2 + 3
$1 ==> 5
smile> var iris = Read.arff("data/weka/iris.arff")
iris ==> [...]
smile> var rf = RandomForest.fit(Formula.lhs("class"), iris)
smile> System.out.println(rf.metrics())
Running a Script
// examples/iris.jsh
import smile.classification.RandomForest;
import smile.data.formula.Formula;
import smile.io.Read;
var data = Read.arff("data/weka/iris.arff");
var formula = Formula.lhs("class");
var rf = RandomForest.fit(formula, data);
System.out.println(rf.metrics());
$ ./smile shell examples/iris.jsh
The Scala REPL has all high-level SMILE operators pre-imported:
$ ./smile scala
$ ./smile scala -J-Xmx30G # with extra heap
smile> val iris = read.arff("data/weka/iris.arff")
smile> val rf = randomForest("class" ~, iris)
smile> println(rf.metrics)
// Tab-completion shows available classifiers:
smile> smile.classification.r
randomForest rbfnet rda
Running a Script
// examples/iris.sc
val data = read.arff("data/weka/iris.arff")
val formula = "class" ~ "."
val rf = smile.classification.randomForest(formula, data)
println(s"OOB error = %.2f%%" format 100 * rf.error)
$ ./smile scala examples/iris.sc
A Kotlin REPL is also available:
$ ./smile kotlin
smile> val iris = read.arff("data/weka/iris.arff")
smile> val rf = randomForest(Formula.lhs("class"), iris)
smile> println(rf.metrics())
Training & Inference CLI
The SMILE shell also exposes a command-line interface for training models, batch prediction, and real-time serving — no code required.
Train a Random Forest on the Iris dataset and save the model:
$ ./smile train \
--data data/weka/iris.arff \
--formula "class ~ ." \
--model iris_rf.sml \
random-forest
Training metrics: {
fit time: 191.678 ms,
score time: 17.059 ms,
validation data size: 150,
error: 6,
accuracy: 96.00%,
cross entropy: 0.1316
}
Run ./smile train -h for a full list of supported algorithms
(ada-boost, cart, gradient-boost, lasso, lda, logistic, mlp, ols, qda,
random-forest, rbf, rda, ridge, svm, …) and
./smile train random-forest -h for algorithm-specific options.
Run batch inference on a file using a saved model:
$ ./smile predict \
--model iris_rf.sml \
--probability \
data/weka/iris.arff
0 0.9601 0.0205 0.0194
0 0.9599 0.0206 0.0195
...
The optional --probability flag outputs posterior class probabilities
alongside the predicted label.
Start an HTTP inference server (powered by Quarkus):
$ ./smile serve --model iris_rf.sml
INFO Listening on: http://0.0.0.0:8080
# Query a single sample
$ curl -X POST http://localhost:8080/api/v1/models/iris_rf-1 \
-H "Content-Type: application/json" \
-d '{"sepallength":5.1,"sepalwidth":3.5,"petallength":1.4,"petalwidth":0.2}'
{"class":0,"probability":[0.9599,0.0207,0.0194]}
If --model points to a directory, every .sml file is
loaded automatically. Discover available endpoints with
GET /api/v1/models.
The /stream endpoint processes a JSON-lines or CSV request body
element-by-element with full back-pressure, making it safe to send very large
payloads (multi-GB):
# Generate 150,000 rows (iris × 1000) and stream them through the server
$ for i in {1..1000}; do
tail -n 153 data/weka/iris.arff | head -n 150
done | curl -H "Content-Type: text/plain" \
-X POST --data-binary @- \
http://localhost:8080/api/v1/models/iris_rf-1/stream
This completes end-to-end (including HTTP transfer and printing) in under 4 seconds for 150,000 samples.
SMILE Studio
SMILE Studio is an integrated desktop IDE for machine learning and data science. It combines an interactive multi-language notebook, an AI-powered agent panel, a file explorer, and a kernel explorer in a single window. Launch it from the release package:
cd bin
./setup # first-time: installs native libraries
./smile # launches SMILE Studio
SMILE Studio requires Java 25 and a graphical (non-headless) environment. At least 4 GB RAM; 8 GB recommended for large datasets.
Application Layout
| Panel | Description |
|---|---|
| Notebook (center) | Multi-cell editor supporting Java (JShell), Scala, and Python kernels.
Supports .java, .jsh, .scala,
.sc, .py, and .ipynb files. |
| AI Agent Panel (right) | Three built-in agents: Clair the Analyst (end-to-end ML),
James the Java Guru (Java/SMILE), and
Guido the Pythonista (Python). Supports slash commands,
shell execution, and long-term memory via SMILE.md. |
| Project Explorer (left) | File tree of the current working directory. Double-click any supported source file to open it in the notebook. |
| Kernel Explorer (left) | Live view of runtime variables grouped by type (DataFrames, Matrices, Models, Services).
Double-click a Model node to save it as a .sml file and register
it as an inference service. |
Execution Kernels
| Kernel | How to use | Notes |
|---|---|---|
| Java (JShell) | Open or create a .java / .jsh notebook |
Runs in a separate JVM; ZGC, 75% max RAM. New notebooks are pre-populated with all SMILE imports. |
| Scala | Open a .scala / .sc notebook |
JSR-223 script engine; all SMILE Scala operators available. |
| Python (iPython) | Open a .py / .ipynb notebook |
Requires pip install ipython. |
Key Shortcuts
| Shortcut | Action |
|---|---|
Ctrl + Enter | Run cell, stay in current cell |
Shift + Enter | Run cell, move to next cell (creates one if needed) |
Alt + Enter | Run cell, insert new cell below |
Tab | AI-powered line completion (requires AI service configured) |
Ctrl + = / Ctrl + - | Increase / decrease font size globally |
AI Agents & Magic Commands
Each cell has a Prompt field: type a natural language description and press Enter to have the AI generate code directly into the editor. The Java kernel also supports Maven dependency magic commands:
//!mvn org.apache.commons:commons-math3:3.6.1
// ↑ resolves the artifact transitively and adds it to the JShell classpath
In the Agent Panel, use slash commands to drive the full ML workflow without writing code:
| Command | Description |
|---|---|
/train | Train a model via smile train |
/predict | Run batch inference via smile predict |
/serve | Start an inference server via smile serve |
/plan <goal> | Enter planning mode for multi-step tasks |
/memory add <text> | Persist project context to SMILE.md |
/compact | Summarize the conversation to free context window space |
/clear | Clear the current agent conversation |
Configure the AI provider (OpenAI, Anthropic, Google Gemini, Azure OpenAI, or Vertex AI)
and API key under File > Settings….
MCP (Model Context Protocol) servers can be wired in via
.smile/mcp.json for extended tool access.
A Gentle Tutorial
This example demonstrates the end-to-end machine learning workflow in SMILE: loading data, training multiple models, evaluating performance, and making predictions.
1. Loading Data
SMILE supports many data formats: Parquet, Avro, Arrow, CSV, ARFF, LibSVM, JSON, and more. All parsers return a DataFrame.
import smile.io.*;
import org.apache.commons.csv.CSVFormat;
// ARFF file (e.g. Weka datasets)
var weather = Read.arff("data/weka/weather.nominal.arff");
// CSV with a space delimiter and no header row
var format = CSVFormat.DEFAULT.withDelimiter(' ');
var zipTrain = Read.csv("data/usps/zip.train", format);
var zipTest = Read.csv("data/usps/zip.test", format);
import smile.io._
val weather = read.arff("data/weka/weather.nominal.arff")
val zipTrain = read.csv("data/usps/zip.train", delimiter = " ", header = false)
val zipTest = read.csv("data/usps/zip.test", delimiter = " ", header = false)
import smile.io.*
val weather = Read.arff("data/weka/weather.nominal.arff")
val zipTrain = Read.csv("data/usps/zip.train")
Because the USPS data has no header, the parser names columns V1, V2, … The first column (V1) is the class label.
2. The Formula API
A Formula specifies the model in a symbolic, R-style way. The left-hand side (LHS) names the response variable; the right-hand side (RHS) lists predictors. When the RHS is omitted, all remaining columns are used. Terms can also include functions and interaction terms.
import smile.data.formula.Formula;
// Predict "V1" from all other columns
var formula = Formula.lhs("V1");
// Extract feature matrix and label array
var x = formula.x(zipTrain).toArray();
var y = formula.y(zipTrain).toIntArray();
val formula: Formula = "V1" ~ "."
val x = formula.x(zipTrain).toArray()
val y = formula.y(zipTrain).toIntArray()
3. Random Forest
Random Forest is an ensemble of decision trees trained on bootstrap samples of the data. It estimates accuracy via out-of-bag (OOB) samples, so a separate validation set is not strictly required.
import smile.classification.*;
import smile.data.formula.Formula;
var formula = Formula.lhs("V1");
var prop = new java.util.Properties();
prop.setProperty("smile.random.forest.trees", "200");
var forest = RandomForest.fit(formula, zipTrain, prop);
System.out.println(forest.metrics());
// Training metrics: {accuracy: 97.28%, ...}
val formula: Formula = "V1" ~ "."
val forest = randomForest(formula, zipTrain, ntrees = 200)
println(forest.metrics)
4. Support Vector Machine
SVM is a kernel-based classifier. It operates on raw arrays rather than DataFrames, so we extract arrays from the formula first. For multi-class problems, use one-vs-one or one-vs-rest strategies.
import smile.math.kernel.GaussianKernel;
import smile.validation.*;
var testx = formula.x(zipTest).toArray();
var testy = formula.y(zipTest).toIntArray();
var kernel = new GaussianKernel(8.0);
var svm = OneVersusOne.fit(x, y,
(xi, yi) -> SVM.fit(xi, yi, kernel, 5, 1E-3));
var pred = svm.predict(testx);
System.out.format("Accuracy = %.2f%%%n",
100.0 * Accuracy.of(testy, pred));
System.out.format("Confusion Matrix:%n%s%n",
ConfusionMatrix.of(testy, pred));
val testx = formula.x(zipTest).toArray()
val testy = formula.y(zipTest).toIntArray()
val kernel = new GaussianKernel(8.0)
val svm = ovo(x, y) { (xi, yi) =>
SVM.fit(xi, yi, kernel, 5, 1E-3)
}
val pred = svm.predict(testx)
println("Accuracy = %.2f%%" format (100.0 * Accuracy.of(testy, pred)))
5. Neural Network (MLP)
The classic SMILE MLP is a lightweight CPU-only network suitable for tabular data. Always standardize features before training a neural network.
import smile.base.mlp.*;
import smile.classification.MLP;
import smile.math.MathEx;
import smile.math.TimeFunction;
import smile.validation.metric.Accuracy;
// Standardize features
var scaler = smile.feature.Standardizer.fit(x);
var xn = scaler.transform(x);
var testxn = scaler.transform(testx);
var net = new MLP(
Layer.input(256),
Layer.sigmoid(768),
Layer.sigmoid(192),
Layer.sigmoid(30),
Layer.mle(10, OutputFunction.SIGMOID)
);
net.setLearningRate(TimeFunction.linear(0.01, 20000, 0.001));
for (int epoch = 0; epoch < 10; epoch++) {
for (int i : MathEx.permutate(xn.length)) {
net.update(xn[i], y[i]);
}
var pred = net.predict(testxn);
System.out.format("Epoch %d Accuracy = %.2f%%%n",
epoch, 100.0 * Accuracy.of(testy, pred));
}
import smile.base.mlp._
import smile.math.TimeFunction
val scaler = smile.feature.Standardizer.fit(x)
val xn = scaler.transform(x)
val testxn = scaler.transform(testx)
val net = new MLP(
Layer.input(256),
Layer.sigmoid(768),
Layer.sigmoid(192),
Layer.sigmoid(30),
Layer.mle(10, OutputFunction.SIGMOID)
)
net.setLearningRate(TimeFunction.linear(0.01, 20000, 0.001))
(0 until 10).foreach { epoch =>
MathEx.permutate(xn.length).foreach(i => net.update(xn(i), y(i)))
val pred = net.predict(testxn)
println(s"Epoch $epoch Accuracy = %.2f%%" format
(100.0 * Accuracy.of(testy, pred)))
}
6. Making Predictions
Call predict on a single sample or an array.
Soft classifiers (neural networks, random forest, logistic regression)
can also return posterior class probabilities.
// Hard prediction (class label only)
int label = forest.predict(zipTest.get(0));
int svmLabel = svm.predict(testx[0]);
// Soft prediction (class label + posterior probabilities)
var proba = new double[10];
forest.predict(zipTest.get(0), proba);
net.predict(testxn[0], proba);
val label = forest.predict(zipTest.get(0))
val svmLabel = svm.predict(testx(0))
val proba = new Array[Double](10)
forest.predict(zipTest.get(0), proba)
net.predict(testxn(0), proba)
7. Saving & Loading Models
Most SMILE models implement java.io.Serializable.
Serialize them to disk for production deployment or Spark scoring.
import java.io.*;
// Save
try (var out = new ObjectOutputStream(
new FileOutputStream("forest.ser"))) {
out.writeObject(forest);
}
// Load
try (var in = new ObjectInputStream(
new FileInputStream("forest.ser"))) {
var loaded = (RandomForest) in.readObject();
System.out.println(loaded.predict(zipTest.get(0)));
}
import java.io._
// Save
val out = new ObjectOutputStream(new FileOutputStream("forest.ser"))
out.writeObject(forest); out.close()
// Load
val in = new ObjectInputStream(new FileInputStream("forest.ser"))
val loaded = in.readObject().asInstanceOf[RandomForest]
in.close()
Deep Learning & LLM Quickstart
The smile-deep module wraps LibTorch (PyTorch C++) and provides
GPU-accelerated tensors, neural network layers, pretrained vision models, and a
complete LLaMA-3 inference stack.
See Deep Learning and LLM
for full documentation.
Image Classification with EfficientNet-V2
import smile.vision.EfficientNet;
import smile.vision.ImageNet;
import javax.imageio.ImageIO;
import java.io.File;
// Load pretrained EfficientNet-V2-S (21 M parameters, ImageNet top-1 ~84%)
var model = EfficientNet.V2S();
// The model's built-in Transform automatically resizes and normalises the image
var img = ImageIO.read(new File("dog.jpg"));
try (var logits = model.forward(img)) { // shape [1, 1000]
var probs = logits.softmax(1);
int classIdx = probs.argmax(1, false).intValue();
System.out.println(ImageNet.INSTANCE.labelOf(classIdx));
}
Training a Custom MLP with LibTorch
import smile.deep.*;
import smile.deep.layer.*;
import smile.deep.tensor.Tensor;
var mlp = new SequentialBlock(
Layer.relu(784, 256),
Layer.relu(256, 128),
Layer.logSoftmax(128, 10)
);
var optimizer = Optimizer.adam(mlp.asTorch().parameters(), 1e-3);
var criterion = Loss.nll();
mlp.train(10, trainDataset, criterion, optimizer,
new smile.deep.metric.Accuracy(), testDataset);
LLaMA-3 Chat Completion
import smile.llm.llama.Llama;
import smile.llm.Message;
var llama = Llama.build(
"model/Meta-Llama-3-8B-Instruct", // checkpoint directory
"model/Meta-Llama-3-8B-Instruct/tokenizer.model",
/*maxBatchSize=*/ 4,
/*maxSeqLen=*/ 2048,
/*deviceId=*/ (byte) 0 // CUDA:0; -1 for CPU
);
var reply = llama.chat(
new Message[]{
Message.system("You are a helpful assistant."),
Message.user("Explain gradient descent in one paragraph.")
},
/*maxGenLen=*/ 256, /*temperature=*/ 0.7,
/*topp=*/ 0.9, /*logprobs=*/ false, /*seed=*/ 0L, /*publisher=*/ null
);
System.out.println(reply.content());
For streaming output, pass a SubmissionPublisher<String> as the
last argument. See Large Language Models for full examples
including raw token generation and the OpenAI-compatible REST server.