Stack Overflow is a call stack overflow error (java.lang.StackOverflowError) that occurs when the maximum stack depth of a thread is exceeded. According to the Java Virtual Machine Specification, the typical JVM stack depth is 1024 frames for 64-bit systems. The main cause is infinite recursion without a base case.
Key Takeaways
StackOverflowError is a fatal error of the Java Virtual Machine (JVM) or Android Runtime (ART) that occurs when the call stack of a thread reaches the maximum allowed depth. Unlike OutOfMemoryError (Heap exhaustion), StackOverflowError is related to a different memory area — the stack, where method call frames and local variables are stored.
Each method call creates a frame on the stack: return address, parameters, and local variables. When the method returns, the frame is destroyed. If a method calls itself (recursion) without a base case, frames accumulate until the stack is full. The JVM cannot allocate a new frame and throws StackOverflowError with a «null» message (in Java) or with an indication of an endlessly repeating stack trace line.
The size of a thread's stack is fixed at creation and does not change during execution. In Android, the typical main thread stack size is 32–48 KB, which gives a depth of roughly 512–1024 frames for methods without many local variables. For background threads, the default size is smaller — 16–24 KB.
The Call Stack is a LIFO (Last In, First Out) data structure that manages the order of method execution. Each time the program calls a method, the JVM creates a frame on the stack and places it on top. When the method completes, the frame is popped.
Each frame contains: operand stack (for bytecode instructions), array of local variables (including this), reference to constant pool, and return address. The more local variables a method has, the larger its frame size and the fewer methods can be called before the stack is full. A method with 10 parameters and 20 local variables takes about 3 times more space than a method with no parameters.
On Android, ART uses its own stack implementation, different from Desktop JVM. ART can dynamically increase the stack within certain limits, but a hard limit still exists for each thread. The main thread (UI thread) has the largest stack, as it handles the entire Activity lifecycle and event processing.
// Recursion that leads to StackOverflowError
fun recursiveCall(depth: Int): Int {
return recursiveCall(depth + 1) // no base case
}
// The call will cause StackOverflowError at depth ~1000
recursiveCall(0)
Five typical scenarios lead to StackOverflowError in mobile applications. Most of them are related to recursion, but there are also less obvious causes.
The most common cause. A developer writes a recursive method without a stopping condition or with a condition that never becomes true. Each call adds a frame, and the stack fills up in 500–2000 iterations depending on frame size. A typical example: calculating n! factorial without checking n == 0.
Check the base case at the beginning of each recursive method. In Kotlin, use require() or check() for parameter validation at the start. For deep recursion (more than 100 levels), consider replacing it with an iterative approach.
Class A creates instance B, class B creates instance A — this is a cyclic dependency in constructors. When trying to create A, the B constructor is called, which calls the A constructor, and so on until StackOverflowError. DI frameworks (Dagger, Hilt) detect such cycles at compile time, but manual object creation does not catch them.
Use Dependency Injection with dependency graphs: Dagger or Koin check cycles at build time. If a cycle is unavoidable, replace direct dependency with an interface using lazy initialization or a Provider factory.
// Cyclic dependency — StackOverflowError
class A(private val b: B)
class B(private val a: A)
// Lazy solution
class A(private val bProvider: Provider<B>)
Traversing a View tree (ViewGroup.getChildAt()), file system, or JSON structure via recursion may exceed the stack limit at a depth of more than 500–1000 elements. An Android ViewGroup with 20 levels of nesting is rare, but recursive parsing of JSON with 2000 nested objects is a real scenario.
Replace recursive traversal with iterative traversal using an explicit Stack<T> or ArrayDeque. This completely eliminates the risk of stack overflow, since heap objects are not limited by the stack limit. BFS (Breadth-First Search) via Queue also solves the problem.
An Android-specific cause: cyclic calls of lifecycle methods when configuration is handled incorrectly. For example, calling recreate() inside onConfigurationChanged, which again calls onConfigurationChanged, and so on until StackOverflowError. Similarly: setContentView() inside onLayout(), which triggers another measure and layout pass.
Do not call recreate() inside methods related to configuration changes. To update the UI when the theme changes, use setTheme() without recreate. For dynamic orientation changes — call requestOrientation() once, without a flag in the configuration.
Gson, Moshi, or Kotlin Serialization when trying to serialize an object with cyclic references (A references B, B references A) go into infinite recursion and crash with StackOverflowError. This is a common problem when serializing entities with bidirectional relationships (JPA, Room with ForeignKey).
Use @Transient, @JsonIgnore, or @kotlinx.serialization.Transient for one side of the cycle. For Gson — JsonSerializer with an explicit depth limit. For Room — never serialize Entity directly, use DTO mappers.
Diagnosing StackOverflowError is easier than other memory errors: the stack trace in most cases shows a repeating sequence of calls. This immediately points to recursion.
The Stack trace of StackOverflowError is unique: after the first 200–500 lines, the same call pattern starts repeating. The JVM truncates repeating lines at the end and shows «... 1234 more». The number of non-repeating lines before «...» indicates the recursion depth that caused the error.
Read the first lines of the stack trace — they show which method started the repetition. Find the method that calls itself or creates a call chain that returns to it. Fix the base case or replace recursion with a loop.
Temporarily, the problem can be solved by increasing the stack size via the JVM flag -Xss. For Android, the stack size is set via AndroidManifest: android:largeHeap does not affect the stack. To increase the thread stack in code: Thread(ThreadGroup, Runnable, name, stackSize). stackSize is the desired size in bytes.
// Creating a thread with an increased stack
val thread = Thread(null, runnable, "big-stack-thread", 64 * 1024)
thread.start()
Important: increasing the stack does not solve the problem, it only delays it. With recursion of 10,000 levels, a 64 KB stack will be replaced by a 128 KB stack, giving 20,000 levels — but the error will still occur, just later. The only correct solution is iterative replacement of recursion.
Iterative algorithms do not use the call stack to store intermediate states — they store them in the heap (Stack<T> or ArrayDeque). Binary tree traversal, factorial calculation, Fibonacci — any recursion can be converted to iteration using an explicit stack.
// Iterative tree traversal — no risk of StackOverflow
fun traverseIterative(root: Node?) {
val stack = ArrayDeque<Node>()
stack.push(root)
while (stack.isNotEmpty()) {
val node = stack.pop() ?: continue
process(node)
node.right?.let { stack.push(it) }
node.left?.let { stack.push(it) }
}
}
Preventing StackOverflowError is a set of rules and tools that identify potential recursive cycles before they reach production.
Add a protective depth counter in recursive methods in debug builds. If the depth exceeds a threshold (e.g., 1000), throw an exception with a clear message. This turns a StackOverflowError with an unreadable trace into a clear business exception.
fun safeRecursive(n: Int, depth: Int = 0): Int {
if (depth > 1000) {
throw IllegalStateException("Recursion exceeded 1000 levels")
}
return if (n <= 1) n
else safeRecursive(n - 1, depth + 1)
}
Detekt (Kotlin) and Infer (Facebook) find potential infinite recursions at the static analysis level. Detekt has a PotentiallyInfiniteRecursion rule that warns about self-calls without changing parameters. Enable it in the CI ruleset and set severity to error.
During code review, pay attention to: any self-call methods, recursive calls inside lambdas (Kotlin inline functions), cyclic calls between different classes, recursion in property delegates. For each recursive method, check: is there a base case, does the parameter change at each step, does the parameter change guarantee reaching the base case.
Kotlin supports the tailrec modifier: if a recursive method is marked tailrec and the call is tail (last operation), the compiler converts it to iteration. However, tailrec only works for self-calls (method calls itself directly), does not work for mutual recursion, and is not supported in Android-compatible Kotlin versions before 1.5.
tailrec fun factorial(n: Int, acc: Int = 1): Int {
return if (n <= 1) acc
else factorial(n - 1, acc * n) // tail call
}
Frequently Asked Questions
Yes, but only at the Java level. Error, like Exception, is a Throwable. However, after StackOverflowError the stack is damaged — frames that did not fit cannot complete correctly. An attempt to create a new object in the catch block may cause another StackOverflowError.
For the main thread — 32–48 KB, for background threads — 16–24 KB. The exact size depends on the Android version and device manufacturer. ART uses dynamic stack expansion but no more than 2× the initial value.
In Kotlin — yes, if the method is marked tailrec. The compiler converts tail recursion into iteration, completely eliminating stack growth. In Java, tail recursion is not optimized by JVM (unlike functional languages like Scala).
The stack size on the emulator and a real device may differ. The emulator uses Desktop JVM with a typical stack of 512–1024 KB, while Android ART uses 32–48 KB. The error will manifest on ART earlier than on Desktop JVM.
Memory area: StackOverflowError is a stack error (call frames), OutOfMemoryError is a heap error (objects). StackOverflowError is almost always caused by recursion, while OutOfMemoryError is caused by memory leaks or large objects.
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