diff --git a/android/app/src/main/cpp/app/organicmaps/Framework.cpp b/android/app/src/main/cpp/app/organicmaps/Framework.cpp
index 4eaf04efa2..e584ca32db 100644
--- a/android/app/src/main/cpp/app/organicmaps/Framework.cpp
+++ b/android/app/src/main/cpp/app/organicmaps/Framework.cpp
@@ -1953,4 +1953,10 @@ Java_app_organicmaps_Framework_nativeGetKayakHotelLink(JNIEnv * env, jclass, jst
   return url.empty() ? nullptr : jni::ToJavaString(env, url);
 }
 
+JNIEXPORT jint JNICALL
+Java_app_organicmaps_Framework_nativeGetUnits(JNIEnv *, jclass)
+{
+  return static_cast<jint>(measurement_utils::GetMeasurementUnits());
+}
+
 }  // extern "C"
diff --git a/android/app/src/main/cpp/app/organicmaps/util/StringUtils.cpp b/android/app/src/main/cpp/app/organicmaps/util/StringUtils.cpp
index e99a34b0ed..dbf7199809 100644
--- a/android/app/src/main/cpp/app/organicmaps/util/StringUtils.cpp
+++ b/android/app/src/main/cpp/app/organicmaps/util/StringUtils.cpp
@@ -57,6 +57,14 @@ JNIEXPORT jobject JNICALL Java_app_organicmaps_util_StringUtils_nativeFormatSpee
                       platform::GetLocalizedSpeedUnits(units));
 }
 
+JNIEXPORT jobject JNICALL Java_app_organicmaps_util_StringUtils_nativeStringFormatSpeedAndUnits(
+        JNIEnv * env, jclass thiz, jdouble metersPerSecond)
+{
+  auto const units = measurement_utils::GetMeasurementUnits();
+  return jni::ToJavaString(env, measurement_utils::FormatSpeedNumeric(metersPerSecond, units) +
+                           ";" + platform::GetLocalizedSpeedUnits(units));
+}
+
 JNIEXPORT jobject JNICALL
 Java_app_organicmaps_util_StringUtils_nativeFormatDistance(JNIEnv * env, jclass, jdouble distanceInMeters)
 {
diff --git a/android/app/src/main/java/app/organicmaps/Framework.java b/android/app/src/main/java/app/organicmaps/Framework.java
index 2a076225c3..fe8c879f0d 100644
--- a/android/app/src/main/java/app/organicmaps/Framework.java
+++ b/android/app/src/main/java/app/organicmaps/Framework.java
@@ -60,6 +60,10 @@ public class Framework
   public static final int ROUTER_TYPE_TRANSIT = 3;
   public static final int ROUTER_TYPE_RULER = 4;
 
+  // Units values shall be the same as the ones defined in c++ in <platform/measurement_utils.hpp>.
+  public static final int UNITS_METRIC = 0;
+  public static final int UNITS_IMPERIAL = 1;
+
   @Retention(RetentionPolicy.SOURCE)
   @IntDef({ROUTE_REBUILD_AFTER_POINTS_LOADING})
   public @interface RouteRecommendationType {}
@@ -460,4 +464,6 @@ public class Framework
   @Nullable
   public static native String nativeGetKayakHotelLink(@NonNull String countryIsoCode, @NonNull String uri,
                                                       long firstDaySec, long lastDaySec, boolean isReferral);
+
+  public static native int nativeGetUnits();
 }
diff --git a/android/app/src/main/java/app/organicmaps/routing/NavigationController.java b/android/app/src/main/java/app/organicmaps/routing/NavigationController.java
index a789dd4c2f..41fab8cb1f 100644
--- a/android/app/src/main/java/app/organicmaps/routing/NavigationController.java
+++ b/android/app/src/main/java/app/organicmaps/routing/NavigationController.java
@@ -155,7 +155,7 @@ public class NavigationController implements TrafficManager.TrafficCallback,
       updateVehicle(info);
 
     updateStreetView(info);
