diff --git a/src/hotspot/share/prims/methodHandles.cpp b/src/hotspot/share/prims/methodHandles.cpp
index fd10f0723bff0..8465eec308bf3 100644
--- a/src/hotspot/share/prims/methodHandles.cpp
+++ b/src/hotspot/share/prims/methodHandles.cpp
@@ -1363,6 +1363,18 @@ JVM_ENTRY(jobject, MH_invokeExact_UOE(JNIEnv* env, jobject mh, jobjectArray args
 }
 JVM_END
 
+/**
+ * Throws a java/lang/UnsupportedOperationException unconditionally.
+ * This is required by the specification of VarHandle.{access-mode} if
+ * invoked directly.
+ */
+JVM_ENTRY(jobject, VH_UOE(JNIEnv* env, jobject vh, jobjectArray args)) {
+  THROW_MSG_NULL(vmSymbols::java_lang_UnsupportedOperationException(), "VarHandle access mode methods cannot be invoked reflectively");
+  return nullptr;
+}
+JVM_END
+
+
 /// JVM_RegisterMethodHandleMethods
 
 #define LANG "Ljava/lang/"
@@ -1402,6 +1414,40 @@ static JNINativeMethod MH_methods[] = {
   {CC "invoke",                    CC "([" OBJ ")" OBJ,                       FN_PTR(MH_invoke_UOE)},
   {CC "invokeExact",               CC "([" OBJ ")" OBJ,                       FN_PTR(MH_invokeExact_UOE)}
 };
+static JNINativeMethod VH_methods[] = {
+  // UnsupportedOperationException throwers
+  {CC "get",                        CC "([" OBJ ")" OBJ,    FN_PTR(VH_UOE)},
+  {CC "set",                        CC "([" OBJ ")V",       FN_PTR(VH_UOE)},
+  {CC "getVolatile",                CC "([" OBJ ")" OBJ,    FN_PTR(VH_UOE)},
+  {CC "setVolatile",                CC "([" OBJ ")V",       FN_PTR(VH_UOE)},
+  {CC "getAcquire",                 CC "([" OBJ ")" OBJ,    FN_PTR(VH_UOE)},
+  {CC "setRelease",                 CC "([" OBJ ")V",       FN_PTR(VH_UOE)},
+  {CC "getOpaque",                  CC "([" OBJ ")" OBJ,    FN_PTR(VH_UOE)},
+  {CC "setOpaque",                  CC "([" OBJ ")V",       FN_PTR(VH_UOE)},
+  {CC "compareAndSet",              CC "([" OBJ ")Z",       FN_PTR(VH_UOE)},
+  {CC "compareAndExchange",         CC "([" OBJ ")" OBJ,    FN_PTR(VH_UOE)},
+  {CC "compareAndExchangeAcquire",  CC "([" OBJ ")" OBJ,    FN_PTR(VH_UOE)},
+  {CC "compareAndExchangeRelease",  CC "([" OBJ ")" OBJ,    FN_PTR(VH_UOE)},
+  {CC "weakCompareAndSetPlain",     CC "([" OBJ ")Z",       FN_PTR(VH_UOE)},
+  {CC "weakCompareAndSet",          CC "([" OBJ ")Z",       FN_PTR(VH_UOE)},
+  {CC "weakCompareAndSetAcquire",   CC "([" OBJ ")Z",       FN_PTR(VH_UOE)},
+  {CC "weakCompareAndSetRelease",   CC "([" OBJ ")Z",       FN_PTR(VH_UOE)},
+  {CC "getAndSet",                  CC "([" OBJ ")" OBJ,    FN_PTR(VH_UOE)},
+  {CC "getAndSetAcquire",           CC "([" OBJ ")" OBJ,    FN_PTR(VH_UOE)},
+  {CC "getAndSetRelease",           CC "([" OBJ ")" OBJ,    FN_PTR(VH_UOE)},
+  {CC "getAndAdd",                  CC "([" OBJ ")" OBJ,    FN_PTR(VH_UOE)},
+  {CC "getAndAddAcquire",           CC "([" OBJ ")" OBJ,    FN_PTR(VH_UOE)},
+  {CC "getAndAddRelease",           CC "([" OBJ ")" OBJ,    FN_PTR(VH_UOE)},
+  {CC "getAndBitwiseOr",            CC "([" OBJ ")" OBJ,    FN_PTR(VH_UOE)},
+  {CC "getAndBitwiseOrAcquire",     CC "([" OBJ ")" OBJ,    FN_PTR(VH_UOE)},
+  {CC "getAndBitwiseOrRelease",     CC "([" OBJ ")" OBJ,    FN_PTR(VH_UOE)},
+  {CC "getAndBitwiseAnd",           CC "([" OBJ ")" OBJ,    FN_PTR(VH_UOE)},
+  {CC "getAndBitwiseAndAcquire",    CC "([" OBJ ")" OBJ,    FN_PTR(VH_UOE)},
+  {CC "getAndBitwiseAndRelease",    CC "([" OBJ ")" OBJ,    FN_PTR(VH_UOE)},
+  {CC "getAndBitwiseXor",           CC "([" OBJ ")" OBJ,    FN_PTR(VH_UOE)},
+  {CC "getAndBitwiseXorAcquire",    CC "([" OBJ ")" OBJ,    FN_PTR(VH_UOE)},
+  {CC "getAndBitwiseXorRelease",    CC "([" OBJ ")" OBJ,    FN_PTR(VH_UOE)}
+};
 
 /**
  * This one function is exported, used by NativeLookup.
@@ -1409,9 +1455,12 @@ static JNINativeMethod MH_methods[] = {
 JVM_ENTRY(void, JVM_RegisterMethodHandleMethods(JNIEnv *env, jclass MHN_class)) {
   assert(!MethodHandles::enabled(), "must not be enabled");
   assert(vmClasses::MethodHandle_klass() != nullptr, "should be present");
+  assert(vmClasses::VarHandle_klass() != nullptr, "should be present");
 
-  oop mirror = vmClasses::MethodHandle_klass()->java_mirror();
-  jclass MH_class = (jclass) JNIHandles::make_local(THREAD, mirror);
+  oop mh_mirror = vmClasses::MethodHandle_klass()->java_mirror();
+  oop vh_mirror = vmClasses::VarHandle_klass()->java_mirror();
+  jclass MH_class = (jclass) JNIHandles::make_local(THREAD, mh_mirror);
+  jclass VH_class = (jclass) JNIHandles::make_local(THREAD, vh_mirror);
 
   {
     ThreadToNativeFromVM ttnfv(thread);
@@ -1423,6 +1472,10 @@ JVM_ENTRY(void, JVM_RegisterMethodHandleMethods(JNIEnv *env, jclass MHN_class))
     status = env->RegisterNatives(MH_class, MH_methods, sizeof(MH_methods)/sizeof(JNINativeMethod));
     guarantee(status == JNI_OK && !env->ExceptionOccurred(),
               "register java.lang.invoke.MethodHandle natives");
+
+    status = env->RegisterNatives(VH_class, VH_methods, sizeof(VH_methods)/sizeof(JNINativeMethod));
+    guarantee(status == JNI_OK && !env->ExceptionOccurred(),
+              "register java.lang.invoke.VarHandle natives");
   }
 
   log_debug(methodhandles, indy)("MethodHandle support loaded (using LambdaForms)");
diff --git a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestReflection.java b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestReflection.java
index 652272ca8a8f4..b20e18d263edd 100644
--- a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestReflection.java
+++ b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestReflection.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -33,6 +33,7 @@
 import java.lang.invoke.MethodHandleInfo;
 import java.lang.invoke.MethodHandles;
 import java.lang.invoke.VarHandle;
+import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.util.stream.Stream;
 
@@ -52,15 +53,33 @@ static VarHandle handle() throws Exception {
     }
 
     @Test(dataProvider = "accessModesProvider", expectedExceptions = IllegalArgumentException.class)
-    public void methodInvocation(VarHandle.AccessMode accessMode) throws Exception {
+    public void methodInvocationArgumentMismatch(VarHandle.AccessMode accessMode) throws Exception {
         VarHandle v = handle();
 
-        // Try a reflective invoke using a Method
+        // Try a reflective invoke using a Method, with no arguments
 
         Method vhm = VarHandle.class.getMethod(accessMode.methodName(), Object[].class);
         vhm.invoke(v, new Object[]{});
     }
 
+    @Test(dataProvider = "accessModesProvider")
+    public void methodInvocationMatchingArguments(VarHandle.AccessMode accessMode) throws Exception {
+        VarHandle v = handle();
+
+        // Try a reflective invoke using a Method, with the minimal required arguments
+
+        Method vhm = VarHandle.class.getMethod(accessMode.methodName(), Object[].class);
+        Object arg = new Object[0];
+        try {
+            vhm.invoke(v, arg);
+        } catch (InvocationTargetException e) {
+            if (!(e.getCause() instanceof UnsupportedOperationException)) {
+                throw new RuntimeException("expected UnsupportedOperationException but got: "
+                                           + e.getCause().getClass().getName(), e);
+            }
+        }
+    }
+
     @Test(dataProvider = "accessModesProvider", expectedExceptions = UnsupportedOperationException.class)
     public void methodHandleInvoke(VarHandle.AccessMode accessMode) throws Throwable {
         VarHandle v = handle();