diff --git a/src/java.base/share/classes/java/util/FormatProcessor.java b/src/java.base/share/classes/java/util/FormatProcessor.java
index d5121ae5c4f..20fbc1cedcf 100644
--- a/src/java.base/share/classes/java/util/FormatProcessor.java
+++ b/src/java.base/share/classes/java/util/FormatProcessor.java
@@ -1,5 +1,6 @@
 /*
  * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, Alibaba Group Holding Limited. 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
@@ -30,7 +31,6 @@ import java.lang.invoke.MethodHandles;
 import java.lang.invoke.MethodType;
 import java.lang.StringTemplate.Processor;
 import java.lang.StringTemplate.Processor.Linkage;
-import java.util.regex.Matcher;
 
 import jdk.internal.javac.PreviewFeature;
 
@@ -218,22 +218,35 @@ public final class FormatProcessor implements Processor<String, RuntimeException
      * @throws MissingFormatArgumentException if not at end or found and not needed
      */
     private static boolean findFormat(String fragment, boolean needed) {
-        Matcher matcher = Formatter.FORMAT_SPECIFIER_PATTERN.matcher(fragment);
-        String group;
-
-        while (matcher.find()) {
-            group = matcher.group();
-
-            if (!group.equals("%%") && !group.equals("%n")) {
-                if (matcher.end() == fragment.length() && needed) {
-                    return true;
-                }
-
-                throw new MissingFormatArgumentException(group +
-                        " is not immediately followed by an embedded expression");
+        int max = fragment.length();
+        for (int i = 0; i < max;) {
+            int n = fragment.indexOf('%', i);
+            if (n < 0) {
+                return false;
             }
-        }
 
+            i = n + 1;
+            if (i >= max) {
+                return false;
+            }
+
+            char c = fragment.charAt(i);
+            if (c == '%' || c == 'n') {
+                i++;
+                continue;
+            }
+            int off = new Formatter.FormatSpecifierParser(null, c, i, fragment, max)
+                    .parse();
+            if (off == 0) {
+                return false;
+            }
+            if (i + off == max && needed) {
+                return true;
+            }
+            throw new MissingFormatArgumentException(
+                    fragment.substring(i - 1, i + off)
+                    + " is not immediately followed by an embedded expression");
+        }
         return false;
     }
 
diff --git a/src/java.base/share/classes/java/util/Formatter.java b/src/java.base/share/classes/java/util/Formatter.java
index 9956ba18d69..14a850a24bd 100644
--- a/src/java.base/share/classes/java/util/Formatter.java
+++ b/src/java.base/share/classes/java/util/Formatter.java
@@ -1,5 +1,6 @@
 /*
  * Copyright (c) 2003, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, Alibaba Group Holding Limited. 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
@@ -49,8 +50,6 @@ import java.text.DecimalFormat;
 import java.text.DecimalFormatSymbols;
 import java.text.NumberFormat;
 import java.text.spi.NumberFormatProvider;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 import java.time.DateTimeException;
 import java.time.Instant;
@@ -2810,20 +2809,14 @@ public final class Formatter implements Closeable, Flushable {
         return this;
     }
 
-    // %[argument_index$][flags][width][.precision][t]conversion
-    static final String FORMAT_SPECIFIER
-        = "%(\\d+\\$)?([-#+ 0,(\\<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z%])";
-
-    static final Pattern FORMAT_SPECIFIER_PATTERN = Pattern.compile(FORMAT_SPECIFIER);
-
     /**
      * Finds format specifiers in the format string.
      */
     static List<FormatString> parse(String s) {
+        FormatSpecifierParser parser = null;
         ArrayList<FormatString> al = new ArrayList<>();
         int i = 0;
         int max = s.length();
-        Matcher m = null; // create if needed
         while (i < max) {
             int n = s.indexOf('%', i);
             if (n < 0) {
@@ -2846,14 +2839,16 @@ public final class Formatter implements Closeable, Flushable {
                 al.add(new FormatSpecifier(c));
                 i++;
             } else {
-                if (m == null) {
-                    m = FORMAT_SPECIFIER_PATTERN.matcher(s);
-                }
                 // We have already parsed a '%' at n, so we either have a
                 // match or the specifier at n is invalid
-                if (m.find(n) && m.start() == n) {
-                    al.add(new FormatSpecifier(s, m));
-                    i = m.end();
+                if (parser == null) {
+                    parser = new FormatSpecifierParser(al, c, i, s, max);
+                } else {
+                    parser.reset(c, i);
+                }
+                int off = parser.parse();
+                if (off > 0) {
+                    i += off;
                 } else {
                     throw new UnknownFormatConversionException(String.valueOf(c));
                 }
@@ -2862,6 +2857,159 @@ public final class Formatter implements Closeable, Flushable {
         return al;
     }
 
+    static final class FormatSpecifierParser {
+        final ArrayList<FormatString> al;
+        final String s;
+        final int max;
+        char first;
+        int start;
+        int off;
+        char c;
+        int argSize;
+        int flagSize;
+        int widthSize;
+
+        FormatSpecifierParser(ArrayList<FormatString> al, char first, int start, String s, int max) {
+            this.al = al;
+
+            this.first = first;
+            this.c = first;
+            this.start = start;
+            this.off = start;
+
+            this.s = s;
+            this.max = max;
+        }
+
+        void reset(char first, int start) {
+            this.first = first;
+            this.c = first;
+            this.start = start;
+            this.off = start;
+
+            argSize = 0;
+            flagSize = 0;
+            widthSize = 0;
+        }
+
+        /**
+         * If a valid format specifier is found, construct a FormatString and add it to {@link #al}.
+         * The format specifiers for general, character, and numeric types have
+         * the following syntax:
+         *
+         * <blockquote><pre>
+         *   %[argument_index$][flags][width][.precision]conversion
+         * </pre></blockquote>
+         *
+         * As described by the following regular expression:
+         *
+         * <blockquote><pre>
+         *    %(\d+\$)?([-#+ 0,(\<]*)?(\d+)?(\.\d+)?([tT])?([a-zA-Z%])
+         * </pre></blockquote>
+         *
+         * @return the length of the format specifier. If no valid format specifier is found, 0 is returned.
+         */
+        int parse() {
+            int precisionSize = 0;
+
+            // (\d+\$)?
+            parseArgument();
+
+            // ([-#+ 0,(\<]*)?
+            parseFlag();
+
+            // (\d+)?
+            parseWidth();
+
+            if (c == '.') {
+                // (\.\d+)?
+                precisionSize = parsePrecision();
+                if (precisionSize == -1) {
+                    return 0;
+                }
+            }
+
+            // ([tT])?([a-zA-Z%])
+            char t = '\0', conversion = '\0';
+            if ((c == 't' || c == 'T') && off + 1 < max) {
+                char c1 = s.charAt(off + 1);
+                if (isConversion(c1)) {
+                    t = c;
+                    conversion = c1;
+                    off += 2;
+                }
+            } else if (isConversion(c)) {
+                conversion = c;
+                ++off;
+            } else {
+                return 0;
+            }
+
+            if (argSize + flagSize + widthSize + precisionSize + t + conversion != 0) {
+                if (al != null) {
+                    FormatSpecifier formatSpecifier
+                            = new FormatSpecifier(s, start, argSize, flagSize, widthSize, precisionSize, t, conversion);
+                    al.add(formatSpecifier);
+                }
+                return off - start;
+            }
+            return 0;
+        }
+
+        private void parseArgument() {
+            // (\d+\$)?
+            int i = off;
+            for (; i < max && isDigit(c = s.charAt(i)); ++i);  // empty body
+            if (i == off || c != '$') {
+                c = first;
+                return;
+            }
+
+            i++; // skip '$'
+            if (i < max) {
+                c = s.charAt(i);
+            }
+
+            argSize = i - off;
+            off = i;
+        }
+
+        private void parseFlag() {
+            // ([-#+ 0,(\<]*)?
+            int i = off;
+            for (; i < max && Flags.isFlag(c = s.charAt(i)); ++i);  // empty body
+            flagSize = i - off;
+            off = i;
+        }
+
+        private void parseWidth() {
+            // (\d+)?
+            int i = off;
+            for (; i < max && isDigit(c = s.charAt(i)); ++i);  // empty body
+            widthSize = i - off;
+            off = i;
+        }
+
+        private int parsePrecision() {
+            int i = ++off;
+            for (; i < max && isDigit(c = s.charAt(i)); ++i);  // empty body
+            if (i != off) {
+                int size = i - off + 1;
+                off = i;
+                return size;
+            }
+            return -1;
+        }
+    }
+
+    static boolean isConversion(char c) {
+        return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '%';
+    }
+
+    private static boolean isDigit(char c) {
+        return c >= '0' && c <= '9';
+    }
+
     interface FormatString {
         int index();
         void print(Formatter fmt, Object arg, Locale l) throws IOException;
@@ -2984,21 +3132,44 @@ public final class Formatter implements Closeable, Flushable {
             }
         }
 
-        FormatSpecifier(String s, Matcher m) {
-            index(s, m.start(1), m.end(1));
-            flags(s, m.start(2), m.end(2));
-            width(s, m.start(3), m.end(3));
-            precision(s, m.start(4), m.end(4));
+        FormatSpecifier(
+                String s,
+                int i,
+                int argSize,
+                int flagSize,
+                int widthSize,
+                int precisionSize,
+                char t,
+                char conversion
+        ) {
+            int argEnd = i + argSize;
+            int flagEnd = argEnd + flagSize;
+            int widthEnd = flagEnd + widthSize;
+            int precisionEnd = widthEnd + precisionSize;
 
-            int tTStart = m.start(5);
-            if (tTStart >= 0) {
+            if (argSize > 0) {
+                index(s, i, argEnd);
+            }
+            if (flagSize > 0) {
+                flags(s, argEnd, flagEnd);
+            }
+            if (widthSize > 0) {
+                width(s, flagEnd, widthEnd);
+            }
+            if (precisionSize > 0) {
+                precision(s, widthEnd, precisionEnd);
+            }
+            if (t != '\0') {
                 dt = true;
-                if (s.charAt(tTStart) == 'T') {
+                if (t == 'T') {
                     flags = Flags.add(flags, Flags.UPPERCASE);
                 }
             }
-            conversion(s.charAt(m.start(6)));
+            conversion(conversion);
+            check();
+        }
 
+        private void check() {
             if (dt)
                 checkDateTime();
             else if (Conversion.isGeneral(c))
@@ -4705,6 +4876,13 @@ public final class Formatter implements Closeable, Flushable {
             };
         }
 
+        private static boolean isFlag(char c) {
+            return switch (c) {
+                case '-', '#', '+', ' ', '0', ',', '(', '<' -> true;
+                default -> false;
+            };
+        }
+
         // Returns a string representation of the current {@code Flags}.
         public static String toString(int f) {
             StringBuilder sb = new StringBuilder();
diff --git a/test/jdk/java/lang/template/FormatterBuilder.java b/test/jdk/java/lang/template/FormatterBuilder.java
index 6406721f8e2..1fc79e89846 100644
--- a/test/jdk/java/lang/template/FormatterBuilder.java
+++ b/test/jdk/java/lang/template/FormatterBuilder.java
@@ -31,6 +31,8 @@
 import java.util.FormatProcessor;
 import java.util.Objects;
 import java.util.Locale;
+import java.util.MissingFormatArgumentException;
+import java.util.UnknownFormatConversionException;
 
 import static java.util.FormatProcessor.FMT;
 
@@ -50,6 +52,28 @@ public class FormatterBuilder {
         }
     }
 
+    public interface Executable {
+        void execute() throws Throwable;
+    }
+
+    static <T extends Throwable> void assertThrows(Class<T> expectedType, Executable executable, String message) {
+        Throwable actualException = null;
+        try {
+            executable.execute();
+        } catch (Throwable e) {
+            actualException = e;
+        }
+        if (actualException == null) {
+            throw new RuntimeException("Expected " + expectedType + " to be thrown, but nothing was thrown.");
+        }
+        if (!expectedType.isInstance(actualException)) {
+            throw new RuntimeException("Expected " + expectedType + " to be thrown, but was thrown " + actualException.getClass());
+        }
+        if (message != null && !message.equals(actualException.getMessage())) {
+            throw new RuntimeException("Expected " + message + " to be thrown, but was thrown " + actualException.getMessage());
+        }
+    }
+
     static void suite(FormatProcessor fmt) {
         Object nullObject = null;
         test(String.format("%b", false), fmt."%b\{false}");
@@ -911,5 +935,27 @@ public class FormatterBuilder {
         test(String.format("%-10A", -12345.6), fmt."%-10A\{-12345.6}");
         test(String.format("%-10A", 0.0), fmt."%-10A\{0.0}");
         test(String.format("%-10A", 12345.6), fmt."%-10A\{12345.6}");
+
+        test("aaa%false", fmt."aaa%%%b\{false}");
+        test("aaa" + System.lineSeparator() + "false", fmt."aaa%n%b\{false}");
+
+        assertThrows(
+                MissingFormatArgumentException.class,
+                () -> fmt. "%10ba\{ false }",
+                "Format specifier '%10b is not immediately followed by an embedded expression'");
+
+        assertThrows(
+                MissingFormatArgumentException.class,
+                () ->fmt. "%ba\{ false }",
+                "Format specifier '%b is not immediately followed by an embedded expression'");
+
+        assertThrows(
+                MissingFormatArgumentException.class,
+                () ->fmt. "%b",
+                "Format specifier '%b is not immediately followed by an embedded expression'");
+        assertThrows(
+                UnknownFormatConversionException.class,
+                () ->fmt. "%0",
+                "Conversion = '0'");
     }
 }
diff --git a/test/jdk/java/util/Formatter/Basic.java b/test/jdk/java/util/Formatter/Basic.java
index af8814328d2..4c8e3dfdf8b 100644
--- a/test/jdk/java/util/Formatter/Basic.java
+++ b/test/jdk/java/util/Formatter/Basic.java
@@ -24,6 +24,7 @@
 import java.io.*;
 import java.util.Formatter;
 import java.util.Locale;
+import java.util.UnknownFormatConversionException;
 
 public class Basic {
 
@@ -168,6 +169,8 @@ public class Basic {
     }
 
     public static void main(String[] args) {
+        common();
+
         BasicBoolean.test();
         BasicBooleanObject.test();
         BasicByte.test();
@@ -197,4 +200,12 @@ public class Basic {
             System.out.printf("All %d tests passed", pass);
         }
     }
+
+    private static void common() {
+        // non-conversion
+        tryCatch("%12", UnknownFormatConversionException.class);
+        tryCatch("% ", UnknownFormatConversionException.class);
+        tryCatch("%,", UnknownFormatConversionException.class);
+        tryCatch("%03.2", UnknownFormatConversionException.class);
+    }
 }
diff --git a/test/micro/org/openjdk/bench/java/lang/StringFormat.java b/test/micro/org/openjdk/bench/java/lang/StringFormat.java
index 9f462a91e33..660f627e161 100644
--- a/test/micro/org/openjdk/bench/java/lang/StringFormat.java
+++ b/test/micro/org/openjdk/bench/java/lang/StringFormat.java
@@ -32,6 +32,7 @@ import org.openjdk.jmh.annotations.Scope;
 import org.openjdk.jmh.annotations.State;
 import org.openjdk.jmh.annotations.Warmup;
 
+import java.math.BigDecimal;
 import java.util.concurrent.TimeUnit;
 
 /*
@@ -47,6 +48,12 @@ public class StringFormat {
 
     public String s = "str";
     public int i = 17;
+    public static final BigDecimal pi = new BigDecimal(Math.PI);
+
+    @Benchmark
+    public String decimalFormat() {
+        return "%010.3f".formatted(pi);
+    }
 
     @Benchmark
     public String stringFormat() {