diff --git a/jme3-core/src/main/java/com/jme3/asset/AssetEventListener.java b/jme3-core/src/main/java/com/jme3/asset/AssetEventListener.java
index 5e391ba5ff..84559f242c 100644
--- a/jme3-core/src/main/java/com/jme3/asset/AssetEventListener.java
+++ b/jme3-core/src/main/java/com/jme3/asset/AssetEventListener.java
@@ -35,8 +35,9 @@
* AssetEventListener is an interface for listening to various
* events happening inside {@link AssetManager}. For now, it is possible
* to receive an event when an asset has been requested
- * (one of the AssetManager.load***() methods were called), or when
- * an asset has been loaded.
+ * (one of the AssetManager.load***() methods were called), when
+ * an asset has been loaded, or when an asset has been reloaded via
+ * {@link AssetManager#reloadAsset(com.jme3.asset.AssetKey)}.
*
* @author Kirill Vainer
*/
@@ -73,4 +74,16 @@ public interface AssetEventListener {
*/
public void assetDependencyNotFound(AssetKey parentKey, AssetKey dependentAssetKey);
+ /**
+ * Called after {@link AssetManager#reloadAsset(com.jme3.asset.AssetKey) }
+ * has successfully reloaded an asset from its locators.
+ *
+ * Default implementation is a no-op so existing listeners do not need to
+ * be updated.
+ *
+ * @param key the key of the reloaded asset
+ */
+ public default void assetReloaded(AssetKey> key) {
+ }
+
}
diff --git a/jme3-core/src/main/java/com/jme3/asset/AssetManager.java b/jme3-core/src/main/java/com/jme3/asset/AssetManager.java
index 73762a1234..1747030cd7 100644
--- a/jme3-core/src/main/java/com/jme3/asset/AssetManager.java
+++ b/jme3-core/src/main/java/com/jme3/asset/AssetManager.java
@@ -427,4 +427,24 @@ public default List getClassLoaders() {
* Clears the asset cache.
*/
public void clearCache();
+
+ /**
+ * Removes the asset for the given key from the cache (if present) and loads
+ * it again from the configured locators. Registered
+ * {@link AssetEventListener}s receive {@link AssetEventListener#assetReloaded(com.jme3.asset.AssetKey) }
+ * after a successful reload.
+ *
+ * This method is intended for development workflows such as refreshing
+ * content after a file change on disk. It fires {@link AssetEventListener#assetRequested(com.jme3.asset.AssetKey) }
+ * and {@link AssetEventListener#assetLoaded(com.jme3.asset.AssetKey) } through
+ * the normal {@link #loadAsset(com.jme3.asset.AssetKey) } path in addition to
+ * {@code assetReloaded}.
+ *
+ * @param The asset type
+ * @param key The asset key to reload (not null)
+ * @return The reloaded asset
+ * @throws IllegalArgumentException If the key is null or specifies no cache
+ * @throws AssetNotFoundException If the asset cannot be located
+ */
+ public T reloadAsset(AssetKey key);
}
diff --git a/jme3-core/src/main/java/com/jme3/asset/DesktopAssetManager.java b/jme3-core/src/main/java/com/jme3/asset/DesktopAssetManager.java
index a8ff73f2c4..74a6037cd3 100644
--- a/jme3-core/src/main/java/com/jme3/asset/DesktopAssetManager.java
+++ b/jme3-core/src/main/java/com/jme3/asset/DesktopAssetManager.java
@@ -253,6 +253,19 @@ public void clearCache() {
}
}
+ @Override
+ public T reloadAsset(AssetKey key) {
+ if (key == null) {
+ throw new IllegalArgumentException("key cannot be null");
+ }
+ deleteFromCache(key);
+ T asset = loadAsset(key);
+ for (AssetEventListener listener : eventListeners) {
+ listener.assetReloaded(key);
+ }
+ return asset;
+ }
+
/**
* Loads an asset that has already been located.
* @param The asset type
diff --git a/jme3-core/src/test/java/com/jme3/asset/DesktopAssetManagerReloadTest.java b/jme3-core/src/test/java/com/jme3/asset/DesktopAssetManagerReloadTest.java
new file mode 100644
index 0000000000..2baa7b0d56
--- /dev/null
+++ b/jme3-core/src/test/java/com/jme3/asset/DesktopAssetManagerReloadTest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2009-2026 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.asset;
+
+import com.jme3.asset.plugins.ClasspathLocator;
+import com.jme3.texture.Texture;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests {@link DesktopAssetManager#reloadAsset(com.jme3.asset.AssetKey)}.
+ */
+public class DesktopAssetManagerReloadTest {
+
+ private static final String TEXTURE_PATH = "Textures/Terrain/Pond/Pond.jpg";
+
+ private DesktopAssetManager assetManager;
+ private TextureKey textureKey;
+
+ private static final class TrackingListener implements AssetEventListener {
+
+ private final List> requested = new ArrayList<>();
+ private final List> loaded = new ArrayList<>();
+ private final List> reloaded = new ArrayList<>();
+
+ @Override
+ public void assetLoaded(AssetKey key) {
+ loaded.add(key);
+ }
+
+ @Override
+ public void assetRequested(AssetKey key) {
+ requested.add(key);
+ }
+
+ @Override
+ public void assetDependencyNotFound(AssetKey parentKey, AssetKey dependentAssetKey) {
+ }
+
+ @Override
+ public void assetReloaded(AssetKey> key) {
+ reloaded.add(key);
+ }
+
+ public int requestCount() {
+ return requested.size();
+ }
+
+ public int loadedCount() {
+ return loaded.size();
+ }
+
+ public int reloadedCount() {
+ return reloaded.size();
+ }
+ }
+
+ @Before
+ public void setUp() {
+ assetManager = new DesktopAssetManager(true);
+ assetManager.registerLocator("/", ClasspathLocator.class);
+ textureKey = new TextureKey(TEXTURE_PATH, false);
+ }
+
+ /**
+ * reloadAsset should invalidate the cache entry, parse again, and notify listeners.
+ */
+ @Test
+ public void reloadAssetReparsesAndNotifiesListeners() {
+ TrackingListener listener = new TrackingListener();
+ assetManager.addAssetEventListener(listener);
+
+ Texture initial = assetManager.loadTexture(textureKey);
+ Assert.assertNotNull(initial);
+ Assert.assertEquals(1, listener.loadedCount());
+
+ Texture reloaded = assetManager.reloadAsset(textureKey);
+
+ Assert.assertNotNull(reloaded);
+ Assert.assertEquals(2, listener.requestCount());
+ Assert.assertEquals(2, listener.loadedCount());
+ Assert.assertEquals(1, listener.reloadedCount());
+ Assert.assertNotSame(initial, reloaded);
+ }
+
+ /**
+ * reloadAsset must reject a null key.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void reloadAssetRejectsNullKey() {
+ assetManager.reloadAsset(null);
+ }
+}