diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/LauncherActivity.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/LauncherActivity.kt
index 824f9f06943fb8d1588e9155f00c26fcbff8bfcf..7a96494ec5fa66bcf1ea1fb7e4ab195e8dd9a20b 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/LauncherActivity.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/LauncherActivity.kt
@@ -5,24 +5,28 @@ import android.net.Uri
 import android.os.Bundle
 import android.widget.Toast
 import androidx.appcompat.app.AppCompatActivity
+import androidx.lifecycle.lifecycleScope
 import de.rki.coronawarnapp.http.DynamicURLs
 import de.rki.coronawarnapp.storage.LocalData
 import de.rki.coronawarnapp.ui.main.MainActivity
 import de.rki.coronawarnapp.ui.onboarding.OnboardingActivity
+import de.rki.coronawarnapp.update.UpdateChecker
+import kotlinx.coroutines.launch
 
 class LauncherActivity : AppCompatActivity() {
     companion object {
         private val TAG: String? = LauncherActivity::class.simpleName
     }
 
+    private lateinit var updateChecker: UpdateChecker
+
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         retrieveCustomURLsFromSchema(intent.data)
+        updateChecker = UpdateChecker(this)
 
-        if (LocalData.isOnboarded()) {
-            startMainActivity()
-        } else {
-            startOnboardingActivity()
+        lifecycleScope.launch {
+            updateChecker.checkForUpdate()
         }
     }
 
@@ -54,15 +58,31 @@ class LauncherActivity : AppCompatActivity() {
         }
     }
 
+    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+        super.onActivityResult(requestCode, resultCode, data)
+
+        updateChecker.onActivityResult(requestCode, resultCode)
+    }
+
+    fun navigateToActivities() {
+        if (LocalData.isOnboarded()) {
+            startMainActivity()
+        } else {
+            startOnboardingActivity()
+        }
+    }
+
     private fun startOnboardingActivity() {
         val onboardingActivity = Intent(this, OnboardingActivity::class.java)
         startActivity(onboardingActivity)
+        this.overridePendingTransition(0, 0)
         finish()
     }
 
     private fun startMainActivity() {
         val mainActivityIntent = Intent(this, MainActivity::class.java)
         startActivity(mainActivityIntent)
+        this.overridePendingTransition(0, 0)
         finish()
     }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/update/UpdateChecker.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/update/UpdateChecker.kt
