Index: src/org/openstreetmap/josm/gui/MainApplication.java
===================================================================
--- src/org/openstreetmap/josm/gui/MainApplication.java	(revision 17814)
+++ src/org/openstreetmap/josm/gui/MainApplication.java	(working copy)
@@ -148,6 +148,7 @@
 import org.openstreetmap.josm.spi.lifecycle.InitStatusListener;
 import org.openstreetmap.josm.spi.lifecycle.Lifecycle;
 import org.openstreetmap.josm.spi.preferences.Config;
+import org.openstreetmap.josm.tools.AntialiasingUtil;
 import org.openstreetmap.josm.tools.FontsManager;
 import org.openstreetmap.josm.tools.GBC;
 import org.openstreetmap.josm.tools.Http1Client;
@@ -1033,6 +1034,11 @@
         }
         // Disable automatic POST retry after 5 minutes, see #17882 / https://bugs.openjdk.java.net/browse/JDK-6382788
         Utils.updateSystemProperty("sun.net.http.retryPost", "false");
+        // Force text antialiasing, not including mappaint text, when antialiasing is not enabled by default on X11
+        // See #20706
+        if (AntialiasingUtil.isDisabledX11() && !AntialiasingUtil.tryEnableX11()) {
+            Logging.warn("Antialiasing could not be enabled");
+        }
     }
 
     /**
Index: src/org/openstreetmap/josm/gui/widgets/JosmEditorPane.java
===================================================================
--- src/org/openstreetmap/josm/gui/widgets/JosmEditorPane.java	(revision 17814)
+++ src/org/openstreetmap/josm/gui/widgets/JosmEditorPane.java	(working copy)
@@ -3,6 +3,9 @@
 
 import java.awt.Color;
 import java.awt.Font;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URL;
@@ -86,6 +89,15 @@
         return conn.getContent();
     }
 
+    @Override
+    public void paintComponent(Graphics g) {
+        // Force antialiasing within the JosmEditorPane for antialiased bullet points
+        Graphics2D g2d = (Graphics2D) g.create();
+        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+        super.paintComponent(g2d);
+        g2d.dispose();
+    }
+
     /**
      * Adapts a {@link JEditorPane} to be used as a powerful replacement of {@link javax.swing.JLabel}.
      * @param pane The editor pane to adapt
Index: src/org/openstreetmap/josm/tools/AntialiasingUtil.java
===================================================================
--- src/org/openstreetmap/josm/tools/AntialiasingUtil.java	(nonexistent)
+++ src/org/openstreetmap/josm/tools/AntialiasingUtil.java	(working copy)
@@ -0,0 +1,105 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.tools;
+
+import java.awt.Toolkit;
+import java.awt.RenderingHints;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Map;
+
+/**
+ * Utility class to detect and enable antialiasing on systems which do not enable it by default
+ * @since xxx
+ */
+public final class AntialiasingUtil {
+
+    private AntialiasingUtil() {
+        // Hide default constructor for util classes
+    }
+
+    /**
+     * Determines if the current system is running X11 and has antialiasing disabled
+     */
+    public static boolean isDisabledX11() {
+        Toolkit toolkit = Toolkit.getDefaultToolkit();
+        return ReflectionUtils.extendsClassWithName(toolkit.getClass(), "sun.awt.X11.XToolkit")
+                && toolkit.getDesktopProperty("awt.font.desktophints") == null;
+    }
+
+    /**
+     * Attempts to enable antialiasing on X11
+     * @return true if enabling antialiasing was successful, otherwise false
+     */
+    public static boolean tryEnableX11() {
+        try {
+            Utils.updateSystemProperty("awt.useSystemAAFontSettings", "on");
+
+            Toolkit toolkit = Toolkit.getDefaultToolkit();
+
+            // Unset cached desktop hints
+            Map<String, Object> desktopProperties = getToolkitDesktopProperties(toolkit);
+            desktopProperties.remove("awt.font.desktophints");
+
+            // Unset flag so new font hints look for new value of awt.useSystemAAFontSettings
+            Field checkedSystemAAFontSettings = getSunToolkitCheckedSystemAAFontSettings(toolkit);
+            checkedSystemAAFontSettings.setBoolean(toolkit, false);
+
+            // Get antialiasing font hints
+            Method getDesktopFontHints = getSunToolkitGetDesktopFontHints(toolkit);
+            Object renderingHints = getDesktopFontHints.invoke(null);
+
+            desktopProperties.put("awt.font.desktophints", renderingHints);
+        } catch (ReflectiveOperationException | RuntimeException e) {
+            Logging.log(Logging.LEVEL_WARN, e);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Get the value of the desktopProperties field from {@link java.awt.Toolkit} through reflection. May emit an
+     * illegal reflective access warning.
+     * @param toolkit the toolkit to get the desktopProperties from
+     * @return the value of the desktopProperties field from toolkit
+     * @throws ReflectiveOperationException if the field could not be found
+     * @throws RuntimeException if the field could not be set accessible
+     */
+    private static Map<String, Object> getToolkitDesktopProperties(Toolkit toolkit) throws ReflectiveOperationException, RuntimeException {
+        Field desktopPropertiesField = Toolkit.class.getDeclaredField("desktopProperties");
+        ReflectionUtils.setObjectsAccessible(desktopPropertiesField);
+        @SuppressWarnings("unchecked") // desktopProperties has type Map<String, Object> and is protected
+        Map<String, Object> desktopProperties = (Map<String, Object>) desktopPropertiesField.get(toolkit);
+        return desktopProperties;
+    }
+
+    /**
+     * Get the boolean checkedSystemAAFontSettings field from sun.awt.SunToolkit through reflection. May emit an
+     * illegal reflective access warning.
+     * @param toolkit the toolkit to get the checkedSystemAAFontSettings field from
+     * @return the checkedSystemAAFontSettings field or null if it could not be found
+     * @throws RuntimeException if the field could not be set accessible
+     */
+    private static Field getSunToolkitCheckedSystemAAFontSettings(Toolkit toolkit) throws RuntimeException {
+        Field field = ReflectionUtils.getSuperclassField(toolkit.getClass(), "checkedSystemAAFontSettings", boolean.class);
+        ReflectionUtils.setObjectsAccessible(field);
+        return field;
+    }
+
+    /**
+     * Get the getDesktopFontHints method or no arguments returning {@link RenderingHints} from sun.awt.SunToolkit
+     * through reflection. May emit an illegal reflective access warning.
+     * @param toolkit the toolkit to get the getDesktopFontHints method from
+     * @return the getDesktopFontHints method or null if it could not be found
+     * @throws RuntimeException if the method could not be set accessible
+     */
+    private static Method getSunToolkitGetDesktopFontHints(Toolkit toolkit) throws RuntimeException {
+        Method getDesktopFontHints = ReflectionUtils.getInheritedMethod(
+                toolkit.getClass(),
+                "getDesktopFontHints",
+                new Class[] {},
+                RenderingHints.class
+        );
+        ReflectionUtils.setObjectsAccessible(getDesktopFontHints);
+        return getDesktopFontHints;
+    }
+}
Index: src/org/openstreetmap/josm/tools/ReflectionUtils.java
===================================================================
--- src/org/openstreetmap/josm/tools/ReflectionUtils.java	(revision 17814)
+++ src/org/openstreetmap/josm/tools/ReflectionUtils.java	(working copy)
@@ -2,9 +2,13 @@
 package org.openstreetmap.josm.tools;
 
 import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
+import java.util.Arrays;
 import java.util.Collection;
+import java.util.Optional;
 import java.util.function.Function;
 
 import org.openstreetmap.josm.plugins.PluginHandler;
@@ -70,4 +74,65 @@
         }
         return null;
     }
