{"id":47750924,"url":"https://github.com/exabrial/petrify","last_synced_at":"2026-04-20T18:00:35.736Z","repository":{"id":348106357,"uuid":"1196522670","full_name":"exabrial/petrify","owner":"exabrial","description":"Compile your ML Models/ONNX Decision Trees (XGBoost, LightGBM, SciKit-Learn) straight to JVM Bytecode","archived":false,"fork":false,"pushed_at":"2026-04-20T16:07:15.000Z","size":793,"stargazers_count":6,"open_issues_count":3,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-04-20T16:23:20.110Z","etag":null,"topics":["bytecode","compiler","decision-trees","inference","java","jvm","lightgbm","machine-learning","onnx","scikit-learn","tree-ensemble","xgboost"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"eupl-1.2","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/exabrial.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":"contributing.md","funding":null,"license":"license.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-30T19:29:15.000Z","updated_at":"2026-04-20T16:07:33.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/exabrial/petrify","commit_stats":null,"previous_names":["exabrial/petrify"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/exabrial/petrify","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/exabrial%2Fpetrify","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/exabrial%2Fpetrify/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/exabrial%2Fpetrify/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/exabrial%2Fpetrify/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/exabrial","download_url":"https://codeload.github.com/exabrial/petrify/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/exabrial%2Fpetrify/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32059139,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-20T11:35:06.609Z","status":"ssl_error","status_checked_at":"2026-04-20T11:34:48.899Z","response_time":94,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bytecode","compiler","decision-trees","inference","java","jvm","lightgbm","machine-learning","onnx","scikit-learn","tree-ensemble","xgboost"],"created_at":"2026-04-03T03:06:09.324Z","updated_at":"2026-04-20T18:00:35.726Z","avatar_url":"https://github.com/exabrial.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 🪨 Petrify \n\n🪵 -\u003e 🪨 ML models -\u003e JVM bytecode compiler. Models go in, fossils (bytecode) come out!\n\n⭐ Before you leave, ⭐ Leave a star! ⭐ Thanks! :)\n\n## Overview\n\n### Theory of operation\n\nPetrify is a machine learning model compiler for the the JVM. It reads your model from an ONNX, LightGBM native, or scikit-learn JSON format, walks the Trees or Linear models, and encodes the model in equivalent JVM bytecode as a stateless class you can invoke.\n\nThis differs from every other ONNX Runtime that I know of, which are essentially interpreters.\n\nA `Grove` or a `Vine` is the IR (intermediate representation) of your model. The resulting `Fossil` is the compiled equivelant of your model.\n\n```java\n// Load a grove:\nfinal Grove = arborist.toGrove(\"/class/path/to/model.onnx\");\n// Or you can load ONNX from a byte[]\n\n// Compile your model as bytecode:\nfinal ClassifierFossil fossil = petrify.fossilize(MethodHandles.lookup(), grove);\n\n// Now make as many predictions as you like:\nfinal int prediction = fossil.predict(new float[] { 1.0f, 2.0f, 3.0f, 4.0f }));\n\n// Do something with your prediction:\nif (prediction != SPANISH_INQUISITION) {\n  // proceed with ruthless efficiency and almost fanatical dedication\n} else {\n  throw new UnexpectedInquistionException();\n}\n```\n\nNo interpretation, no JNI, no giant runtime, no massive list of dependencies, no array traversal, no pointer chasing; just raw comparisons, conditional jumps, and arithmetic petrified as bytecode. Your model executes as native JVM instructions.\n\nOnce your ONNX models are compiled, the only runtime dependency is the `Fossil` interface from the `petrify-model` submodule (ASL2-licensed, safe for business). This interface provides the entry point into your model. Once your model is compiled, no execution runtime is needed, making Petrify the ultimate lightweight champ to run your models.\n\n\n## Model Coverage\n\n### Importers\n\n| Module | Format | Precision | Model Types |\n|---|---|---|---|\n| `petrify-import-onnx` | ONNX (`.onnx`) | F32 | Tree ensembles, linear models |\n| `petrify-import-lightgbm` | LightGBM native text (`.txt`) | F64 | LightGBM tree ensembles |\n| `petrify-import-scikit` | scikit-learn JSON (`.json`) | F64 | Linear/logistic regression |\n\n### Precision modes\n\nEach `Grove` and `Vine` carries a `PrecisionMode` (`F32` or `F64`) that controls whether the compiled bytecode performs arithmetic in 32-bit float or 64-bit double precision. ONNX imports default to `F32` to match the ONNX specification; LightGBM native and scikit-learn JSON imports default to `F64`. You can override precision before fossilizing by setting `grove.precisionMode = PrecisionMode.F64`. Both `predict(float[])` and `predict(double[])` overloads are available on the compiled fossil regardless of precision mode.\n\nThe LightGBM native text importer (`petrify-import-lightgbm`) in F64 mode produces **bit-identical** IEEE 754 output to the official LightGBM C/C++ runtime (at least on  my machine). To achieve this, use `double[]` features so that feature values enter the F64 computation at full precision; using `float[]` introduces F32 quantization on the inputs and will produce small deltas (~1e-06).\n\n### Supported ONNX Operators\n\n\n| ONNX Operator | Task | Status |\n|---|---|---|\n| `TreeEnsembleClassifier` | Classification (binary \u0026 multiclass) | ✅ Supported |\n| `TreeEnsembleRegressor` | Regression | ✅ Supported |\n| `TreeEnsemble` | Classification / Regression | ✅ Supported |\n| `LinearClassifier` | Classification (binary \u0026 multiclass) | ✅ Supported |\n| `LinearRegressor` | Regression | ✅ Supported |\n| `SVMClassifier` | Classification | Planned |\n| `SVMRegressor` | Regression | Planned |\n\n\nTree node modes supported:\n\n* `BRANCH_LEQ`\n* `BRANCH_LT`\n* `BRANCH_GEQ`\n* `BRANCH_GTE`\n* `BRANCH_GT`\n* `BRANCH_EQ`\n* `BRANCH_NEQ`\n\nPassthrough operators (safely ignored during import):\n\n* `Cast`\n* `ZipMap`\n* `Normalizer`\n* `Identity`\n\n### Supported Frameworks\n\nAny framework that exports to a supported ONNX operator should work. The table below lists known-compatible frameworks and model types.\n\n| Framework | Model Type | Task | Importer | Test Included |\n|-----------|-----------|------|----------|--------|\n| XGBoost | `XGBClassifier` | Binary classification | ONNX | ✅ |\n| XGBoost | `XGBClassifier` | Multiclass classification | ONNX | ✅ |\n| LightGBM | `LGBMClassifier` | Multiclass classification | ONNX, Native | ✅ |\n| LightGBM | `LGBMRegressor` | Regression | ONNX, Native | ✅ |\n| CatBoost | `CatBoostClassifier` | Multiclass classification | ONNX | ✅ |\n| CatBoost | `CatBoostRegressor` | Regression | ONNX | ✅ |\n| scikit-learn | `DecisionTreeClassifier` | Binary classification | ONNX | ✅ |\n| scikit-learn | `RandomForestClassifier` | Multiclass classification | ONNX | ✅ |\n| scikit-learn | `RandomForestRegressor` | Regression | ONNX | ✅ |\n| scikit-learn | `ExtraTreesClassifier` | Multiclass classification | ONNX | ✅ |\n| scikit-learn | `ExtraTreesRegressor` | Regression | ONNX | ✅ |\n| scikit-learn | `GradientBoostingClassifier` | Multiclass classification | ONNX | ✅ |\n| scikit-learn | `GradientBoostingRegressor` | Regression | ONNX | ✅ |\n| scikit-learn | `LogisticRegression` | Multiclass classification | ONNX, JSON | ✅ |\n| scikit-learn | `LinearRegression` | Regression | ONNX, JSON | ✅ |\n| XGBoost | `XGBRegressor` | Regression | ONNX | ✅ |\n| scikit-learn | `HistGradientBoostingClassifier` | Classification | ONNX | |\n| scikit-learn | `HistGradientBoostingRegressor` | Regression | ONNX | |\n\n## Requirements\n\n* Compiler: JDK25\n* Runtime: JDK17\n\nBecause Petrify uses the new Bytecode/Class-File API that was introduced in JEP-484, **JDK 25 is required** to run the Petrify compiler (including the Maven plugin). However, compiled fossils target JDK 17 bytecode and can run on JDK 17+.\n\nAs such, the `petrify-model` module (containing the `Fossil` interfaces) is JDK 17 compatible and is the only runtime dependency your application needs.\n\n## Usage\n\n### Maven Coordinates\n\n```xml\n\u003c!-- Fossil interfaces (ASL2 licensed) --\u003e\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.github.exabrial\u003c/groupId\u003e\n  \u003cartifactId\u003epetrify-model\u003c/artifactId\u003e\n  \u003cversion\u003e1.2.0\u003c/version\u003e\n  \u003cscope\u003ecompile\u003c/scope\u003e\n\u003c/dependency\u003e\n\n\u003c!-- Compiler and libs --\u003e\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.github.exabrial\u003c/groupId\u003e\n  \u003cartifactId\u003epetrify\u003c/artifactId\u003e\n  \u003cversion\u003e1.2.0\u003c/version\u003e\n  \u003cscope\u003ecompile\u003c/scope\u003e\n\u003c/dependency\u003e\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.github.exabrial\u003c/groupId\u003e\n  \u003cartifactId\u003epetrify-compiler-model\u003c/artifactId\u003e\n  \u003cversion\u003e1.2.0\u003c/version\u003e\n  \u003cscope\u003ecompile\u003c/scope\u003e\n\u003c/dependency\u003e\n\n\u003c!-- ONNX importer (f32 \"float\" precision) --\u003e\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.github.exabrial\u003c/groupId\u003e\n  \u003cartifactId\u003epetrify-import-onnx\u003c/artifactId\u003e\n  \u003cversion\u003e1.2.0\u003c/version\u003e\n  \u003cscope\u003ecompile\u003c/scope\u003e\n\u003c/dependency\u003e\n\n\u003c!-- LightGBM native text importer (f64 \"double\" precision) --\u003e\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.github.exabrial\u003c/groupId\u003e\n  \u003cartifactId\u003epetrify-import-lightgbm\u003c/artifactId\u003e\n  \u003cversion\u003e1.2.0\u003c/version\u003e\n  \u003cscope\u003ecompile\u003c/scope\u003e\n\u003c/dependency\u003e\n\n\u003c!-- scikit-learn JSON importer (f64 \"double\" precision) --\u003e\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.github.exabrial\u003c/groupId\u003e\n  \u003cartifactId\u003epetrify-import-scikit\u003c/artifactId\u003e\n  \u003cversion\u003e1.2.0\u003c/version\u003e\n  \u003cscope\u003ecompile\u003c/scope\u003e\n\u003c/dependency\u003e\n```\n\n### Compiling a model at runtime\n\nTree ensemble models (XGBoost, LightGBM, CatBoost, scikit-learn trees) use `OnnxArborist` to produce a `Grove`. Linear models (LogisticRegression, LinearRegression) use `OnnxVintner` to produce a `Vine`. Both are then compiled to bytecode with `Petrify.fossilize()`.\n\n#### Grove classifier (tree ensemble)\n\n```java\nfinal Arborist arborist = new OnnxArborist();\nfinal ClassifierGrove grove = arborist.toGrove(\"/models/xgboostClassifier.onnx\");\n\nfinal Petrify petrify = new Petrify();\nfinal ClassifierFossil fossil = petrify.fossilize(MethodHandles.lookup(), grove);\n\nfinal int prediction = fossil.predict(new float[] { 1.0f, 2.0f, 3.0f, 4.0f });\n```\n\n#### Grove regressor (tree ensemble)\n\n```java\nfinal Arborist arborist = new OnnxArborist();\nfinal RegressorGrove grove = arborist.toGrove(\"/models/xgboostRegressor.onnx\");\n\nfinal Petrify petrify = new Petrify();\nfinal RegressionFossil fossil = petrify.fossilize(MethodHandles.lookup(), grove);\n\nfinal float prediction = fossil.predict(new float[] { 8.3252f, 41.0f, 6.9841f, 1.0238f, 322.0f, 2.5556f, 37.88f, -122.23f });\n```\n\n#### Vine classifier (linear model)\n\n```java\nfinal Vintner vintner = new OnnxVintner();\nfinal ClassifierVine vine = vintner.toVine(\"/models/logisticRegression.onnx\");\n\nfinal Petrify petrify = new Petrify();\nfinal ClassifierFossil fossil = petrify.fossilize(MethodHandles.lookup(), vine);\n\nfinal int prediction = fossil.predict(new float[] { 1.6812f, 25.0f, 4.1922f, 1.0223f, 1392.0f, 3.8774f, 36.06f, -119.01f });\n```\n\n#### Vine regressor (linear model)\n\n```java\nfinal Vintner vintner = new OnnxVintner();\nfinal RegressorVine vine = vintner.toVine(\"/models/linearRegression.onnx\");\n\nfinal Petrify petrify = new Petrify();\nfinal RegressionFossil fossil = petrify.fossilize(MethodHandles.lookup(), vine);\n\nfinal float prediction = fossil.predict(new float[] { 8.3252f, 41.0f, 6.9841f, 1.0238f, 322.0f, 2.5556f, 37.88f, -122.23f });\n```\n\n### Pre-compiling models at build time with the Maven plugin\n\nThe `petrify-maven-plugin` compiles ML models to JVM bytecode during your build. The compiled `.class` files are written to your project's output directory and packaged into your jar automatically.\n\n**Important:** The Maven plugin runs in-process and loads JDK 25 classes. Your Maven process must be running on JDK 25, even if your project targets JDK 17.\n\n#### Plugin configuration\n\n```xml\n\u003cbuild\u003e\n  \u003cplugins\u003e\n    \u003cplugin\u003e\n      \u003cgroupId\u003ecom.github.exabrial\u003c/groupId\u003e\n      \u003cartifactId\u003epetrify-maven-plugin\u003c/artifactId\u003e\n      \u003cversion\u003e1.2.0\u003c/version\u003e\n      \u003cexecutions\u003e\n        \u003cexecution\u003e\n          \u003cgoals\u003e\n            \u003cgoal\u003efossilize\u003c/goal\u003e\n          \u003c/goals\u003e\n          \u003cconfiguration\u003e\n            \u003cfossils\u003e\n              \u003cfossil\u003e\n                \u003cmodelFile\u003evolcanicRiskClassifier.onnx\u003c/modelFile\u003e\n                \u003cimporter\u003eonnx\u003c/importer\u003e\n                \u003cmodelType\u003eclassifier\u003c/modelType\u003e\n                \u003ctargetPackageName\u003ecom.example.models\u003c/targetPackageName\u003e\n              \u003c/fossil\u003e\n            \u003c/fossils\u003e\n          \u003c/configuration\u003e\n        \u003c/execution\u003e\n      \u003c/executions\u003e\n    \u003c/plugin\u003e\n  \u003c/plugins\u003e\n\u003c/build\u003e\n```\n\nModel files are resolved from `src/main/models/` by default.\n\n#### Fossil configuration reference\n\n| Parameter | Required | Description |\n|---|---|---|\n| `modelFile` | Yes | Filename of the model relative to the model directory |\n| `importer` | Yes | `onnx`, `lightgbm`, or `scikit` |\n| `modelType` | Yes | `classifier` or `regressor` |\n| `targetPackageName` | Yes | Java package for the generated class |\n| `targetClassName` | No | Class name stem (defaults to the model filename, PascalCased). `Fossil` is always appended. |\n| `modelDirectory` | No | Override the model directory (defaults to `src/main/models/`) |\n| `featureNames` | No | Comma-separated feature names in column order. Enables `FeatureMapper` on the compiled `Fossil`. |\n| `ignoreFeatureNamesFromModel` | No | If `true`, discard any feature names embedded in the model file. Defaults to `false`. |\n| `modelName` | No | Model name metadata. Stored on the compiled `Fossil`. |\n| `modelVersion` | No | Model version metadata. Stored on the compiled `Fossil`. |\n\n\n#### Skipping execution\n\n```bash\nmvn compile -Dpetrify.skip=true\n```\n\n#### Using the compiled fossil\n\nYour project only needs `petrify-model` as a runtime dependency:\n\n```xml\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.github.exabrial\u003c/groupId\u003e\n  \u003cartifactId\u003epetrify-model\u003c/artifactId\u003e\n  \u003cversion\u003e1.2.0\u003c/version\u003e\n  \u003cscope\u003ecompile\u003c/scope\u003e\n\u003c/dependency\u003e\n```\n\nThe generated class is available on the classpath like any other compiled class:\n\n```java\nfinal ClassifierFossil fossil = new VolcanicRiskFossil();\nfinal int prediction = fossil.predict(new float[] { 1.0f, 2.0f, 3.0f, 4.0f });\n```\n\n### FeatureMapper\n\nIf your model's `Fossil` contains feature name metadata, you can use `FeatureMapper` to convert a `Map\u003cString, Object\u003e` to the positional primitive array your model expects. This avoids hand-maintaining index-aligned arrays that must stay synchronized with the model's training feature order.\n\n```java\n// ONNX does not carry feature name metadata, so set it yourself before fossilizing:\ngrove.metadata = new ModelMetadata();\ngrove.metadata.featureNames = new String[] { \"MedInc\", \"HouseAge\", \"AveRooms\", \"AveBedrms\", \"Population\", \"AveOccup\", \"Latitude\", \"Longitude\" };\n\n// Other importers (LightGBM native, scikit-learn JSON) read feature names from the model file by default.\n```\n\n```java\nfinal FeatureMapper mapper = new FeatureMapper(fossil);\n\nfinal Map\u003cString, Object\u003e features = Map.of(\n    \"MedInc\", 8.3252,\n    \"HouseAge\", 41.0,\n    \"AveRooms\", 6.984,\n    \"AveBedrms\", 1.024,\n    \"Population\", 322.0,\n    \"AveOccup\", 2.556,\n    \"Latitude\", 37.88,\n    \"Longitude\", -122.23\n);\n\nfinal float[] f32 = mapper.mapToF32(features);\nfinal double[] f64 = mapper.mapToF64(features);\n```\n\nNull map values are mapped to `NaN` (the ONNX missing-value sentinel), `Number` subtypes are narrowed to the target precision, and `Boolean` values are mapped to `1.0`/`0.0`. Unsupported types throw `FossilUnconformity`.\n\n### Known Limitations\n\n- **String class labels are not supported.** ONNX classifiers can store labels as either `classlabels_ints` or `classlabels_strings`. Petrify only supports integer labels at this time. Models trained on string targets (e.g., `[\"cat\", \"dog\", \"fish\"]`) must be label-encoded to `integer`s before export.\n\n## Bootnotes\n\n### Why the Geology theme?\n\nA tribute to my father; a Geologist. Although I never studied his area of science, growing up around it I learned a ton through osmosis.\n\nAnd... like every other good project name is taken.\n\n### Motivation (Why compile to native?)\n\nWe needed fast, lightweight tree ensemble inference on the JVM without dragging in a heavyweight runtime or relying on interpretation. The java source code route is also really messy, triggers many source code alarms, and is a sharp point in builds.\n\nWe looked at the existing options and weren't happy with any of them.\n\n* ONNX Runtime (Java binding)\n    - https://onnxruntime.ai\n    - The reference ONNX inference engine\n    - Interpretted mode; does not compile to native JVM bytecode.\n    - Chases pointers across the JVM Heap\n    - Requires a large native shared library (~150MB+ depending on platform) bundled via JNI\n    - Brings significant transitive dependencies\n    - No pure Java option; platform-specific binaries required\n* Tribuo (Oracle)\n    - https://github.com/oracle/tribuo\n    - Just delegates to ONNX Runtime\n* JPMML-Evaluator\n    - https://github.com/jpmml/jpmml-evaluator\n    - Mature PMML-based model evaluator\n    - Interpretted mode; does not compile to native JVM bytecode\n    - AGPL License (or commercial) may be less than ideal\n* JPMML-Transpiler\n    - https://github.com/jpmml/jpmml-transpiler\n    - Compiles PMML models to Java source code\n    - Operates on PMML (not ONNX) and generates `.java` source files rather than bytecode\n    - License may be less than ideal\n* m2cgen\n    - https://github.com/BayesWitnesses/m2cgen\n    - Transpiles trained Python model objects `.java` source file\n    - Giant nest of `if/then/else`\n    - Runs into 64kb method limits\n    - Reads scikit-learn, XGBoost, LightGBM model objects directly, not ONNX\n    - Last commit was 2022\n    - No runtime dependencies in the generated code, hey nice!\n\nPetrify takes a different approach: compile the tree ensemble directly to JVM bytecode. The result is a plain Java class with no runtime dependencies beyond the `Fossil` interface. No JNI, no native libraries, no interpretation loop, no Java source conversion step.\n\n\n### License and other boring legal notes\n\n- All files in this project are copyrighted\n- All files in `petrify-model` are Apache Source Licensed (ASL2.0)\n    - This is done so your models extend from and use ASL2.0 classes at runtime\n- All files in `petrify-onnx-proto` are Apache Source Licensed (ASL2.0)\n    - `onnx-ml.proto` copied from the main ONNX project. Their license and rights are maintained.\n- All all other files in this project are licensed under EUPL-1.2\n    - This license allows you to safely use unmodified/un-extended code in closed-source commercial projects, without revealing your company's proprietary application code in most cases.\n    - However: Note that if you modify/extend Petrify, distribute it, and/or offer online access to apps through a modified/extended Petrify, it is required by law that the source code for your Petrify changeset be made available _first_, before offering said access to your app or distribution.\n    - Again, this does not include your proprietary application source code, just the changeset to Petrify.\n- ONNX, XGBoost, LightGBM, scikit-learn, and other names are trademarks; this project is not endorsed by nor affiliated with them.\n\n### Verifying artifacts\n\nAll release artifacts are signed with Jonathan's GPG key (at this time; more committers would be awesome). You can verify signatures using the following public key:\n\n```\nFingerprint: 871638A21A7F2C38066471420306A354336B4F0D\n```\n\nTo import the key and verify artifacts in your local Maven repository:\n\n```bash\ngpg --keyserver keyserver.ubuntu.com --recv-keys 871638A21A7F2C38066471420306A354336B4F0D\n\nfind ~/.m2/repository/com/github/exabrial/petrify* -name '*.asc' -exec gpg --verify {} \\;\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fexabrial%2Fpetrify","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fexabrial%2Fpetrify","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fexabrial%2Fpetrify/lists"}