Code obfuscation transforms an app’s compiled code so that it keeps working exactly as before but becomes far harder for a person or a decompiler to understand. It renames identifiers, hides strings and reshapes logic without changing behaviour. What it can reach depends on the language: Java and Kotlin bytecode, Swift and Objective-C machine code, C and C++ libraries, compiled Dart and JavaScript bundles each reveal different things. Obfuscation slows reverse engineering and protects the checks placed around it. It does not hide traffic the app sends over the network, and secrets that belong on a server should stay there.
What obfuscation acts on
| Code | How it ships | What an analyst sees without protection |
|---|---|---|
| Kotlin and Java (Android) | DEX bytecode in the APK or app bundle | Class, method and field names; output that reads close to source |
| Swift (iOS) | Native machine code in the app binary | Type metadata and names the Swift runtime keeps |
| Objective-C (iOS) | Native machine code with runtime metadata | Every class and selector name |
| C and C++ (both) | Shared libraries | Exported symbols and plain strings |
| Dart (Flutter) | Ahead-of-time compiled native library | Function and class names unless renamed |
| JavaScript (React Native) | Text bundle or Hermes bytecode | Readable logic after unpacking |
Platform tooling offers a baseline. R8 renames and shrinks Java and Kotlin code, and Flutter’s obfuscate build option renames Dart symbols. Strings, control flow and native code stay readable under both, and on iOS the standard build strips symbols but does not rename Swift or Objective-C names.
Compatibility: what can break
Obfuscation changes names, and some code looks names up at runtime. Test each of these before release:
- Reflection. Code that finds a class or method by its name string, common in Android libraries.
- Serialisation. JSON and database mappers that derive keys from field names.
- Selectors and key-value coding. Objective-C calls resolved by name, and Swift members exposed to the Objective-C runtime.
- Plugins and bridges. Flutter platform channels and React Native native modules that match names across the language boundary.
- Dependency injection and third-party SDKs. Frameworks that expect their own class names to survive.
- Crash symbolication. Android traces need the mapping file, iOS traces need the dSYM, and Flutter traces need the split debug information.
Each item needs either a keep rule or a test that proves the renamed path still works.
Validation: proving it worked
Good obfuscation is measured, not assumed. Pick the classes and strings that matter, decompile the protected and unprotected builds, and compare what an analyst can recover. Run your regression suite and the journeys users rely on. Measure launch time and package size on real devices. Trigger a test crash and confirm the trace translates back to readable code. Record the results per release so a later build that drops a rule is noticed.
How Otukan applies obfuscation
Otukan obfuscates identifiers, strings, control flow, resources and native libraries, and protects Dart and JavaScript or Hermes code directly. A maintained keep list covers reflection, serialisation and SDK names. You choose where heavier transforms run, and every protected build ships with a private mapping for crash analysis. Obfuscation also hides Otukan’s own integrity and runtime checks, and Otukan validates each release with you through decompiler comparison, a test crash and measured journeys on your devices, inside your existing build pipeline, with your signing keys staying yours.
The product control is covered on mobile code obfuscation. SDK providers protecting their own code inside other companies’ apps can read protecting an SDK you distribute. For the wider protection set, see what is app shielding. Related terms are in the mobile security glossary.