Reflection is a runtime mechanism that allows code to inspect its own structure: obtaining classes, methods, fields and annotations without knowing types at compile time. This tool is the foundation of many mobile frameworks — JSON serialization (Gson, Moshi), dependency injection (Dagger, Koin) and test runners (JUnit, XCTest). According to the Oracle Java Reflection Tutorial, 2024, reflection is a mandatory element of the Java platform, used by all major libraries.
Key points
Reflection is the ability of a program to observe and modify its own structure and behavior during execution. In object-oriented languages this means obtaining Class, Method, Field and Constructor objects that represent program elements as data available for reading and calling.
The term “reflection” was introduced in the artificial intelligence community in 1982 (Brian Cantwell Smith) and was implemented in the Smalltalk language. In mobile development, reflection first appeared in Java ME and Objective-C (1986, NextStep). Today every major mobile platform has its own reflection API: Java/Kotlin for Android, Objective-C Runtime for iOS, Swift Mirror API for Swift.
The reflection mechanism is based on metadata that the compiler saves in the bytecode or binary. Android stores full information about classes in DEX files, iOS stores it in the __objc_classlist section of the Mach-O segment. The runtime loads this metadata into memory and provides an API for traversing it.
Java Reflection API is built around the java.lang.Class class. Any object in Java can be converted to Class via .getClass() or Class.forName(). From Class you can extract all methods, fields, constructors, annotations and superclasses. Kotlin inherits Java reflection and adds its own KClass, KFunction, KProperty from the kotlin.reflect package.
import kotlin.reflect.full.declaredMemberFunctions
data class User(
val name: String,
val email: String
)
fun inspectClass() {
val kClass = User::class
val properties = kClass.declaredMemberProperties
val functions = kClass.declaredMemberFunctions
properties.forEach { prop ->
println("Property: ${prop.name}, type: ${prop.returnType}")
}
}
In this example KClass provides metadata for the data class User. declaredMemberProperties returns a list of properties with their types and getters. Kotlin reflection is closely integrated with coroutines: KFunction supports the suspend modifier, which allows async methods to be called through reflection.
Java reflection works with Class<?>, Method.setAccessible() and Field.get(). setAccessible(true) disables Java language access control checks for private elements. This is a powerful but dangerous mechanism: on Android starting with API 28, calling setAccessible on hidden system methods can cause InaccessibleObjectException.
// Java reflection: invoking a private method
Class> clazz = Class.forName("com.example.MyClass");
Object instance = clazz.getDeclaredConstructor().newInstance();
Method method = clazz.getDeclaredMethod("privateMethod", String.class);
method.setAccessible(true);
method.invoke(instance, "reflection test");
The code demonstrates Class.forName() — dynamic class loading by string name. This is the basis of plugin architectures: a class may be unknown at compile time, but can be loaded and executed through reflection at runtime. getDeclaredMethod(“privateMethod”, ...) finds a method by name and parameter types, and invoke executes it.
Objective-C runtime provides the functions class_copyMethodList, class_copyPropertyList, objc_getAssociatedObject. Unlike Java, Objective-C does not hide private methods by default — the runtime sees all class methods. This explains why method swizzling works without setAccessible: the runtime has no encapsulation at the metadata level.
Reflection is used in key mobile development libraries. JSON serialization (Gson, Moshi, Kotlinx.serialization) obtains object properties through reflection and matches them with JSON keys. Dependency injection (Dagger, Koin, Swinject) analyzes constructors and fields for automatic dependency injection. ORM libraries (Room, Realm) use reflection to map classes to database tables.
Each of these use cases works exactly in runtime — the code does not know in advance which classes it will encounter. Reflection provides a universal mechanism for overcoming this uncertainty at the cost of performance and security.
Reflection is 10–100 times slower than direct method calls. The reason is the lack of JIT optimizations (devirtualization, inlining), type checking on every call and wrapping parameters into Object[]/varargs. ART on Android 14 cannot inline-optimize reflection calls because the target method is unknown until execution time.
| Operation | Direct call | Through Reflection | Slowdown |
|---|---|---|---|
| Calling a method with no parameters | ~3 ns | ~120 ns | 40x |
| Reading an int field | ~1 ns | ~85 ns | 85x |
| Calling a method with 2 parameters | ~4 ns | ~250 ns | 62x |
| Creating an instance through a constructor | ~5 ns | ~180 ns | 36x |
| Resolving a class by string | — | ~800 ns | — |
The data was obtained on a Google Pixel 8 (Android 14, ART). Reflection performance improves with every Android version: on Android 9 a call through Method.invoke() was 150 times slower than a direct one, on Android 14 it is 40 times. ART uses built-in method handle mechanisms for optimization.
For performance-critical sections developers replace reflection with code generation: Dagger uses annotation processing instead of runtime lookup, Kotlinx.serialization generates serializers through KSP, Moshi adapts @JsonClass(generateAdapter = true) for compile-time codegen.
Annotation processing (KAPT, KSP) and code generation are the main alternatives to reflection in mobile development. They move metadata analysis from runtime to compile time: code is generated before the app starts, which eliminates reflection overhead and improves performance.
// KSP: code generation instead of reflection
@Serializable
data class Config(
val apiUrl: String,
val timeout: Int
)
// KSP generates ConfigSerializer without reflection
fun loadConfig(json: String): Config {
return Config.serializer().decodeFromString(json)
}
In this example @Serializable is the Kotlinx.serialization annotation. KSP (Kotlin Symbol Processing) analyzes the source code at compile time, finds all @Serializable classes and generates serializers. During application execution reflection is not used — the serializer is already compiled into machine code.
Code generation provides better performance, type safety and a smaller binary size (dead code elimination removes unused reflection dependencies). Reflection remains necessary for tasks where types are unknown at compile time: dynamic plugin loading, runtime proxies, test instrumentation. According to Kotlin, Kotlinx.serialization with KSP is 3–5 times faster than Gson, which is based on reflection.
Reflection on mobile platforms has security and performance limitations. Android starting with API 28 (Pie) restricts setAccessible for non-SDK interfaces — an attempt to open a hidden system method leads to an exception or warning. iOS with Swift does not support reflection in the classic sense: the Swift Mirror API only provides reading of properties (name, value) without modification or method calls.
Google Play rejects applications that use reflection to bypass platform restrictions: replacing system services, modifying SELinux policies, reading protected permissions. Apple also blocks applications that call private APIs through reflection — the App Review check scans the binary for string signatures of objc_msgSend with known private selectors.
ProGuard/R8 is another limitation. Obfuscation and code minification rename classes and methods to short names (a, b, c). If the code uses Class.forName(“com.example.MyClass”), it will break after obfuscation. The solution is keep rules in proguard-rules.pro:
// ProGuard keep rules for reflection
-keep class com.example.** { *; }
-keep class * implements java.io.Serializable { *; }
-keepclassmembers class * {
@com.google.gson.annotations.SerializedName ;
}
-keepattributes Signature, InnerClasses, EnclosingMethod
The -keep rules tell R8 not to rename classes used through reflection. Without these rules an obfuscated app will crash with ClassNotFoundException — the runtime will not be able to find the class by the string name that has changed.
Frequently asked questions
Yes, reflection is 10–100 times slower than a direct call. The main reasons: lack of JIT optimizations (inlining, devirtualization), parameter wrapping and type checking on every call. For production code it is recommended to replace reflection with code generation through KSP or annotation processing.
Java reflection works through Class, Method, Field and requires setAccessible for private members. Kotlin reflection uses KClass, KFunction, KProperty and supports sealed class, data class, coroutines (suspend functions) and null-safety. Kotlin reflection is based on Java reflection but adds a type-safe API.
Add ProGuard/R8 keep rules for classes, methods and fields used through reflection. For every Class.forName(), getDeclaredMethod(), getDeclaredField() there must be a corresponding directive -keep. Tools such as GreenDAO and Room automatically generate keep rules.
Swift does not have reflection in the full sense. Mirror API (Swift 2+) allows reading the properties of a struct or class: name, value, type. Calling methods, modifying fields and creating instances by type are impossible. For this purpose the Objective-C Runtime is used when inheriting from NSObject with @objc dynamic.
Gson (JSON serialization), Retrofit (creating interface implementations through dynamic proxy), Mockito (creating mocks), Koin (dependency injection), Room (checking Entity at compile time through KAPT), Firebase Crashlytics (stack trace analysis). Most libraries are moving to code generation with KSP/KAPT.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also