+
+    /**
+     * Get the matching method implemented directly on the class or inherited from a superclass (or superclass of a superclass...).
+     * @param searchOn class to start the search from
+     * @param name the name that the matching method must have
+     * @param parameterTypes the types the parameters of the matching method must have
+     * @param returnType the type the matching method must return
+     * @return the first matching method found, starting from the given class and proceeding through superclasses, or
+     *  null if no match was found
+     * @since xxx
+     */
+    public static Method getInheritedMethod(Class<?> searchOn, String name, Class<?>[] parameterTypes, Class<?> returnType) {
+        while (searchOn != null) {
+            Optional<Method> firstMatch = Arrays.stream(searchOn.getDeclaredMethods())
+                    .filter(method -> methodMatches(method, name, parameterTypes, returnType))
+                    .findFirst();
+            if (firstMatch.isPresent()) {
+                return firstMatch.get();
+            }
+            searchOn = searchOn.getSuperclass();
+        }
+        return null;
+    }
+
+    private static boolean methodMatches(Method method, String expectedName, Class<?>[] expectedParameterTypes, Class<?> expectedReturnType) {
+        return method.getName().equals(expectedName)
+                && Arrays.equals(method.getParameterTypes(), expectedParameterTypes)
+                && method.getReturnType().equals(expectedReturnType);
+    }
+
+    /**
+     * Get the matching field which is part of the class or a superclass (or superclass of a superclass...).
+     * @param searchOn class to start the search from
+     * @param name the name that the matching field must have
+     * @param type the type that the matching field must have
+     * @return the first matching field found, starting from the given class and proceeding through superclasses, or
+     *  null if no match was found
+     * @since xxx
+     */
+    public static Field getSuperclassField(Class<?> searchOn, String name, Class<?> type) {
+        while (searchOn != null) {
+            Optional<Field> firstMatch = Arrays.stream(searchOn.getDeclaredFields())
+                    .filter(field -> field.getName().equals(name) && field.getType().equals(type))
+                    .findFirst();
+            if (firstMatch.isPresent()) {
+                return firstMatch.get();
+            }
+            searchOn = searchOn.getSuperclass();
+        }
+        return null;
+    }
+
+    public static boolean extendsClassWithName(Class<?> doesExtend, String className) {
+        while (doesExtend != null) {
+            if (doesExtend.getName().equals(className)) {
+                return true;
+            }
+            doesExtend = doesExtend.getSuperclass();
+        }
+        return false;
+    }
 }