-    mNavMenu.update(info);
+    mNavMenu.update(info, Framework.nativeGetUnits());
   }
 
   private void updateStreetView(@NonNull RoutingInfo info)
diff --git a/android/app/src/main/java/app/organicmaps/util/Speed.java b/android/app/src/main/java/app/organicmaps/util/Speed.java
new file mode 100644
index 0000000000..15c7b5b485
--- /dev/null
+++ b/android/app/src/main/java/app/organicmaps/util/Speed.java
@@ -0,0 +1,171 @@
+package app.organicmaps.util;
+
+import android.content.Context;
+import android.util.Pair;
+
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.text.NumberFormat;
+import java.util.Formatter;
+import java.util.Locale;
+
+import app.organicmaps.Framework;
+import app.organicmaps.util.log.Logger;
+
+public class Speed
+{
+  private static String mUnitStringKmh = "km/h";
+  private static String mUnitStringMiph = "mph";
+
+  private static char mDecimalSeparator = Character.MIN_VALUE;
+
+  public static double MpsToKmph(double mps) { return mps * 3.6; }
+  public static double MpsToMiph(double mps) { return mps * 2.236936; }
+
+  public static void setUnitStringKmh(String unitStringKmh) { mUnitStringKmh = unitStringKmh; }
+  public static void setUnitStringMiph(String unitStringMiph) { mUnitStringMiph = unitStringMiph; }
+
+  private final static DecimalFormat mDecimalFormatNoDecimal = new DecimalFormat("#");
+  private final static DecimalFormat mDecimalFormatOneDecimal = new DecimalFormat("0.0");
+
+  private final static Locale mLocale = Locale.getDefault();
+
+  private final static StringBuilder mSb = new StringBuilder();
+  private final static Formatter mFormatter = new Formatter(mSb, mLocale);
+
+  private final static NumberFormat mNumberFormatNoDecimal = NumberFormat.getInstance(mLocale);
+  private final static NumberFormat mNumberFormatOneDecimal = NumberFormat.getInstance(mLocale);
+
+  public static Pair<String, String> formatMeasurements(double speedInMetersPerSecond, int units,
+                                            Context context)
+  {
+    double speedValue;
+    String unitsString;
+
+    if (units == Framework.UNITS_IMPERIAL)
+    {
+      speedValue = MpsToMiph(speedInMetersPerSecond);
+      unitsString = mUnitStringMiph;
+    }
+    else
+    {
+      speedValue = MpsToKmph(speedInMetersPerSecond);
+      unitsString = mUnitStringKmh;
+    }
+
+    // Option 1: String.format()
+    long start1 = System.nanoTime();
+    String formatString = (speedValue < 10.0)? "%.1f" : "%.0f";
+    String speedString = String.format(mLocale, formatString, speedValue);
+    long elapsed1 = System.nanoTime() - start1;
+    Logger.i("LOCALE_MEASURE", "1) " + speedString);
+
+    // Option 2: DecimalFormat class
+    long start2 = System.nanoTime();
+    if (speedValue < 10.0)
+      speedString = mDecimalFormatOneDecimal.format(speedValue);
+    else
+      speedString = mDecimalFormatNoDecimal.format(speedValue);
+    long elapsed2 = System.nanoTime() - start2;
+    Logger.i("LOCALE_MEASURE", "2) " + speedString);
+
+    // Option 3: Long.toString() + StringBuffer.insert()
+    long start3 = System.nanoTime();
+    if (speedValue < 10.0)
+    {
+      speedString = Long.toString(Math.round(speedValue * 10.0));
+
+      StringBuffer buffer = new StringBuffer(speedString);
+
+      if (mDecimalSeparator == Character.MIN_VALUE)
+        mDecimalSeparator = DecimalFormatSymbols.getInstance().getDecimalSeparator();
+
+      // For low values (< 1.0), force to have 2 characters in string.
+      if (buffer.length() < 2)
+        buffer.insert(0, "0");
+
+      buffer.insert(1, mDecimalSeparator);
+
+      speedString = buffer.toString();
+    }
+    else
+      speedString = Long.toString(Math.round(speedValue));
+    long elapsed3 = System.nanoTime() - start3;
+    Logger.i("LOCALE_MEASURE", "3) " + speedString);
+
+    // Option 4: Formatter class
+    mSb.setLength(0);
+    long start4 = System.nanoTime();
+    if (speedValue < 10.0)
+      mFormatter.format("%.1f", speedValue);
+    else
+      mFormatter.format("%d", Math.round(speedValue));
+    speedString = mSb.toString();
+    long elapsed4 = System.nanoTime() - start4;
+    Logger.i("LOCALE_MEASURE", "4) " + speedString);
+
+    // Option 5: NumberFormat class
+    mNumberFormatNoDecimal.setMaximumFractionDigits(0);
+    mNumberFormatOneDecimal.setMaximumFractionDigits(1);
+    long start5 = System.nanoTime();
+    if (speedValue < 10.0)
+      speedString = mNumberFormatOneDecimal.format(speedValue);
+    else
+      speedString = mNumberFormatNoDecimal.format(Math.round(speedValue));
+    long elapsed5 = System.nanoTime() - start5;
+    Logger.i("LOCALE_MEASURE", "5) " + speedString);
+
+    String text = String.format(Locale.US,
+            "Java calls: %5d / %5d / %5d / %5d / %5d",
+            Math.round(0.001 * elapsed1),
+            Math.round(0.001 * elapsed2),
+            Math.round(0.001 * elapsed3),
+            Math.round(0.001 * elapsed4),
+            Math.round(0.001 * elapsed5));
+    Logger.i("LOCALE_MEASURE", text);
+
+    return new Pair<>(speedString, unitsString);
+  }
+
+  public static Pair<String, String> format(double speedInMetersPerSecond, int units,
+                                                        Context context)
+  {
+    double speedValue;
+    String unitsString;
+
+    if (units == Framework.UNITS_IMPERIAL)
+    {
+      speedValue = MpsToMiph(speedInMetersPerSecond);
+      unitsString = mUnitStringMiph;
+    }
+    else
+    {
+      speedValue = MpsToKmph(speedInMetersPerSecond);
+      unitsString = mUnitStringKmh;
+    }
+
+    String speedString;
+
+    if (speedValue < 10.0)
+    {
+      speedString = Long.toString(Math.round(speedValue * 10.0));
+
+      StringBuffer buffer = new StringBuffer(speedString);
+
+      if (mDecimalSeparator == Character.MIN_VALUE)
+        mDecimalSeparator = DecimalFormatSymbols.getInstance().getDecimalSeparator();
+
+      // For low values (< 1.0), force to have 2 characters in string.
+      if (buffer.length() < 2)
+        buffer.insert(0, "0");
+
+      buffer.insert(1, mDecimalSeparator);
+
+      speedString = buffer.toString();
+    }
+    else
+      speedString = Long.toString(Math.round(speedValue));
+
+    return new Pair<>(speedString, unitsString);
+  }
+}
diff --git a/android/app/src/main/java/app/organicmaps/util/StringUtils.java b/android/app/src/main/java/app/organicmaps/util/StringUtils.java
index d82b80a092..1ae77dd330 100644
--- a/android/app/src/main/java/app/organicmaps/util/StringUtils.java
+++ b/android/app/src/main/java/app/organicmaps/util/StringUtils.java
@@ -30,6 +30,7 @@ public class StringUtils
   public static native String[] nativeFilterContainsNormalized(String[] strings, String substr);
 
   public static native Pair<String, String> nativeFormatSpeedAndUnits(double metersPerSecond);
