Index: src/org/openstreetmap/josm/actions/upload/ApiPreconditionCheckerHook.java
===================================================================
--- src/org/openstreetmap/josm/actions/upload/ApiPreconditionCheckerHook.java	(revision 19436)
+++ src/org/openstreetmap/josm/actions/upload/ApiPreconditionCheckerHook.java	(working copy)
@@ -22,6 +22,7 @@
 import org.openstreetmap.josm.io.OsmApiInitializationException;
 import org.openstreetmap.josm.io.OsmTransferCanceledException;
 import org.openstreetmap.josm.tools.Logging;
+import org.openstreetmap.josm.tools.Utils;
 
 /**
  * Checks certain basic conditions, that are listed in the OSM API
@@ -63,7 +64,7 @@
             for (Map.Entry<String, String> entry: osmPrimitive.getKeys().entrySet()) {
                 String key = entry.getKey();
                 String value = entry.getValue();
-                if (key.length() > Tagged.MAX_TAG_LENGTH) {
+                if (!Utils.checkCodePointCount(value, Tagged.MAX_TAG_LENGTH)) {
                     if (osmPrimitive.isDeleted()) {
                         // if OsmPrimitive is going to be deleted we automatically shorten the value
                         Logging.warn(
@@ -72,12 +73,12 @@
                                         Long.toString(osmPrimitive.getId())
                                 )
                         );
-                        osmPrimitive.put(key, value.substring(0, Tagged.MAX_TAG_LENGTH));
+                        osmPrimitive.put(key, Utils.shortenString(value, Tagged.MAX_TAG_LENGTH));
                         continue;
                     }
                     JOptionPane.showMessageDialog(MainApplication.getMainFrame(),
                             tr("Length of value for tag ''{0}'' on object {1} exceeds the max. allowed length {2}. Values length is {3}.",
-                                    key, Long.toString(osmPrimitive.getId()), Tagged.MAX_TAG_LENGTH, value.length()
+                                    key, Long.toString(osmPrimitive.getId()), Tagged.MAX_TAG_LENGTH, Utils.getCodePointCount(value)
                             ),
                             tr("Precondition violation"),
                             JOptionPane.ERROR_MESSAGE
Index: src/org/openstreetmap/josm/data/osm/Changeset.java
===================================================================
--- src/org/openstreetmap/josm/data/osm/Changeset.java	(revision 19436)
+++ src/org/openstreetmap/josm/data/osm/Changeset.java	(working copy)
@@ -16,6 +16,7 @@
 import org.openstreetmap.josm.data.Bounds;
 import org.openstreetmap.josm.data.coor.LatLon;
 import org.openstreetmap.josm.tools.CheckParameterUtil;
+import org.openstreetmap.josm.tools.Utils;
 
 /**
  * Represents a single changeset in JOSM. For now its only used during
@@ -312,7 +313,7 @@
     public void setKeys(Map<String, String> keys) {
         CheckParameterUtil.ensureParameterNotNull(keys, "keys");
         keys.values().stream()
-                .filter(value -> value != null && value.length() > MAX_CHANGESET_TAG_LENGTH)
+                .filter(value -> !Utils.checkCodePointCount(value, MAX_CHANGESET_TAG_LENGTH))
                 .findFirst()
                 .ifPresent(value -> {
                 throw new IllegalArgumentException("Changeset tag value is too long: "+value);
@@ -339,9 +340,10 @@
     @Override
     public void put(String key, String value) {
         CheckParameterUtil.ensureParameterNotNull(key, "key");
-        if (value != null && value.length() > MAX_CHANGESET_TAG_LENGTH) {
-            throw new IllegalArgumentException("Changeset tag value is too long: "+value);
+        if (!Utils.checkCodePointCount(value, MAX_CHANGESET_TAG_LENGTH)) {
+            throw new IllegalArgumentException("Changeset tag value is too long: " + value);
         }
+
         this.tags.put(key, value);
     }
 
Index: src/org/openstreetmap/josm/data/validation/tests/TagChecker.java
===================================================================
--- src/org/openstreetmap/josm/data/validation/tests/TagChecker.java	(revision 19436)
+++ src/org/openstreetmap/josm/data/validation/tests/TagChecker.java	(working copy)
@@ -1015,9 +1015,10 @@
                     .build());
             withErrors.put(p, "UUCV");
         }
-        if ((value.length() > Tagged.MAX_TAG_LENGTH) && !withErrors.contains(p, "LV")) {
+        final int codePoints = Utils.getCodePointCount(value);
+        if (codePoints > Tagged.MAX_TAG_LENGTH && !withErrors.contains(p, "LV")) {
             errors.add(TestError.builder(this, Severity.ERROR, LONG_VALUE)
-                    .message(tr("Tag value longer than {0} characters ({1} characters)", Tagged.MAX_TAG_LENGTH, value.length()), s, key)
+                    .message(tr("Tag value longer than {0} characters ({1} characters)", Tagged.MAX_TAG_LENGTH, codePoints), s, key)
                     .primitives(p)
                     .build());
             withErrors.put(p, "LV");
@@ -1064,9 +1065,10 @@
                     .build());
             withErrors.put(p, "ICK");
         }
-        if (key.length() > Tagged.MAX_TAG_LENGTH && !withErrors.contains(p, "LK")) {
+        final int codePoints = Utils.getCodePointCount(key);
+        if (codePoints > Tagged.MAX_TAG_LENGTH && !withErrors.contains(p, "LK")) {
             errors.add(TestError.builder(this, Severity.ERROR, LONG_KEY)
-                    .message(tr("Tag key longer than {0} characters ({1} characters)", Tagged.MAX_TAG_LENGTH, key.length()), s, key)
+                    .message(tr("Tag key longer than {0} characters ({1} characters)", Tagged.MAX_TAG_LENGTH, codePoints), s, key)
                     .primitives(p)
                     .build());
             withErrors.put(p, "LK");
Index: src/org/openstreetmap/josm/tools/TextTagParser.java
===================================================================
--- src/org/openstreetmap/josm/tools/TextTagParser.java	(revision 19436)
+++ src/org/openstreetmap/josm/tools/TextTagParser.java	(working copy)
@@ -154,8 +154,8 @@
                 r = callback.warning(tr("Suspicious characters in key:"), key, "tags.paste.keydoesnotmatch");
                 if (r == 2 || r == 3) return false; if (r == 4) return true;
             }
-            if (value.length() > MAX_VALUE_LENGTH) {
-                r = callback.warning(tr("Value is too long (max {0} characters):", MAX_VALUE_LENGTH), value, "tags.paste.valuetoolong");
+            if (!Utils.checkCodePointCount(value, MAX_VALUE_LENGTH)) {
+                r = callback.warning(tr("Value is too long (max {0} characters):", MAX_VALUE_LENGTH), value, "");
                 if (r == 2 || r == 3) return false; if (r == 4) return true;
             }
         }
Index: src/org/openstreetmap/josm/tools/Utils.java
===================================================================
--- src/org/openstreetmap/josm/tools/Utils.java	(revision 19436)
+++ src/org/openstreetmap/josm/tools/Utils.java	(working copy)
@@ -2025,4 +2025,28 @@
             default: throw new IllegalArgumentException("Invalid length unit: " + unit);
         }
     }
+
+    /**
+     * Calculate the number of unicode code points. See #24446
+     * @param s the string
+     * @return 0 if s is null or empty, else the number of code points
+     * @since xxx
+     */
+    public static int getCodePointCount(String s) {
+        if (s == null)
+            return 0;
+        return s.codePointCount(0, s.length());
+    }
+
+    /**
+     * Check if a given string has more than the allowed number of code points.
+     * See #24446. The OSM server checks this number, not the value returned by String.length()
+     * @param s the string
+     * @param maxLen the maximum number of code points
+     * @return true if s is null or within the given limit, false else
+     * @since xxx
+     */
+    public static boolean checkCodePointCount(String s, int maxLen) {
+        return getCodePointCount(s) <= maxLen;
+    }
 }
