Lambda Expressions
Overview
Java 8 lambda expressions help eliminate boilerplate code that makes the syntax verbose and less clear. For instance, consider the standard basic click listener:
myButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d("debug", "Button clicked");
}
});
Lambda expressions can greatly simplify this code, especially in this case where the event listener has only one method that needs to be implemented:
myButton.setOnClickListener(v -> Log.d("debug", "Button clicked"));
If you have more than one line to execute, then you should surround the block of code with braces:
myButton.setOnClickListener(v -> {
Log.d("debug", "Button clicked");
Toast.makeText(MyActivity.this, "here", Toast.LENGTH_LONG).show();
});
Method References
You may also notice in some cases lambda expressions also contain double colons ::. These refer to a new syntax in Java 8 known as method references. You can reference a class or instance and pass along the method that will handle the event:
public void onCreate() {
myButton.setOnClickListener(this::logError);
}
public void logError(View v) {
Log.d("debug", "Button clicked");
}
Lambdas in Kotlin
Most newer CodePath guides are Kotlin-first. The Kotlin lambda syntax is similar to Java’s but with two notable differences: the lambda is always wrapped in curly braces, and the -> lives inside those braces rather than between the parameter list and the body. The same click listener written in Kotlin is:
myButton.setOnClickListener { view ->
Log.d("debug", "Button clicked")
}
This works because Kotlin performs a SAM conversion when the Java method’s parameter is a single-abstract-method interface — here View.OnClickListener. Per the official docs, “Kotlin function literals can be automatically converted into implementations of Java interfaces with a single non-default method, as long as the parameter types of the interface method match the parameter types of the Kotlin function.” See the matching Android example in the official input events guide.
A few Kotlin-specific conventions from the Kotlin lambda reference shape how Kotlin lambdas read in practice:
-
Full form. Parameters and types live inside the braces; the body comes after
->:val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y } -
The
itimplicit parameter. When a lambda has exactly one parameter, you can drop both the parameter declaration and the->. Kotlin exposes the parameter asit:myButton.setOnClickListener { Log.d("debug", "Clicked view id=${it.id}") } -
Trailing lambda convention. When the last parameter of a function is itself a function, the lambda goes outside the parentheses. When the lambda is the only argument the parentheses are dropped entirely — which is why
setOnClickListener { ... }reads as a block rather than a call with one argument.
Method references in Kotlin
Kotlin uses the same :: syntax as Java for function references, with two flavours:
class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
myButton.setOnClickListener(::logError)
}
private fun logError(v: View) {
Log.d("debug", "Button clicked")
}
}
::namereferences a top-level function or a member of the enclosing class (an unbound reference).instance::nameis a bound reference whose receiver is fixed to that instance — for exampleregex::matchesis a value of type(CharSequence) -> Booleanthat callsregex.matches(...).
RxJava
Lambda expressions are especially helpful in RxJava as well. Take a look at the code below for creating an Observable and subscribing an Observer to it.
Creating and subscribing to an observable without lambdas:
Observable.just("1", "2", "3")
.subscribe(new Subscriber<String>() {
@Override
public void onCompleted() {
Log.d("debug", "complete");
}
@Override
public void onError(Throwable throwable) {
Log.d("debug", throwable.getMessage());
}
@Override
public void onNext(String s) {
Log.d("debug", s);
}
});
Consider the same code with lambda expressions:
Observable.just("1", "2", "3")
.subscribe(
value -> Log.d("debug", value),
throwable -> Log.d("debug", throwable.getMessage()),
() -> Log.d("debug", "complete"));
Lambda expressions rely on type inference to fill in the blanks. Notice that the right-hand side of the arrow does not require a return statement if you do not surround the block with { and }. Also notice that a function with zero or multiple arguments need parenthesis enclosing them.
The same call in Kotlin passes each argument as its own lambda inside the parentheses (trailing-lambda placement only applies when the lambda is the final argument):
Observable.just("1", "2", "3")
.subscribe(
{ value -> Log.d("debug", value) },
{ throwable -> Log.d("debug", throwable.message ?: throwable.toString()) },
{ Log.d("debug", "complete") }
)
You can look to the left of Android Studio to see how it is inferring which type to use:
Setup
Lambda expressions, method references, and the rest of the Java 8 language features are supported out of the box by the Android Gradle Plugin (AGP) 3.0 or higher — no external plugin (such as the old Retrolambda) and no opt-in toolchain (such as the deprecated Jack) is required. AGP relies on a bytecode-level desugar step to make the features run on every supported minSdk.
To enable Java 8 source on a module, set sourceCompatibility and targetCompatibility in the module-level build.gradle (Groovy DSL):
android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
// For Kotlin projects, also set the JVM target.
kotlinOptions {
jvmTarget = "1.8"
}
}
Or, in build.gradle.kts (Kotlin DSL) — note the = assignment operator:
android {
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
}
That is the entire setup — there is no separate apply plugin: 'me.tatarka.retrolambda', no jackOptions { enabled true }, and no need to add a custom buildscript classpath. Newly created projects from Android Studio already include this block.
Heads up about the old approaches. If you are reading older tutorials or maintaining an app that still references them:
- The Jack toolchain was officially deprecated by Google in March 2017 and removed from later AGP releases. Remove any
jackOptionsblock from yourbuild.gradle.- The third-party Gradle Retrolambda plugin has been explicitly deprecated by its maintainer in favor of AGP’s built-in support. Remove
apply plugin: 'me.tatarka.retrolambda'and the matchingclasspathdependency.
Using Java 8+ APIs (optional)
If you also want to use Java 8+ standard library APIs such as java.time, java.util.stream, or java.util.Optional across all supported minSdk levels, enable core library desugaring (requires AGP 4.0+):
// build.gradle (Groovy DSL)
android {
compileOptions {
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3'
}
Or for build.gradle.kts (Kotlin DSL):
android {
compileOptions {
isCoreLibraryDesugaringEnabled = true
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
}
dependencies {
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.3")
}
See Java 8+ API desugaring support for the full feature list and the latest desugar_jdk_libs version.
Converting Lambda Expressions with Android Studio
If you wish to convert your code to lambda expressions, move your cursor to the slightly greyed out section of your anonymous class and look on the left-hand side of the Android Studio editor for the light bulb:
Once you see the Replace with lambda appear, you can also apply Fix all click on the left-hand side to convert all the possible candidates automatically as outlined here.
Troubleshooting
- If lambda expressions fail to compile with a message about Java 8 source not being supported, double-check that the
compileOptionsblock above is set inside theandroid { ... }block of your module-levelbuild.gradle, not the root project file. - Make sure the JDK configured in Android Studio (Settings → Build, Execution, Deployment → Build Tools → Gradle) is JDK 8 or newer — recent versions of Android Studio ship with an embedded JDK that satisfies this automatically.
Attribution
This guide was originally drafted by Adegeye Mayowa.