+  public static native String nativeStringFormatSpeedAndUnits(double metersPerSecond);
   public static native Distance nativeFormatDistance(double meters);
   @NonNull
   public static native Pair<String, String> nativeGetLocalizedDistanceUnits();
diff --git a/android/app/src/main/java/app/organicmaps/widget/menu/NavMenu.java b/android/app/src/main/java/app/organicmaps/widget/menu/NavMenu.java
index 1c772cd69d..518d0bb956 100644
--- a/android/app/src/main/java/app/organicmaps/widget/menu/NavMenu.java
+++ b/android/app/src/main/java/app/organicmaps/widget/menu/NavMenu.java
@@ -1,6 +1,7 @@
 package app.organicmaps.widget.menu;
 
 import android.location.Location;
+import android.util.Log;
 import android.util.Pair;
 import android.view.View;
 import android.widget.Button;
@@ -15,12 +16,16 @@ import app.organicmaps.location.LocationHelper;
 import app.organicmaps.routing.RoutingInfo;
 import app.organicmaps.sound.TtsPlayer;
 import app.organicmaps.util.Graphics;
+import app.organicmaps.util.Speed;
 import app.organicmaps.util.StringUtils;
 import app.organicmaps.util.UiUtils;
+import app.organicmaps.util.log.Logger;
+
 import com.google.android.material.progressindicator.LinearProgressIndicator;
 
 import java.time.LocalTime;
 import java.time.format.DateTimeFormatter;
