JavaObf
Docs & quickstart

From a stock JAR to a protected one in five minutes.

No CLI, no build plugin, no Gradle config. JavaObf runs in the browser. Drop a JAR, pick a profile, ship the result. The pages below cover the workflow, the modules at a level you actually need, and the things that bite first-time users.

~5 minute first run No agent, no plugin Descriptor-aware locks

1. Quickstart

Three steps. Skip the rest of this page until you have actually shipped one protected build. Reading docs about a tool you have not used is the slowest path.

  1. 01
    Sign in. Use the email you want to bind your license to. Free tier needs no card. Create an account.
  2. 02
    New project, drop a JAR. The engine parses the descriptors (plugin.yml, paper-plugin.yml, fabric.mod.json, mods.toml, mixins, access wideners) and builds the analysis graph. You will see the explorer fill in within a few seconds.
  3. 03
    Open the Protect tab and click Build. Default profile is balanced. The first build typically finishes in under a minute. Download the JAR and the mapping file. Test the JAR in your dev server before shipping.
The first build is the cheapest sanity check you'll ever run. If your plugin loads against the protected output, the rest of the work is tuning, not surgery.

2. Your first protected build

Open a project after upload. You'll see four tabs: Overview, Call graph, CFG, and Protect.

  • Overview shows the class you have selected, its methods, and the strings it embeds.
  • Call graph shows who calls who. Use it to find the methods your value depends on.
  • CFG shows the basic blocks of the active method. Useful when deciding what to harden.
  • Protect is where you choose a profile and queue the build.

For the first build, leave the profile on balanced. The engine has opinionated defaults that work for the vast majority of plugins. You'll add targeting rules in later builds, not the first one.

Click Build. The build queues, runs, and produces a row in the builds list. When it turns green, the JAR and the mapping file are downloadable.

3. Reading the build output

A successful build produces four artifacts:

protected.jar The hardened binary. Ship this.
mapping.json Original-to-obfuscated symbol map. Keep this private. You need it to deobfuscate stack traces.
report.json What the engine did, broken down by module. Useful for sanity checks and for auditing.
fingerprint.json The watermark embedded in this specific build. If a copy leaks, this identifies the source.

Test protected.jar in a real server before you ship it. The engine locks descriptors automatically, but every plugin has its own quirks (reflection, custom mixins, native libs). The first run in a real Paper or Velocity server is where those surface.

Concept: profiles

A profile is a named bundle of settings. Three ship with the engine:

quick-sanity

Identifier rename and basic string concealment. Fastest build, smallest impact, cheapest to test compatibility against.

balanced

Symbol distortion, string concealment, control-flow flattening, integrity mesh, decoy classes. The sane default for paid plugins. Used in 80% of builds.

max-hardening-plus

Everything in balanced plus selective VM, anti-patch interlocks, dispatcher flattening. Heaviest profile. Use this on classes that encode actual value.

You can override any module on top of a profile. The profile sets the floor; rules and toggles raise or lower it per class.

Concept: presets

A preset is your own saved profile, scoped to a project (or a JAR by hash). Build a config you like once, save it as a preset, and apply it to the next plugin without re-tuning the same 22 modules.

Presets are useful when you ship multiple variants of the same plugin (paid versions, gift versions, demo versions). Each variant gets a different preset so a leaked demo cannot be patched into a paid copy.

Concept: locks & entrypoints

The engine locks symbols that the platform must see by their original name. Two kinds of lock:

  • Hard lock. Required by the platform. The main class in plugin.yml, the bootstrap class in fabric.mod.json, registered listeners. These never get renamed.
  • Soft lock. Convention-based. onEnable, onDisable, common reflection patterns. The engine warns before touching these.

You can add custom locks via the rules panel if your plugin uses an unusual convention (a custom service loader, a reflective bootstrap, a third-party plugin API). The rules are simple regex over class or method patterns.

Concept: targeting rules

Rules let you raise or lower the protection floor on specific code. Five chips show up in the right inspector when you select a class or method:

LHard-lock class HMax-harden method SSkip rename for class RRelax this method VLift to VM

The general rule of thumb: hard-lock classes that the platform reflects on, max-harden methods that encode actual value (license verification, anti-cheat checks, custom algorithms), relax methods that need to stay debuggable in production logs.

Module guide

