Reflection in app development — what it is, reflection mechanisms and how to use them

Author: IT Sectr Published: 2026-05-17 Reading time: 8 min

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 access to the metadata of classes, methods and fields during program execution.
  • Java Reflection API provides the Class, Method, Field and Constructor classes for dynamic analysis.
  • Kotlin reflection uses KClass and KFunction, integrated with coroutines and serialization.
  • Objective-C Runtime is a form of reflection through class_copyMethodList and objc_getClass.
  • Reflection performance is 10–100 times lower than direct calls due to the lack of JIT optimizations.

What is Reflection?

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.

How Reflection works in Java and Kotlin

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.

kotlin
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: Class, Method, Field

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
// 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: an alternative reflection model

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.

Using Reflection in mobile development

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.

  • Serialization — Gson reads the declared fields of an object through Field.get() and creates JSON according to the @SerializedName annotations.
  • Dependency Injection — Dagger generates code through annotation processing, Koin uses Kotlin reflection for runtime resolution.
  • Testing — JUnit finds methods with @Test through reflection and calls them; Mockito creates mocks through dynamic proxy.
  • Database — Room checks Entity fields through Class.getDeclaredFields() at compile time (via KAPT/KSP).
  • Analytics and monitoring — Firebase Crashlytics obtains a stack trace through Throwable.getStackTrace(), which is based on reflection.

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 performance: the cost of dynamic access

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.

OperationDirect callThrough ReflectionSlowdown
Calling a method with no parameters~3 ns~120 ns40x
Reading an int field~1 ns~85 ns85x
Calling a method with 2 parameters~4 ns~250 ns62x
Creating an instance through a constructor~5 ns~180 ns36x
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.

Reflection alternatives: annotations and code generation

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.

kotlin
// 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.

Comparison of approaches

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 limitations on Android and iOS

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:

groovy
// 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

Is Reflection harmful to application performance?

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.

How does Java reflection differ from Kotlin reflection?

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.

How to avoid problems with obfuscation when using Reflection?

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.

Is there Reflection in Swift?

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.

Which libraries use Reflection on Android?

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

  • Reflection is a runtime mechanism for accessing the metadata of classes, methods and fields.
  • Java reflection uses Class, Method, Field; Kotlin uses KClass, KFunction, KProperty with coroutine integration.
  • Objective-C Runtime provides class_copyMethodList and objc_getClass without access restrictions.
  • Reflection is slower than a direct call by 10–100 times due to the lack of JIT optimizations.
  • Alternatives — code generation (KSP, KAPT) and annotation processing — eliminate reflection overhead.
  • ProGuard/R8 requires keep rules for classes used through Class.forName() and getDeclaredMethod().
  • Reflection is irreplaceable for dynamic plugin loading, DI and test frameworks where types are unknown at compile time.

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.

Discuss the project

Read also