new file mode 100644
index 0000000000000000000000000000000000000000..07bf9cc0ede200968366a53ff982de42d4e04618
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/update/UpdateChecker.kt
@@ -0,0 +1,160 @@
+package de.rki.coronawarnapp.update
+
+import android.app.Activity.RESULT_CANCELED
+import android.app.Activity.RESULT_OK
+import android.content.IntentSender.SendIntentException
+import android.util.Log
+import android.widget.Toast
+import androidx.appcompat.app.AlertDialog
+import com.google.android.play.core.appupdate.AppUpdateInfo
+import com.google.android.play.core.appupdate.AppUpdateManager
+import com.google.android.play.core.appupdate.AppUpdateManagerFactory
+import com.google.android.play.core.install.model.ActivityResult.RESULT_IN_APP_UPDATE_FAILED
+import com.google.android.play.core.install.model.AppUpdateType
+import com.google.android.play.core.install.model.UpdateAvailability
+import de.rki.coronawarnapp.BuildConfig
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.server.protocols.ApplicationConfigurationOuterClass
+import de.rki.coronawarnapp.service.applicationconfiguration.ApplicationConfigurationService
+import de.rki.coronawarnapp.ui.LauncherActivity
+import kotlin.coroutines.resume
+import kotlin.coroutines.resumeWithException
+import kotlin.coroutines.suspendCoroutine
+
+class UpdateChecker(private val activity: LauncherActivity) {
+
+    companion object {
+        val TAG: String? = UpdateChecker::class.simpleName
+        private const val REQUEST_CODE = 100
+    }
+
+    suspend fun checkForUpdate() {
+
+        // check if an update is needed based on server config
+        val updateNeededFromServer: Boolean = try {
+            checkIfUpdatesNeededFromServer()
+        }
+        // TODO replace with signature exception
+        catch (exception: Exception) {
+            true
+        }
+
+        // get AppUpdateManager
+        val baseContext = activity.baseContext
+        val appUpdateManager = AppUpdateManagerFactory.create(baseContext)
+
+        var appUpdateInfo: AppUpdateInfo? = null
+
+        val updateAvailableFromGooglePlay = try {
+            appUpdateInfo = checkForGooglePlayUpdate(appUpdateManager)
+
+            val availability = appUpdateInfo.updateAvailability()
+            val immediateUpdateAllowed =
+                appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)
+
+            availability == UpdateAvailability.UPDATE_AVAILABLE && immediateUpdateAllowed
+        } catch (exception: Exception) {
+            false
+        }
+
+        if (updateNeededFromServer && updateAvailableFromGooglePlay && appUpdateInfo != null) {
+            Log.i(TAG, "show update dialog")
+            showUpdateAvailableDialog(appUpdateManager, appUpdateInfo)
+        } else {
+            activity.navigateToActivities()
+        }
+    }
+
+    private fun showUpdateAvailableDialog(
+        appUpdateManager: AppUpdateManager,
+        appUpdateInfo: AppUpdateInfo
+    ) {
+        AlertDialog.Builder(activity)
+            .setTitle(activity.getString(R.string.update_dialog_title))
+            .setMessage(activity.getString(R.string.update_dialog_message))
+            .setPositiveButton(activity.getString(R.string.update_dialog_button)) { _, _ ->
+                startGooglePlayUpdateFlow(appUpdateManager, appUpdateInfo)
+            }
+            .create().show()
+    }
+
+    private fun startGooglePlayUpdateFlow(
+        appUpdateManager: AppUpdateManager,
+        appUpdateInfo: AppUpdateInfo
+    ) {
+        try {
+            appUpdateManager.startUpdateFlowForResult(
+                appUpdateInfo,
+                AppUpdateType.IMMEDIATE,
+                activity,
+                REQUEST_CODE
+            )
+        } catch (exception: SendIntentException) {
+            Log.i(TAG, exception.toString())
+        }
+    }
+
+    fun onActivityResult(requestCode: Int, resultCode: Int) {
+        if (REQUEST_CODE == requestCode) {
+
+            // TODO react to these
+            when (resultCode) {
+                RESULT_OK -> {
+                    Log.i(TAG, "startFlowResult RESULT_OK")
+                    activity.navigateToActivities()
+                }
+                RESULT_CANCELED -> {
+                    Log.i(TAG, "startFlowResult RESULT_CANCELED")
+                }
+                RESULT_IN_APP_UPDATE_FAILED -> {
+                    Log.i(TAG, "startFlowResult RESULT_IN_APP_UPDATE_FAILED")
+                    val toast = Toast.makeText(activity, "In app update failed", Toast.LENGTH_LONG)
+                    toast.show()
+                    activity.navigateToActivities()
+                }
+            }
+        }
+    }
+
+    private suspend fun checkIfUpdatesNeededFromServer(): Boolean {
+
+        val applicationConfigurationFromServer =
+            ApplicationConfigurationService.asyncRetrieveApplicationConfiguration()
+
+        val minVersionFromServer = applicationConfigurationFromServer.appVersion.android.min
+        val minVersionFromServerString =
+            constructSemanticVersionString(minVersionFromServer)
+        Log.i(
+            TAG,
+            "minVersionStringFromServer:" + constructSemanticVersionString(
+                minVersionFromServer
+            )
+        )
+        Log.i(TAG, "Current app version:" + BuildConfig.VERSION_NAME)
+
+        val needsImmediateUpdate = VersionComparator.isVersionOlder(
+            BuildConfig.VERSION_NAME,
+            minVersionFromServerString
+        )
+        Log.i(TAG, "needs update:" + needsImmediateUpdate)
+        return true
+    }
+
+    private fun constructSemanticVersionString(
+        semanticVersion: ApplicationConfigurationOuterClass.SemanticVersion
+    ): String {
+        return semanticVersion.major.toString() + "." +
+                semanticVersion.minor.toString() + "." +
+                semanticVersion.patch.toString()
+    }
+
+    private suspend fun checkForGooglePlayUpdate(appUpdateManager: AppUpdateManager) =
+        suspendCoroutine<AppUpdateInfo> { cont ->
+            val appUpdateInfoTask = appUpdateManager.appUpdateInfo
+            appUpdateInfoTask.addOnSuccessListener {
+                cont.resume(it)
+            }.addOnFailureListener {
+                cont.resumeWithException(it)
+            }
+        }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/update/VersionComparator.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/update/VersionComparator.kt
new file mode 100644
index 0000000000000000000000000000000000000000..1df0b5dde051662beb2912bb2c008701666b6d8e
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/update/VersionComparator.kt
@@ -0,0 +1,30 @@
+package de.rki.coronawarnapp.update
+
+object VersionComparator {
+
+    fun isVersionOlder(currentVersion: String, versionToCompareTo: String): Boolean {
+        var isVersionOlder = false
+
+        val delimiter = "."
+
+        val currentVersionParts = currentVersion.split(delimiter)
+        val currentVersionMajor = currentVersionParts[0].toInt()
+        val currentVersionMinor = currentVersionParts[1].toInt()
+        val currentVersionPatch = currentVersionParts[2].toInt()
+
+        val versionToCompareParts = versionToCompareTo.split(delimiter)
+        val versionToCompareMajor = versionToCompareParts[0].toInt()
+        val versionToCompareMinor = versionToCompareParts[1].toInt()
+        val versionToComparePatch = versionToCompareParts[2].toInt()
+
+        if (versionToCompareMajor > currentVersionMajor) {
+            isVersionOlder = true
+        } else if (versionToCompareMinor > currentVersionMinor) {
+            isVersionOlder = true
+        } else if (versionToComparePatch > currentVersionPatch) {
+            isVersionOlder = true
+        }
+
+        return isVersionOlder
+    }
+}
diff --git a/Corona-Warn-App/src/main/res/values/strings.xml b/Corona-Warn-App/src/main/res/values/strings.xml
index f9c2f4e33671dc207ee3aeca11957f7e790c44e7..0add622529cc819a528466e4e4aecf3db38d8e2b 100644
--- a/Corona-Warn-App/src/main/res/values/strings.xml
+++ b/Corona-Warn-App/src/main/res/values/strings.xml
@@ -150,6 +150,14 @@
     <string name="notification_headline">Corona-Warn App</string>
     <string name="notification_body">Es gibt Neuigkeiten von Ihrer Corona-Warn-App</string>
 