+import java.util.Locale;
 import java.util.concurrent.TimeUnit;
 
 public class NavMenu
@@ -203,22 +208,57 @@ public class NavMenu
     mTimeEstimate.setText(localTime.format(DateTimeFormatter.ofPattern(format)));
   }
 
-  private void updateSpeedView(@NonNull RoutingInfo info)
+  private void updateSpeedView(@NonNull RoutingInfo info, int units)
   {
     final Location last = LocationHelper.from(mActivity).getSavedLocation();
     if (last == null)
       return;
 
+    // Log measurements for different Java implementations.
+    Speed.formatMeasurements(last.getSpeed(), units, mActivity.getApplicationContext());
+
+    // Speed formatting using native calls.
+    long start1 = System.nanoTime();
     Pair<String, String> speedAndUnits = StringUtils.nativeFormatSpeedAndUnits(last.getSpeed());
+    long elapsed1 = System.nanoTime() - start1;
 
     mSpeedUnits.setText(speedAndUnits.second);
     mSpeedValue.setText(speedAndUnits.first);
+
+    // Speed formatting using native calls. Speed and units is returned in a single string.
+    long start2 = System.nanoTime();
+    String speedAndUnitsString = StringUtils.nativeStringFormatSpeedAndUnits(last.getSpeed());
+    // Speed and units are separated by semicolon ";" in returned string.
+    int separatorPos = speedAndUnitsString.indexOf(";");
+    String speedString = speedAndUnitsString.substring(0, separatorPos);
+    String unitsString = speedAndUnitsString.substring(separatorPos + 1);
+    long elapsed2 = System.nanoTime() - start2;
+
+    mSpeedUnits.setText(speedString);
+    mSpeedValue.setText(unitsString);
+
+    // Speed formatting using Android Java calls.
+    long start3 = System.nanoTime();
+    speedAndUnits = Speed.format(last.getSpeed(), units, mActivity.getApplicationContext());
+    long elapsed3 = System.nanoTime() - start3;
+
+    mSpeedUnits.setText(speedAndUnits.second);
+    mSpeedValue.setText(speedAndUnits.first);
+
+    String text = String.format(Locale.US,
+            "Native calls: %5d / %5d / %5d",
+            Math.round(0.001 * elapsed1),
+            Math.round(0.001 * elapsed2),
+            Math.round(0.001 * elapsed3));
+
+    Logger.i("LOCALE_MEASURE", text);
+
     mSpeedViewContainer.setActivated(info.isSpeedLimitExceeded());
   }
 
-  public void update(@NonNull RoutingInfo info)
+  public void update(@NonNull RoutingInfo info, int units)
   {
-    updateSpeedView(info);
+    updateSpeedView(info, units);
     updateTime(info.totalTimeInSeconds);
     mDistanceValue.setText(info.distToTarget.mDistanceStr);
     mDistanceUnits.setText(info.distToTarget.getUnitsStr(mActivity.getApplicationContext()));