diff --git a/error.c b/error.c
index 575c3ebfe0..bb62792f6d 100644
--- a/error.c
+++ b/error.c
@@ -376,6 +376,20 @@ rb_compile_warning(const char *file, int line, const char *fmt, ...)
     rb_write_warning_str(str);
 }
 
+void
+rb_category_compile_warn(const char* category, const char *file, int line, const char *fmt, ...)
+{
+    VALUE str;
+    va_list args;
+
+    if (NIL_P(ruby_verbose)) return;
+
+    va_start(args, fmt);
+    str = warn_vsprintf(NULL, file, line, fmt, args);
+    va_end(args);
+    rb_warn_category(str, ID2SYM(rb_intern(category)));
+}
+
 static VALUE
 warning_string(rb_encoding *enc, const char *fmt, va_list args)
 {
@@ -2832,6 +2846,7 @@ Init_Exception(void)
     warning_categories = rb_hash_new();
     rb_gc_register_mark_object(warning_categories);
     rb_hash_aset(warning_categories, ID2SYM(rb_intern_const("deprecated")), Qtrue);
+    rb_hash_aset(warning_categories, ID2SYM(rb_intern_const("experimental")), Qtrue);
     rb_obj_freeze(warning_categories);
 }
 
diff --git a/include/ruby/internal/error.h b/include/ruby/internal/error.h
index dc842cc6f6..7d1bcaa343 100644
--- a/include/ruby/internal/error.h
+++ b/include/ruby/internal/error.h
@@ -65,6 +65,7 @@ VALUE *rb_ruby_debug_ptr(void);
 PRINTF_ARGS(void rb_warning(const char*, ...), 1, 2);
 PRINTF_ARGS(void rb_category_warning(const char*, const char*, ...), 2, 3);
 PRINTF_ARGS(void rb_compile_warning(const char *, int, const char*, ...), 3, 4);
+PRINTF_ARGS(void rb_category_compile_warn(const char *, const char *, int, const char*, ...), 4, 5);
 PRINTF_ARGS(void rb_sys_warning(const char*, ...), 1, 2);
 /* reports always */
 COLDFUNC PRINTF_ARGS(void rb_warn(const char*, ...), 1, 2);
diff --git a/parse.y b/parse.y
index 664df9f542..3282dd0ea9 100644
--- a/parse.y
+++ b/parse.y
@@ -1019,6 +1019,19 @@ endless_method_name(struct parser_params *p, NODE *defn, const YYLTYPE *loc)
 # define rb_warning4L(l,fmt,a,b,c,d) WARNING_CALL(WARNING_ARGS_L(l, fmt, 5), (a), (b), (c), (d))
 #ifdef RIPPER
 static ID id_warn, id_warning, id_gets, id_assoc;
+static VALUE experimental_category_kw;
+static VALUE
+warn0L_experimental(VALUE p_value, VALUE mesg)
+{
+  VALUE args[2];
+  args[0] = mesg;
+  args[1] = experimental_category_kw;
+rb_p(p_value);
+rb_p(mesg);
+rb_p(experimental_category_kw);
+  return rb_funcallv_kw(p_value, id_warn, 2, args, RB_PASS_KEYWORDS);
+}
+# define rb_warn0L_experimental(l, fmt) warn0L_experimental(p->value, rb_usascii_str_new_lit(fmt))
 # define WARN_S_L(s,l) STR_NEW(s,l)
 # define WARN_S(s) STR_NEW2(s)
 # define WARN_I(i) INT2NUM(i)
@@ -1051,6 +1064,7 @@ PRINTF_ARGS(static void ripper_compile_error(struct parser_params*, const char *
 # define WARN_ARGS(fmt,n) WARN_ARGS_L(p->ruby_sourceline,fmt,n)
 # define WARN_ARGS_L(l,fmt,n) p->ruby_sourcefile, (l), (fmt)
 # define WARN_CALL rb_compile_warn
+# define rb_warn0L_experimental(l,fmt) rb_category_compile_warn("experimental", WARN_ARGS_L(l, fmt, 1))
 # define WARNING_ARGS(fmt,n) WARN_ARGS(fmt,n)
 # define WARNING_ARGS_L(l,fmt,n) WARN_ARGS_L(l,fmt,n)
 # define WARNING_CALL rb_compile_warning
@@ -4226,7 +4240,7 @@ p_find		: p_rest ',' p_args_post ',' p_rest
 			$$ = new_find_pattern_tail(p, $1, $3, $5, &@$);
 
 			if (rb_warning_category_enabled_p(RB_WARN_CATEGORY_EXPERIMENTAL))
-			    rb_warn0L(nd_line($$), "Find pattern is experimental, and the behavior may change in future versions of Ruby!");
+			    rb_warn0L_experimental(nd_line($$), "Find pattern is experimental, and the behavior may change in future versions of Ruby!");
 		    }
 		;
 
@@ -11936,7 +11950,7 @@ warn_one_line_pattern_matching(struct parser_params *p, NODE *node, NODE *patter
 
     if (rb_warning_category_enabled_p(RB_WARN_CATEGORY_EXPERIMENTAL) &&
 	!(right_assign && (type == NODE_LASGN || type == NODE_DASGN || type == NODE_DASGN_CURR)))
-	rb_warn0L(nd_line(node), "One-line pattern matching is experimental, and the behavior may change in future versions of Ruby!");
+	rb_warn0L_experimental(nd_line(node), "One-line pattern matching is experimental, and the behavior may change in future versions of Ruby!");
 }
 
 static NODE*
@@ -13670,6 +13684,13 @@ Init_ripper(void)
     id_gets = rb_intern_const("gets");
     id_assoc = rb_intern_const("=>");
 
+     experimental_category_kw = rb_hash_new();
+     rb_gc_register_mark_object(experimental_category_kw);
+     rb_hash_aset(experimental_category_kw,
+                  ID2SYM(rb_intern_const("category")),
+                  ID2SYM(rb_intern_const("experimental")));
+     rb_obj_freeze(experimental_category_kw);
+
     (void)yystpcpy; /* may not used in newer bison */
 
     InitVM(ripper);
diff --git a/ractor.c b/ractor.c
index f7be7daea9..02579ae578 100644
--- a/ractor.c
+++ b/ractor.c
@@ -1140,8 +1140,9 @@ cancel_single_ractor_mode(void)
     rb_transient_heap_evacuate();
 
     if (rb_warning_category_enabled_p(RB_WARN_CATEGORY_EXPERIMENTAL)) {
-        rb_warn("Ractor is experimental, and the behavior may change in future versions of Ruby! "
-                "Also there are many implementation issues.");
+        rb_category_warn("experimental",
+                         "Ractor is experimental, and the behavior may change in future versions of Ruby! "
+                         "Also there are many implementation issues.");
     }
 
     ruby_single_main_ractor = NULL;