+    <!-- ####################################
+              App Auto Update
+    ###################################### -->
+
+    <string name="update_dialog_title">Update verfügbar</string>
+    <string name="update_dialog_message">Bitte die app updaten</string>
+    <string name="update_dialog_button">Update</string>
+
     <!-- ####################################
                   Risk Card
     ###################################### -->
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/update/VersionComparatorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/update/VersionComparatorTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..421e73c5ad404152e5a23cc0408f688aad8d7ffc
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/update/VersionComparatorTest.kt
@@ -0,0 +1,51 @@
+package de.rki.coronawarnapp.update
+
+import org.hamcrest.MatcherAssert.assertThat
+import org.hamcrest.Matchers.`is`
+import org.junit.Test
+
+class VerificationServiceTest {
+
+    @Test
+    fun testVersionMajorOlder() {
+        val result = VersionComparator.isVersionOlder("1.0.0", "2.0.0")
+        assertThat(result, `is`(true))
+    }
+
+    @Test
+    fun testVersionMinorOlder() {
+        val result = VersionComparator.isVersionOlder("1.0.0", "1.1.0")
+        assertThat(result, `is`(true))
+    }
+
+    @Test
+    fun testVersionPatchOlder() {
+        val result = VersionComparator.isVersionOlder("1.0.1", "1.0.2")
+        assertThat(result, `is`(true))
+    }
+
+    @Test
+    fun testVersionMajorNewer() {
+        val result = VersionComparator.isVersionOlder("2.0.0", "1.0.0")
+        assertThat(result, `is`(false))
+    }
+
+    @Test
+    fun testVersionMinorNewer() {
+        val result = VersionComparator.isVersionOlder("1.2.0", "1.1.0")
+        assertThat(result, `is`(false))
+    }
+
+    @Test
+    fun testVersionPatchNewer() {
+        val result = VersionComparator.isVersionOlder("1.0.3", "1.0.2")
+        assertThat(result, `is`(false))
+    }
+
+    @Test
+    fun testSameVersion() {
+        val result = VersionComparator.isVersionOlder("1.0.1", "1.0.1")
+        assertThat(result, `is`(false))
+    }
+
+}
\ No newline at end of file