diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/JVMUpcalls.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/JVMUpcalls.java
index 2eed7b918fd21..6d1d5cf793823 100644
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/JVMUpcalls.java
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/JVMUpcalls.java
@@ -28,6 +28,7 @@
 
 import jdk.jfr.internal.event.EventConfiguration;
 import jdk.jfr.internal.util.Bytecode;
+import jdk.jfr.internal.util.Utils;
 /**
  * All upcalls from the JVM should go through this class.
  *
@@ -59,7 +60,7 @@ final class JVMUpcalls {
     static byte[] onRetransform(long traceId, boolean dummy1, boolean dummy2, Class<?> clazz, byte[] oldBytes) throws Throwable {
         try {
             if (jdk.internal.event.Event.class.isAssignableFrom(clazz) && !Modifier.isAbstract(clazz.getModifiers())) {
-                if (!JVMSupport.shouldInstrument(clazz.getClassLoader() == null, clazz.getName())) {
+                if (!JVMSupport.shouldInstrument(Utils.isJDKClass(clazz), clazz.getName())) {
                     Logger.log(LogTag.JFR_SYSTEM, LogLevel.INFO, "Skipping instrumentation for " + clazz.getName() + " since container support is missing");
                     return oldBytes;
                 }
@@ -70,9 +71,9 @@ static byte[] onRetransform(long traceId, boolean dummy1, boolean dummy2, Class<
                     // Probably triggered by some other agent
                     return oldBytes;
                 }
-                boolean bootClassLoader = clazz.getClassLoader() == null;
+                boolean jdkClass = Utils.isJDKClass(clazz);
                 Logger.log(LogTag.JFR_SYSTEM, LogLevel.INFO, "Adding instrumentation to event class " + clazz.getName() + " using retransform");
-                EventInstrumentation ei = new EventInstrumentation(clazz.getSuperclass(), oldBytes, traceId, bootClassLoader, false);
+                EventInstrumentation ei = new EventInstrumentation(clazz.getSuperclass(), oldBytes, traceId, jdkClass, false);
                 byte[] bytes = ei.buildInstrumented();
                 Bytecode.log(clazz.getName(), bytes);
                 return bytes;
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataRepository.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataRepository.java
index 9a2bbfe4c6314..7031718cb284e 100644
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataRepository.java
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataRepository.java
@@ -204,7 +204,7 @@ private EventConfiguration makeConfiguration(Class<? extends jdk.internal.event.
         // and assign the result to a long field is not enough to always get a proper
         // stack trace. Purpose of the mechanism is to transfer metadata, such as
         // native type IDs, without specialized Java logic for each type.
-        if (eventClass.getClassLoader() == null) {
+        if (Utils.isJDKClass(eventClass)) {
             Name name = eventClass.getAnnotation(Name.class);
             if (name != null) {
                 String n = name.value();
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/MirrorEvents.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/MirrorEvents.java
index 35eadff40d129..e8a26fe425d14 100644
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/MirrorEvents.java
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/MirrorEvents.java
@@ -48,6 +48,7 @@
 import jdk.jfr.events.VirtualThreadSubmitFailedEvent;
 import jdk.jfr.events.X509CertificateEvent;
 import jdk.jfr.events.X509ValidationEvent;
+import jdk.jfr.internal.util.Utils;
 
 /**
  * This class registers all mirror events.
@@ -85,7 +86,7 @@ private static void register(String eventClassName, Class<? extends MirrorEvent>
     }
 
     static Class<? extends MirrorEvent> find(Class<? extends jdk.internal.event.Event> eventClass) {
-        return find(eventClass.getClassLoader() == null, eventClass.getName());
+        return find(Utils.isJDKClass(eventClass), eventClass.getName());
     }
 
     static Class<? extends MirrorEvent> find(boolean bootClassLoader, String name) {
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/TypeLibrary.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/TypeLibrary.java
index cab6e7befe1aa..350bf55ccd4ee 100644
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/TypeLibrary.java
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/TypeLibrary.java
@@ -166,7 +166,9 @@ public static synchronized AnnotationElement createAnnotation(Annotation annotat
             for (ValueDescriptor v : type.getFields()) {
                 values.add(invokeAnnotation(annotation, v.getName()));
             }
-            return PrivateAccess.getInstance().newAnnotation(type, values, annotation.annotationType().getClassLoader() == null);
+            // Only annotation classes in the boot class loader can always be resolved.
+            boolean bootClassLoader = annotationType.getClassLoader() == null;
+            return PrivateAccess.getInstance().newAnnotation(type, values, bootClassLoader);
         }
         return null;
     }
@@ -220,7 +222,7 @@ private static Type defineType(Class<?> clazz, String superType, boolean eventTy
             long id = Type.getTypeId(clazz);
             Type t;
             if (eventType) {
-                t = new PlatformEventType(typeName, id, clazz.getClassLoader() == null, true);
+                t = new PlatformEventType(typeName, id, Utils.isJDKClass(clazz), true);
             } else {
                 t = new Type(typeName, superType, id);
             }
@@ -283,7 +285,7 @@ public static synchronized Type createType(Class<?> clazz, List<AnnotationElemen
         }
         addAnnotations(clazz, type, dynamicAnnotations);
 
-        if (clazz.getClassLoader() == null) {
+        if (Utils.isJDKClass(clazz)) {
             type.log("Added", LogTag.JFR_SYSTEM_METADATA, LogLevel.INFO);
         } else {
             type.log("Added", LogTag.JFR_METADATA, LogLevel.INFO);
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/periodic/JDKEventTask.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/periodic/JDKEventTask.java
index 73d4f5479eb04..a891b1c3775c1 100644
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/periodic/JDKEventTask.java
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/periodic/JDKEventTask.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, 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
@@ -25,6 +25,7 @@
 package jdk.jfr.internal.periodic;
 
 import jdk.internal.event.Event;
+import jdk.jfr.internal.util.Utils;
 
 /**
  * Periodic task that runs trusted code that doesn't require an access control
@@ -39,11 +40,11 @@ public JDKEventTask(Class<? extends Event> eventClass, Runnable runnable) {
         if (!getEventType().isJDK()) {
             throw new InternalError("Must be a JDK event");
         }
-        if (eventClass.getClassLoader() != null) {
-            throw new SecurityException("Periodic task can only be registered for event classes that are loaded by the bootstrap class loader");
+        if (!Utils.isJDKClass(eventClass)) {
+            throw new SecurityException("Periodic task can only be registered for event classes that belongs to the JDK");
         }
-        if (runnable.getClass().getClassLoader() != null) {
-            throw new SecurityException("Runnable class must be loaded by the bootstrap class loader");
+        if (!Utils.isJDKClass(runnable.getClass())) {
+            throw new SecurityException("Runnable class must belong to the JDK");
         }
     }
 
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Utils.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Utils.java
index 53b3599f9d953..0049662e9a1f6 100644
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Utils.java
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Utils.java
@@ -435,4 +435,12 @@ public static String format(String template, Map<String, String> parameters) {
         }
         return sb.toString();
     }
+
+    public static boolean isJDKClass(Class<?> type) {
+        return type.getClassLoader() == null;
+        // In the future we might want to also do:
+        // type.getClassLoader() == ClassLoader.getPlatformClassLoader();
+        // but only if it is safe and there is a mechanism to register event
+        // classes in other modules besides jdk.jfr and java.base.
+    }
 }