The 22 modules group into five families. The descriptions below are the level you actually need to make decisions. The exact transforms each module applies are not public for the same reason your locks would not be either.

Identifiers 6 modules

Symbol distortion, layout scrambling, package-layout scrambling, resource-aware rename, reflection lock tightening, class-literal concealment.

When to enable: always, except on demos you want a reverser to be able to read. The cheapest tier of protection and the one that breaks the casual decompiler.

Watch out for: reflective lookups in your own code that depend on class or method names. Use S rules to skip rename on those, or refactor the lookup to use a constant the engine can track.

Strings & literals 5 modules

Concealment, invokedynamic strings, class-string rewriting, broad string encryption, concat-template concealment.

When to enable: always for paid plugins. Strings are the first thing a reverser greps for. License URLs, error messages, and config keys leak the design.

Watch out for: log messages get rewritten too. If your support workflow relies on grepping logs for a phrase, exempt the logger class with a S rule on the strings module.

Control flow 4 modules

CFG distortion, dispatcher flattening, method splitting, arithmetic distortion.

When to enable: always for the methods your value sits in. Decompilers rely on recognizable control-flow shapes. Once those go, the cost of reading the method jumps from minutes to a serious afternoon of dynamic analysis.

Watch out for: hot inner loops can take a small performance hit when aggressively flattened. Use R on tight game loops or per-tick handlers.

Anti-tamper 4 modules

Integrity mesh, anti-patch interlocks, decoy class generation, metadata hygiene.

When to enable: always for anything you charge for. The interlocks are where the real anti-modification cost shows up. They are what make the fifth attempted patch break the first.

Watch out for: nothing, in normal operation. If a build fails the integrity check at boot, that is the engine catching real corruption. File a support ticket with the build ID and we will look at it.

Runtime & resources 3 modules

Resource rewriting, anti-debug / anti-agent, selective VM (covered separately).

When to enable: resource rewriting is fine to leave on. Anti-debug is off by default and should stay off unless you have a specific reason. It catches aggressive reversers but may interact with profilers and analytics agents.

Selective Java VM crown jewel

The selective VM rewrites individual methods into a custom opcode set executed by a tiny embedded interpreter. The decompiler walks into a dispatcher that has no recognizable Java semantics. The encoding rotates per build.

When to enable: on the methods that matter. License verification. Anti-cheat heuristics. Pricing logic. Anything you would lose sleep over if a reverser could read it cleanly.

Watch out for: a small overhead per call. Keep VM-protected methods out of inner loops that run thousands of times per tick. Use V on methods that run at session start, not per frame.

Native stub lifting enterprise

For the most valuable methods, the engine compiles them into a hardened native runtime layer the Java side calls into. The Java side becomes a stub. Disassembly resistant, per-customer polymorphic, integrity-bound at load.

When to enable: when the cost of a single leak would justify the price of the plan. Native stub is heavy on the build pipeline (we cross-compile for x86-64 and ARM64) and changes the deployment shape (your JAR now ships shared libraries).

Watch out for: not all methods are stub-lifting candidates. The engine tells you which ones are eligible, and falls back to VM-protected Java where the target platform is unsupported.

Product Licensing

Pro and Enterprise plans include the licensing module. JavaObf injects a tamper resistant license client into your protected jar at build time, so you do not ship a single line of license code from your own repo. Issue keys to your customers, revoke them in minutes from your dashboard, and let the gateway enforce the rules at runtime.

no client code

The licensing client is patched into your protected build by the engine. You write zero license logic in your own source.

machine bound

On first run, the license is bound to the customer's machine ID. Spoofing the machine ID fails server side, with no client side boolean to flip.

revoke in minutes

Revoke a key from your dashboard and every running copy fails its next validation cycle. The jar self corrupts shortly after.

Quickstart

  1. 01
    Open /licensing and create a product. Or open the project you already have and use Create from this project on the Product Licensing card; that creates and links the product in one click.
  2. 02
    Make sure the project is linked to the product. The project page shows a green chip when it is. Each linked project carries the product's cryptographic identity into every build of that project.
  3. 03
    Queue a build with the licensing toggle on. The build form shows a checkbox Patch licensing client. On by default for linked projects.
  4. 04
    Issue a license and hand the customer the file. The Licenses tab gives you a copy chip for the key and a Download .key button. The customer drops license.key next to the jar.

The license.key file

