JObfuscator — Maven plugin for Java source obfuscation
JObfuscator is a Java source obfuscator: it transforms .java files into a protected form while preserving behavior. It applies renaming, non-linear control flow, string encryption, and many other strategies (product overview).
This article presents how to use Maven plugin to protect your Java projects from decompilation, reverse engineering and LLMs analysis.
| Artifact | Role |
|---|---|
| jobfuscator-maven-plugin | Maven goal jobfuscator:obfuscate-sources (runs in generate-sources) |
| jobfuscator-annotations | Compile-time @Obfuscate markers (provided scope) |
| jobfuscator-client | Standalone Java client for the JObfuscator Web API |
More documentation, downloads, and APIs:
Why obfuscate Java?
Compiled Java (.class, .jar, .war) is bytecode that decompiles cleanly back to readable Java. Obfuscation makes recovered source harder to analyse while keeping your application runnable.
Installation (Maven)
You can find jobfuscator-maven-plugin at Maven Central under com.pelock:
Add the plugin to your application pom.xml:
<plugin>
<groupId>com.pelock</groupId>
<artifactId>jobfuscator-maven-plugin</artifactId>
<version><!-- latest released version --></version>
</plugin>
Optional property for version alignment:
<properties>
<jobfuscator.version><!-- e.g. 1.0.3 --></jobfuscator.version>
</properties>
<plugin>
<groupId>com.pelock</groupId>
<artifactId>jobfuscator-maven-plugin</artifactId>
<version>${jobfuscator.version}</version>
</plugin>
Invoke the goal explicitly:
mvn com.pelock:jobfuscator-maven-plugin:<version>:obfuscate-sources
Usage examples
Example 1 — minimal Maven project layout
my-app/
├── pom.xml ← plugin + jobfuscator-annotations (provided)
└── src/main/java/com/example/
└── App.java ← your code with @Obfuscate
Example 2 — minimal pom.xml (plugin + compiler)
Replace <version> placeholders with your released coordinates:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>my-app</artifactId>
<version>1.0.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.release>11</maven.compiler.release>
<jobfuscator.apiKey><!-- YOU-API-HERE or leave empty for DEMO VERSION --></jobfuscator.apiKey>
<jobfuscator.version><!-- e.g. 1.0.3 --></jobfuscator.version>
</properties>
<dependencies>
<dependency>
<groupId>com.pelock</groupId>
<artifactId>jobfuscator-annotations</artifactId>
<version>${jobfuscator.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>com.pelock</groupId>
<artifactId>jobfuscator-maven-plugin</artifactId>
<version>${jobfuscator.version}</version>
<executions>
<execution>
<goals>
<goal>obfuscate-sources</goal>
</goals>
<phase>generate-sources</phase>
</execution>
</executions>
<configuration>
<apiKey>${jobfuscator.apiKey}</apiKey>
<enableCompression>true</enableCompression>
<mixCodeFlow>true</mixCodeFlow>
<renameVariables>true</renameVariables>
<renameMethods>true</renameMethods>
<shuffleMethods>true</shuffleMethods>
<intsMathCrypt>true</intsMathCrypt>
<cryptStrings>true</cryptStrings>
<stringSplit>true</stringSplit>
<intsToArrays>true</intsToArrays>
<dblsToArrays>true</dblsToArrays>
<dblsMathCrypt>true</dblsMathCrypt>
<stringCharVault>true</stringCharVault>
<intsFromDoubleMath>true</intsFromDoubleMath>
<opaqueMixerChain>true</opaqueMixerChain>
<complexifyBooleans>true</complexifyBooleans>
<tryFinallyNoise>true</tryFinallyNoise>
<arrayIntCrypt>true</arrayIntCrypt>
<arrayCharCrypt>true</arrayCharCrypt>
<arrayDoubleCrypt>true</arrayDoubleCrypt>
<arrayStringCrypt>true</arrayStringCrypt>
<selfCheck>true</selfCheck>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<release>${maven.compiler.release}</release>
</configuration>
</plugin>
</plugins>
</build>
</project>
Example 3 — src/main/java/com/example/App.java
package com.example;
import com.pelock.jobfuscator.Obfuscate;
@Obfuscate
public class App {
public static void main(String[] args) {
System.out.println("Hello");
}
}
Add the jobfuscator-annotations dependency (see Example 2 and Declaring @Obfuscate so javac accepts it below).
Example 4 — shell: full build with API key
mvn clean package -Djobfuscator.apiKey="YOUR-ACTIVATION-KEY"
Example 5 — shell: skip obfuscation (compile original sources only)
mvn clean compile -Djobfuscator.skip=true
Example 6 — shell: only run the plugin goal (no <execution> in pom.xml)
Replace 1.0.3 with your plugin version:
mvn com.pelock:jobfuscator-maven-plugin:1.0.3:obfuscate-sources \
-Djobfuscator.apiKey="YOUR-ACTIVATION-KEY"
Example 7 — shell: tune parallelism
mvn clean package -Djobfuscator.apiKey="YOUR-KEY" -Djobfuscator.threads=8
Example 8 — Java: call the Web API with jobfuscator-client
import com.pelock.jobfuscator.JObfuscatorClient;
import com.pelock.jobfuscator.JObfuscatorResult;
public class ObfuscateFromJava {
public static void main(String[] args) throws Exception {
String key = System.getenv("JOBFUSCATOR_API_KEY"); // or pass args[0], can be null for demo limits
JObfuscatorClient client = new JObfuscatorClient(key);
// Optional: e.g. client.enableCompression = true;
String javaSource =
"@Obfuscate\n"
+ "class Ideone {\n"
+ " public static void main(String[] a) {\n"
+ " System.out.println(\"hi\");\n"
+ " }\n"
+ "}\n";
JObfuscatorResult result = client.obfuscateJavaSource(javaSource);
if (result.getError() != JObfuscatorClient.ERROR_SUCCESS) {
System.err.println("API error code: " + result.getError());
System.exit(1);
}
System.out.println(result.getOutput());
// Optional license probe:
// JObfuscatorResult info = client.login();
}
}
Add the jobfuscator-client dependency to the same project (coordinates are listed under Standalone Java client below).
Usage (Maven)
Bind obfuscate-sources to generate-sources so obfuscated sources are produced before compilation:
<plugin>
<groupId>com.pelock</groupId>
<artifactId>jobfuscator-maven-plugin</artifactId>
<version>${jobfuscator.version}</version>
<executions>
<execution>
<goals>
<goal>obfuscate-sources</goal>
</goals>
<phase>generate-sources</phase>
</execution>
</executions>
<configuration>
<apiKey>${jobfuscator.apiKey}</apiKey>
<enableCompression>true</enableCompression>
<!-- Global strategies (defaults align with the Web API) -->
<mixCodeFlow>true</mixCodeFlow>
<renameVariables>true</renameVariables>
<renameMethods>true</renameMethods>
<shuffleMethods>true</shuffleMethods>
<intsMathCrypt>true</intsMathCrypt>
<cryptStrings>true</cryptStrings>
<stringSplit>true</stringSplit>
<intsToArrays>true</intsToArrays>
<dblsToArrays>true</dblsToArrays>
<dblsMathCrypt>true</dblsMathCrypt>
<stringCharVault>true</stringCharVault>
<intsFromDoubleMath>true</intsFromDoubleMath>
<opaqueMixerChain>true</opaqueMixerChain>
<complexifyBooleans>true</complexifyBooleans>
<tryFinallyNoise>true</tryFinallyNoise>
<arrayIntCrypt>true</arrayIntCrypt>
<arrayCharCrypt>true</arrayCharCrypt>
<arrayDoubleCrypt>true</arrayDoubleCrypt>
<arrayStringCrypt>true</arrayStringCrypt>
<selfCheck>true</selfCheck>
</configuration>
</plugin>
Provide the activation key via -Djobfuscator.apiKey=…, a <properties> entry, or an encrypted value from settings.xml. Never commit keys.
What the plugin does
Original .java files are never overwritten. For each relative path, the goal reads from inputDirectory and writes the API response only under outputDirectory (default under target/generated-sources/…). If inputDirectory and outputDirectory resolve to the same path, the build fails immediately (otherwise clearing the output tree could delete your sources).
Scope vs compilation. The goal walks every *.java under inputDirectory, which defaults to src/main/java — the usual main compile source root. If inputDirectory is not one of project.getCompileSourceRoots(), a warning is logged so you can align configuration (extra compile roots are still compiled by Maven but would not be obfuscated unless included under inputDirectory or via a separate execution).
- Scans
inputDirectory(defaultsrc/main/java) for*.javafiles. - Sends each file to the JObfuscator Web API (
multipart/form-data, optional zlib compression on request and response bodies). - Writes transformed sources under
outputDirectory(defaulttarget/generated-sources/jobfuscator), preserving package directories. - By default (
replaceMainCompileSources=true), removes the compile source root that matchesinputDirectoryfrom the model and addsoutputDirectory, sojavaccompiles obfuscated sources while leaving the original tree unchanged on disk.
@Obfuscate markers are interpreted by the service from your Java source text; this plugin does not parse annotations locally.
Behaviour switches
| Property | Meaning |
|---|---|
skip / -Djobfuscator.skip=true
|
Skip obfuscation; normal src/main/java is compiled. |
failOnError |
Fail the build on first API / I/O error (default true). |
replaceMainCompileSources |
Replace default main sources with generated tree (default true). |
threads / -Djobfuscator.threads
|
Concurrent API requests (default 4). Capped by file count; values < 1 are treated as 1. Respect provider rate limits and credits. |
@Obfuscate annotations in source code
JObfuscator recognises @Obfuscate in Java source to enable protection:
- Whole type — annotate the class, interface, enum, or annotation type.
- Single member — annotate individual methods when you do not want whole-class obfuscation.
-
Parameters — optional attributes use snake_case names aligned with the Web API (
rename_methods,crypt_strings, …), so you can tune strategies per class or per method on top of global Maven settings.
Declaring @Obfuscate so javac accepts it
The compiler needs an @Obfuscate type. Add the published annotations JAR (compile-time only; SOURCE retention — not required at runtime):
<dependency>
<groupId>com.pelock</groupId>
<artifactId>jobfuscator-annotations</artifactId>
<version>${jobfuscator.version}</version>
<scope>provided</scope>
</dependency>
Import in source: import com.pelock.jobfuscator.Obfuscate;
The full attribute list lives in Obfuscate.java (snake_case names aligned with the Web API). For marker-only usage, @Obfuscate with no attributes is enough.
Offline / no Maven: you may still copy a local SOURCE-retention @interface Obfuscate stub into your tree (same shape as the library type above).
Example — entire class
import java.io.*;
@Obfuscate
class Ideone {
public static double calculateSD(double[] numArray) {
double sum = 0.0;
double standardDeviation = 0.0;
int length = numArray.length;
for (double num : numArray) {
sum += num;
}
double mean = sum / length;
for (double num : numArray) {
standardDeviation += Math.pow(num - mean, 2);
}
return Math.sqrt(standardDeviation / length);
}
public static void main(String[] args) {
double[] numArray = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
double sd = calculateSD(numArray);
System.out.format("Standard Deviation = %.6f%n", sd);
}
}
Example — selected methods only
class Mixed {
@Obfuscate
public static double calculateSD(double[] nums) {
return internalStats(nums);
}
public static void main(String[] args) {
System.out.println(calculateSD(new double[] {1, 2, 3}));
}
private static double internalStats(double[] nums) {
/* ... */
return 0;
}
}
Example — interface type and methods
@Obfuscate
public interface PaymentGateway {
void authorize(String id, double amount);
@Obfuscate
default void log(String message) {
System.err.println(message);
}
}
public class StripeGateway implements PaymentGateway {
@Override
public void authorize(String id, double amount) {
/* ... */
}
}
Example — enum
@Obfuscate
public enum Mode {
FAST,
SAFE
}
Example — selective strategies
Per-type overrides use the same snake_case attribute names as the Web API:
@Obfuscate(
rename_methods = false,
rename_variables = true,
shuffle_methods = true,
mix_code_flow = true,
crypt_strings = true,
string_split = true,
ints_math_crypt = true,
dbls_math_crypt = true,
string_char_vault = true,
ints_from_double_math = true,
opaque_mixer_chain = true,
complexify_booleans = true,
try_finally_noise = true,
ints_to_arrays = true,
dbls_to_arrays = true,
array_int_crypt = true,
array_char_crypt = true,
array_double_crypt = true,
array_string_crypt = true,
self_check = true)
public final class FineGrained {
/* ... */
}
If you disable strategies globally in the Maven <configuration>, the remote service may ignore @Obfuscate markers in source—keep global flags and annotations aligned when tuning builds.
Annotations library (jobfuscator-annotations)
<dependency>
<groupId>com.pelock</groupId>
<artifactId>jobfuscator-annotations</artifactId>
<version>${jobfuscator.version}</version>
<scope>provided</scope>
</dependency>
Use with the Maven plugin or when authoring sources that call the Web API with @Obfuscate in the text.
Standalone Java client (jobfuscator-client)
<dependency>
<groupId>com.pelock</groupId>
<artifactId>jobfuscator-client</artifactId>
<version>${jobfuscator.version}</version>
</dependency>
Use com.pelock.jobfuscator.JObfuscatorClient: obfuscateJavaSource(String), login(), and public boolean fields (enableCompression, strategy toggles) that mirror the plugin <configuration> parameters.
Building this repository
mvn clean install
Optional Invoker IT profile (offline sample with <skip>true</skip>; run from repository root):
mvn clean verify -Pinvoker-tests
Requirements
- JDK 11+
- Maven 3.9+ (typical)
- Network access at build time when obfuscation is not skipped
Bartosz Wójcik
- Visit my site at — https://www.pelock.com
- X — https://x.com/PELock
- GitHub — https://github.com/PELock
Top comments (0)