Index: /trunk/src/org/openstreetmap/josm/gui/layer/AbstractTileSourceLayer.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/layer/AbstractTileSourceLayer.java	(revision 18186)
+++ /trunk/src/org/openstreetmap/josm/gui/layer/AbstractTileSourceLayer.java	(revision 18187)
@@ -889,5 +889,5 @@
         if (tile == null) {
             if (coordinateConverter.requiresReprojection()) {
-                tile = new ReprojectionTile(tileSource, x, y, zoom);
+                tile = new ReprojectionTile(createTile(tileSource, x, y, zoom));
             } else {
                 tile = createTile(tileSource, x, y, zoom);
Index: /trunk/src/org/openstreetmap/josm/gui/layer/imagery/ReprojectionTile.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/layer/imagery/ReprojectionTile.java	(revision 18186)
+++ /trunk/src/org/openstreetmap/josm/gui/layer/imagery/ReprojectionTile.java	(revision 18187)
@@ -5,4 +5,6 @@
 import java.awt.geom.Point2D;
 import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.io.InputStream;
 
 import org.openstreetmap.gui.jmapviewer.Tile;
@@ -11,4 +13,5 @@
 import org.openstreetmap.josm.data.coor.EastNorth;
 import org.openstreetmap.josm.data.imagery.CoordinateConversion;
+import org.openstreetmap.josm.data.imagery.vectortile.VectorTile;
 import org.openstreetmap.josm.data.projection.Projection;
 import org.openstreetmap.josm.data.projection.ProjectionRegistry;
@@ -26,4 +29,5 @@
 public class ReprojectionTile extends Tile {
 
+    private final Tile tile;
     protected TileAnchor anchor;
     private double nativeScale;
@@ -32,5 +36,5 @@
     /**
      * Constructs a new {@code ReprojectionTile}.
-     * @param source sourec tile
+     * @param source source tile
      * @param xtile X coordinate
      * @param ytile Y coordinate
@@ -39,4 +43,14 @@
     public ReprojectionTile(TileSource source, int xtile, int ytile, int zoom) {
         super(source, xtile, ytile, zoom);
+        this.tile = null;
+    }
+
+    /**
+     * Create a reprojection tile for a specific tile
+     * @param tile The tile to use
+     */
+    public ReprojectionTile(Tile tile) {
+        super(tile.getTileSource(), tile.getXtile(), tile.getYtile(), tile.getZoom());
+        this.tile = tile;
     }
 
@@ -73,4 +87,13 @@
             return false;
         return !maxZoomReached || currentScale >= nativeScale;
+    }
+
+    @Override
+    public void loadImage(InputStream inputStream) throws IOException {
+        if (this.tile instanceof VectorTile) {
+            this.tile.loadImage(inputStream);
+        } else {
+            super.loadImage(inputStream);
+        }
     }
 
Index: /trunk/test/unit/org/openstreetmap/josm/gui/layer/imagery/MVTLayerTest.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/gui/layer/imagery/MVTLayerTest.java	(revision 18187)
+++ /trunk/test/unit/org/openstreetmap/josm/gui/layer/imagery/MVTLayerTest.java	(revision 18187)
@@ -0,0 +1,152 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.layer.imagery;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Collections;
+
+import org.apache.commons.jcs3.access.behavior.ICacheAccess;
+import org.awaitility.Awaitility;
+import org.awaitility.Durations;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.openstreetmap.gui.jmapviewer.interfaces.TileLoaderListener;
+import org.openstreetmap.josm.TestUtils;
+import org.openstreetmap.josm.actions.ExpertToggleAction;
+import org.openstreetmap.josm.data.Bounds;
+import org.openstreetmap.josm.data.imagery.ImageryInfo;
+import org.openstreetmap.josm.data.imagery.TileJobOptions;
+import org.openstreetmap.josm.data.imagery.vectortile.mapbox.MVTFile;
+import org.openstreetmap.josm.data.imagery.vectortile.mapbox.MVTTile;
+import org.openstreetmap.josm.data.imagery.vectortile.mapbox.MapboxVectorCachedTileLoader;
+import org.openstreetmap.josm.data.osm.BBox;
+import org.openstreetmap.josm.data.projection.Projection;
+import org.openstreetmap.josm.data.projection.ProjectionRegistry;
+import org.openstreetmap.josm.data.projection.Projections;
+import org.openstreetmap.josm.gui.MainApplication;
+import org.openstreetmap.josm.testutils.FakeGraphics;
+import org.openstreetmap.josm.testutils.JOSMTestRules;
+import org.openstreetmap.josm.testutils.annotations.BasicPreferences;
+
+/**
+ * Test class for {@link MVTLayer}
+ */
+@BasicPreferences
+class MVTLayerTest {
+    // Needed for setting HTTP factory and the main window/mapview
+    @RegisterExtension
+    JOSMTestRules josmTestRules = new JOSMTestRules().main().projection();
+
+    MVTLayer testLayer;
+
+    @BeforeEach
+    void setUp() {
+        final ImageryInfo imageryInfo = new ImageryInfo("MvtLayerTest", "file:" + TestUtils.getTestDataRoot() + "pbf/mapillary/{z}/{x}/{y}.mvt");
+        imageryInfo.setImageryType(ImageryInfo.ImageryType.MVT);
+        this.testLayer = new MVTLayer(imageryInfo);
+    }
+
+    @Test
+    void getTileLoaderClass() {
+        assertEquals(MapboxVectorCachedTileLoader.class, this.testLayer.getTileLoaderClass());
+    }
+
+    @Test
+    void getCacheName() {
+        assertEquals("MVT", this.testLayer.getCacheName());
+    }
+
+    @Test
+    void getCache() {
+        assertNotNull(MVTLayer.getCache());
+    }
+
+    @Test
+    void getNativeProjections() {
+        assertArrayEquals(Collections.singleton(MVTFile.DEFAULT_PROJECTION).toArray(), this.testLayer.getNativeProjections().toArray());
+    }
+
+    /**
+     * This is a non-regression test for JOSM #21260
+     * @param projectionCode The projection code to use
+     * @throws ReflectiveOperationException If the required method was unable to be called
+     */
+    @ParameterizedTest
+    @ValueSource(strings = {"EPSG:3857" /* WGS 84 */, "EPSG:4326" /* Mercator (default) */, "EPSG:32612" /* UTM 12 N */})
+    void ensureDifferentProjectionsAreFetched(final String projectionCode) throws ReflectiveOperationException {
+        final Projection originalProjection = ProjectionRegistry.getProjection();
+        try {
+            ProjectionRegistry.setProjection(Projections.getProjectionByCode(projectionCode));
+            // Needed to initialize mapView
+            MainApplication.getLayerManager().addLayer(this.testLayer);
+            final BBox tileBBox = new MVTTile(this.testLayer.getTileSource(), 3251, 6258, 14).getBBox();
+            MainApplication.getMap().mapView.zoomTo(new Bounds(tileBBox.getMinLat(), tileBBox.getMinLon(),
+                    tileBBox.getMaxLat(), tileBBox.getMaxLon()));
+            final FakeGraphics graphics2D = new FakeGraphics();
+            graphics2D.setClip(0, 0, 100, 100);
+            this.testLayer.setZoomLevel(14);
+            this.testLayer.getDisplaySettings().setAutoZoom(false);
+            MainApplication.getMap().mapView.paintLayer(this.testLayer, graphics2D);
+            Awaitility.await().atMost(Durations.ONE_SECOND).until(() -> !this.testLayer.getData().allPrimitives().isEmpty());
+            assertFalse(this.testLayer.getData().allPrimitives().isEmpty());
+        } finally {
+            ProjectionRegistry.setProjection(originalProjection);
+        }
+    }
+
+    @Test
+    void getTileSource() {
+        assertEquals(this.testLayer.getInfo().getUrl(), this.testLayer.getTileSource().getBaseUrl());
+    }
+
+    @Test
+    void createTile() {
+        assertNotNull(this.testLayer.createTile(this.testLayer.getTileSource(), 3251, 6258, 14));
+    }
+
+    @ParameterizedTest
+    @ValueSource(booleans = {true, false})
+    void getMenuEntries(final boolean isExpert) {
+        ExpertToggleAction.getInstance().setExpert(isExpert);
+        // For now, just ensure that nothing throws on implementation
+        MainApplication.getLayerManager().addLayer(this.testLayer);
+        assertNotNull(assertDoesNotThrow(() -> this.testLayer.getMenuEntries()));
+    }
+
+    @Test
+    void getData() {
+        assertNotNull(this.testLayer.getData());
+    }
+
+    @Test
+    void finishedLoading() throws ReflectiveOperationException {
+        final MVTTile mvtTile = (MVTTile) this.testLayer.createTile(this.testLayer.getTileSource(), 3251, 6258, 14);
+        final FinishedLoading finishedLoading = new FinishedLoading();
+        mvtTile.addTileLoaderFinisher(finishedLoading);
+        assertTrue(this.testLayer.getData().allPrimitives().isEmpty());
+        this.testLayer.getTileLoaderClass().getConstructor(TileLoaderListener.class, ICacheAccess.class, TileJobOptions.class)
+                .newInstance(this.testLayer, MVTLayer.getCache(), new TileJobOptions(50, 50, Collections.emptyMap(), 1))
+                .createTileLoaderJob(mvtTile).submit();
+        Awaitility.await().atMost(Durations.ONE_SECOND).until(() -> finishedLoading.finished);
+        assertFalse(this.testLayer.getData().allPrimitives().isEmpty());
+    }
+
+    /**
+     * For some reason, lambdas get garbage collected by WeakReference's. This avoids that.
+     */
+    private static final class FinishedLoading implements MVTTile.TileListener {
+        boolean finished;
+        @Override
+        public void finishedLoading(MVTTile tile) {
+            this.finished = true;
+        }
+    }
+}
Index: /trunk/test/unit/org/openstreetmap/josm/testutils/FakeGraphics.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/testutils/FakeGraphics.java	(revision 18186)
+++ /trunk/test/unit/org/openstreetmap/josm/testutils/FakeGraphics.java	(revision 18187)
@@ -32,4 +32,7 @@
  */
 public final class FakeGraphics extends Graphics2D {
+    // This is needed just in case someone wants to fake paint something
+    private Rectangle bounds;
+
     @Override
     public void setXORMode(Color c1) {
@@ -50,4 +53,5 @@
     @Override
     public void setClip(int x, int y, int width, int height) {
+        this.bounds = new Rectangle(x, y, width, height);
     }
 
@@ -73,5 +77,5 @@
     @Override
     public Rectangle getClipBounds() {
-        return null;
+        return this.bounds;
     }
 