One opaque base64 line. The customer places it in the same directory as the jar (or in the working directory the jar is launched from). The patched runtime locates it automatically.

What the customer sees on success. Nothing, by default. The jar boots like normal. You can flip on Print success message to console in product settings if you want a confirmation line for support purposes.

If the file is missing or invalid: the jar prints a short generic notice (license.key not found or license.key invalid) and refuses to start. No URLs, no marketing copy, no hints about which check failed.

Runtime behaviour

On first launch, the patched runtime activates the license against the gateway and binds it to the machine. From then on it continuously re validates in the background. If it cannot reach the gateway for an extended window, or if you revoke the key from your dashboard, the jar stops working.

Failure mode is intentionally delayed. When the gateway rejects a session, the runtime does not crash at the check site. It silently corrupts its own internal state, so the failure surfaces minutes later inside customer code rather than pointing an attacker at the verification logic.

Watch out for: machines without internet access. The licensing module is online only by design. If your customers have offline deployments, tell us and we will discuss enterprise options.

Notifications

Each product can fan out license events to channels you configure. Three channel types are supported:

Discord webhook

Branded rich embed, color coded by severity, deep links back to the dashboard. Paste the webhook URL and we handle the rest.

Generic webhook

Signed JSON POST to any URL. The signing secret is shown to you once at creation time. Verify the X-JavaObf-Signature header on receipt.

Email

Plain text alerts to a single inbox. Useful for low volume products and operator on call rotations.

Pick which event kinds each channel subscribes to. Defaults cover the things that signal piracy or abuse (machine ID mismatch, replay attempt, signature failure, ban). Heartbeat noise is off by default to keep your channel readable.

Common workflows

Shipping a paid plugin

  1. First build with balanced. Confirm it boots in your target server.
  2. Switch to max-hardening-plus. Confirm it still boots.
  3. Add V rules on the two or three methods that hold value (license, anti-cheat, custom algo).
  4. Save as a preset. Apply to every paid build going forward.

Shipping a demo / trial variant

  1. Use the same preset as your paid build, but add a S rule on the trial-gating class so its strings stay readable.
  2. Set a different fingerprint suffix so leaked demos identify themselves separately from leaked paid copies.
  3. Watermark the trial-only resources so they cannot be lifted into a paid build.

Updating a plugin without breaking installed users

Polymorphic encoding means each new build is structurally different. That is the point: a crack of v1.4 does not work on v1.5. As long as your plugin's external contract (commands, config schema) stays stable, your users will not notice. The mapping file changes per build, so keep them all if you need to deobfuscate stack traces from older versions.

Troubleshooting

The protected JAR fails to load with "no main manifest attribute"

This means a custom entrypoint convention was not auto-detected. Add a hard-lock rule on your main class via the rules panel and rebuild. If your platform is unusual, paste a snippet from your descriptor to support and we will add it to the auto-lock list.

Reflection inside my plugin fails after protection

You are looking up a class or method by a name the engine renamed. Either refactor to use a stable handle (a service interface, a constant), or add a skip-rename rule on the lookup target. The build report flags every reflective access it could not resolve.

The plugin is slower after enabling selective VM

That is expected on hot paths. VM dispatch is not free. Identify the methods marked V that run inside per-tick loops and either move the V tag to a less hot caller, or replace it with the strong CFG profile, which has near-zero overhead.

A class that should be locked is being renamed anyway

Open the build report. Search for the class name. The report tells you which descriptor (or lack of one) the engine used to decide. Add an explicit lock rule for that class.

Build fails with "ecosystem could not be classified"

Your JAR has no recognizable descriptor. The engine refuses to guess. Either add a stub plugin.yml with a single line declaring the main class, or set the ecosystem manually in the project settings.

FAQ

Where do I find the engine version a build was produced with?

The build report has it as engine.version. The protected JAR also embeds it inside the integrity manifest. Useful for support tickets.

Can I export my projects?

Yes. Account → Export. You get a JSON archive of all projects, presets, builds, and rules. No source code is exported, only metadata.

Is there an API or a CLI?

The web app is the primary interface. Enterprise customers get a sealed Docker runner image with a license-bound CLI for CI integration. Get in touch if that is your shape.

Do you have a public roadmap?

Internal one, not public. Subscribe to the changelog from the dashboard. Major modules and crown-jewel features get announced in the Discord first.