Merge remote-tracking branch '3.0' into '3.1'
diff --git a/core-common/src/main/java/org/glassfish/jersey/JerseyPriorities.java b/core-common/src/main/java/org/glassfish/jersey/JerseyPriorities.java
index 6bcde01..941b383 100644
--- a/core-common/src/main/java/org/glassfish/jersey/JerseyPriorities.java
+++ b/core-common/src/main/java/org/glassfish/jersey/JerseyPriorities.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014, 2020 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 2023 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -16,6 +16,7 @@
 
 package org.glassfish.jersey;
 
+import jakarta.annotation.Priority;
 import jakarta.ws.rs.Priorities;
 
 /**
@@ -36,4 +37,15 @@
      * processing after the components with {@code Priorities.ENTITY_CODER} are processed.
      */
     public static final int POST_ENTITY_CODER = Priorities.ENTITY_CODER + 100;
+
+    /**
+     * Return the value of priority annotation on a given class, if exists. Return the default value if not present.
+     * @param prioritized the provider class that potentially has a priority.
+     * @param defaultValue the default priority value if not {@link @Priority) present
+     * @return the value of Priority annotation if present or the default otherwise.
+     */
+    public static int getPriorityValue(Class<?> prioritized, int defaultValue) {
+        final Priority priority = prioritized.getAnnotation(Priority.class);
+        return priority != null ? priority.value() : defaultValue;
+    }
 }
diff --git a/core-common/src/main/java/org/glassfish/jersey/internal/ExceptionMapperFactory.java b/core-common/src/main/java/org/glassfish/jersey/internal/ExceptionMapperFactory.java
index 7c51af4..55e5a68 100644
--- a/core-common/src/main/java/org/glassfish/jersey/internal/ExceptionMapperFactory.java
+++ b/core-common/src/main/java/org/glassfish/jersey/internal/ExceptionMapperFactory.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010, 2022 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 2023 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -28,9 +28,11 @@
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
+import jakarta.ws.rs.Priorities;
 import jakarta.ws.rs.ProcessingException;
 import jakarta.ws.rs.ext.ExceptionMapper;
 
+import org.glassfish.jersey.JerseyPriorities;
 import org.glassfish.jersey.internal.inject.Bindings;
 import org.glassfish.jersey.internal.inject.InjectionManager;
 import org.glassfish.jersey.internal.inject.InstanceBinding;
@@ -111,19 +113,18 @@
     private <T extends Throwable> ExceptionMapper<T> find(final Class<T> type, final T exceptionInstance) {
         ExceptionMapper<T> mapper = null;
         int minDistance = Integer.MAX_VALUE;
+        int priority = Priorities.USER;
 
         for (final ExceptionMapperType mapperType : exceptionMapperTypes.get()) {
             final int d = distance(type, mapperType.exceptionType);
             if (d >= 0 && d <= minDistance) {
                 final ExceptionMapper<T> candidate = mapperType.mapper.getInstance();
+                final int p = mapperType.mapper.getRank() > 0 ? mapperType.mapper.getRank() : Priorities.USER;
 
-                if (isPreferredCandidate(exceptionInstance, candidate, d == minDistance)) {
+                if (isPreferredCandidate(exceptionInstance, candidate, d == minDistance && p >= priority)) {
                     mapper = candidate;
                     minDistance = d;
-                    if (d == 0) {
-                        // slight optimization: if the distance is 0, it is already the best case, so we can exit
-                        return mapper;
-                    }
+                    priority = p;
                 }
             }
         }
diff --git a/core-common/src/main/java/org/glassfish/jersey/internal/RuntimeDelegateImpl.java b/core-common/src/main/java/org/glassfish/jersey/internal/RuntimeDelegateImpl.java
index 89e7089..db16be8 100644
--- a/core-common/src/main/java/org/glassfish/jersey/internal/RuntimeDelegateImpl.java
+++ b/core-common/src/main/java/org/glassfish/jersey/internal/RuntimeDelegateImpl.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011, 2021 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2023 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -87,6 +87,7 @@
         for (RuntimeDelegate delegate : ServiceFinder.find(RuntimeDelegate.class)) {
             // try to find runtime delegate from core-server
             if (delegate.getClass() != RuntimeDelegateImpl.class) {
+                RuntimeDelegate.setInstance(delegate);
                 return delegate;
             }
         }
diff --git a/core-common/src/main/java/org/glassfish/jersey/internal/config/ExternalPropertiesConfigurationFactory.java b/core-common/src/main/java/org/glassfish/jersey/internal/config/ExternalPropertiesConfigurationFactory.java
index f5c2bd7..1a83e23 100644
--- a/core-common/src/main/java/org/glassfish/jersey/internal/config/ExternalPropertiesConfigurationFactory.java
+++ b/core-common/src/main/java/org/glassfish/jersey/internal/config/ExternalPropertiesConfigurationFactory.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2019, 2022 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2023 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -16,11 +16,11 @@
 
 package org.glassfish.jersey.internal.config;
 
+import org.glassfish.jersey.JerseyPriorities;
 import org.glassfish.jersey.internal.ServiceFinder;
 import org.glassfish.jersey.spi.ExternalConfigurationModel;
 import org.glassfish.jersey.spi.ExternalConfigurationProvider;
 
-import jakarta.annotation.Priority;
 import jakarta.ws.rs.Priorities;
 import jakarta.ws.rs.core.Configurable;
 import java.util.ArrayList;
@@ -152,19 +152,8 @@
 
         @Override
         public int compare(ExternalConfigurationProvider config1, ExternalConfigurationProvider config2) {
-
-            boolean config1PriorityPresent = config1.getClass().isAnnotationPresent(Priority.class);
-            boolean config2PriorityPresent = config2.getClass().isAnnotationPresent(Priority.class);
-
-            int priority1 = Priorities.USER;
-            int priority2 = Priorities.USER;
-
-            if (config1PriorityPresent) {
-                priority1 = config1.getClass().getAnnotation(Priority.class).value();
-            }
-            if (config2PriorityPresent) {
-                priority2 = config2.getClass().getAnnotation(Priority.class).value();
-            }
+            int priority1 = JerseyPriorities.getPriorityValue(config1.getClass(), Priorities.USER);
+            int priority2 = JerseyPriorities.getPriorityValue(config2.getClass(), Priorities.USER);
 
             if (priority1 == priority2) {
                 return config1.getClass().getName().compareTo(config2.getClass().getName());
diff --git a/core-common/src/main/java/org/glassfish/jersey/internal/inject/Providers.java b/core-common/src/main/java/org/glassfish/jersey/internal/inject/Providers.java
index e157d12..4bbab3e 100644
--- a/core-common/src/main/java/org/glassfish/jersey/internal/inject/Providers.java
+++ b/core-common/src/main/java/org/glassfish/jersey/internal/inject/Providers.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2023 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -42,6 +42,7 @@
 
 import jakarta.annotation.Priority;
 
+import org.glassfish.jersey.JerseyPriorities;
 import org.glassfish.jersey.internal.LocalizationMessages;
 import org.glassfish.jersey.model.ContractProvider;
 import org.glassfish.jersey.model.internal.RankedComparator;
@@ -357,13 +358,7 @@
     }
 
     private static int getPriority(Class<?> serviceClass) {
-        Priority annotation = serviceClass.getAnnotation(Priority.class);
-        if (annotation != null) {
-            return annotation.value();
-        }
-
-        // default priority
-        return Priorities.USER;
+        return JerseyPriorities.getPriorityValue(serviceClass, /* default priority */ Priorities.USER);
     }
 
     private static <T> Class<T> getImplementationClass(Class<T> contract, ServiceHolder<T> serviceHolder) {
diff --git a/core-common/src/main/java/org/glassfish/jersey/message/internal/TracingLogger.java b/core-common/src/main/java/org/glassfish/jersey/message/internal/TracingLogger.java
index 1249432..b1ed54f 100644
--- a/core-common/src/main/java/org/glassfish/jersey/message/internal/TracingLogger.java
+++ b/core-common/src/main/java/org/glassfish/jersey/message/internal/TracingLogger.java
@@ -23,8 +23,7 @@
 import jakarta.ws.rs.core.MultivaluedMap;
 import jakarta.ws.rs.core.Response;
 
-import jakarta.annotation.Priority;
-
+import org.glassfish.jersey.JerseyPriorities;
 import org.glassfish.jersey.internal.PropertiesDelegate;
 
 /**
@@ -315,8 +314,9 @@
             } else {
                 textSB.append('[');
                 formatInstance(instance, textSB);
-                if (instance.getClass().isAnnotationPresent(Priority.class)) {
-                    textSB.append(" #").append(instance.getClass().getAnnotation(Priority.class).value());
+                final int priority = JerseyPriorities.getPriorityValue(instance.getClass(), -1);
+                if (priority != -1) {
+                    textSB.append(" #").append(priority);
                 }
                 if (instance instanceof WebApplicationException) {
                     formatResponse(((WebApplicationException) instance).getResponse(), textSB);
diff --git a/core-common/src/main/java/org/glassfish/jersey/model/internal/CommonConfig.java b/core-common/src/main/java/org/glassfish/jersey/model/internal/CommonConfig.java
index 328b899..1afa8c1 100644
--- a/core-common/src/main/java/org/glassfish/jersey/model/internal/CommonConfig.java
+++ b/core-common/src/main/java/org/glassfish/jersey/model/internal/CommonConfig.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2023 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -45,6 +45,7 @@
 import jakarta.annotation.Priority;
 
 import org.glassfish.jersey.ExtendedConfig;
+import org.glassfish.jersey.JerseyPriorities;
 import org.glassfish.jersey.internal.LocalizationMessages;
 import org.glassfish.jersey.internal.ServiceFinder;
 import org.glassfish.jersey.internal.inject.Binder;
@@ -131,12 +132,7 @@
             if (priority != ContractProvider.NO_PRIORITY) {
                 return priority;
             }
-            final Priority priorityAnnotation = featureClass.getAnnotation(Priority.class);
-            if (priorityAnnotation != null) {
-                return priorityAnnotation.value();
-            } else {
-                return Priorities.USER;
-            }
+            return JerseyPriorities.getPriorityValue(featureClass, Priorities.USER);
         }
 
         /**
@@ -592,10 +588,8 @@
         // Check whether meta providers have been initialized for a config this config has been loaded from.
         if (!disableMetaProviderConfiguration) {
             final Set<AutoDiscoverable> providers = new TreeSet<>((o1, o2) -> {
-                final int p1 = o1.getClass().isAnnotationPresent(Priority.class)
-                        ? o1.getClass().getAnnotation(Priority.class).value() : Priorities.USER;
-                final int p2 = o2.getClass().isAnnotationPresent(Priority.class)
-                        ? o2.getClass().getAnnotation(Priority.class).value() : Priorities.USER;
+                final int p1 = JerseyPriorities.getPriorityValue(o1.getClass(), Priorities.USER);
+                final int p2 = JerseyPriorities.getPriorityValue(o2.getClass(), Priorities.USER);
 
                 return (p1 < p2 || p1 == p2) ? -1 : 1;
             });
diff --git a/core-common/src/main/java/org/glassfish/jersey/model/internal/RankedProvider.java b/core-common/src/main/java/org/glassfish/jersey/model/internal/RankedProvider.java
index 56b3470..cc255f2 100644
--- a/core-common/src/main/java/org/glassfish/jersey/model/internal/RankedProvider.java
+++ b/core-common/src/main/java/org/glassfish/jersey/model/internal/RankedProvider.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2023 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -23,6 +23,7 @@
 
 import jakarta.annotation.Priority;
 
+import org.glassfish.jersey.JerseyPriorities;
 import org.glassfish.jersey.model.ContractProvider;
 
 /**
@@ -84,11 +85,7 @@
                 clazz = clazz.getSuperclass();
             }
 
-            if (clazz.isAnnotationPresent(Priority.class)) {
-                return clazz.getAnnotation(Priority.class).value();
-            } else {
-                return Priorities.USER;
-            }
+            return JerseyPriorities.getPriorityValue(clazz, Priorities.USER);
         }
     }
 
diff --git a/core-common/src/main/java/org/glassfish/jersey/uri/internal/UriTemplateParser.java b/core-common/src/main/java/org/glassfish/jersey/uri/internal/UriTemplateParser.java
index b438023..0f5f759 100644
--- a/core-common/src/main/java/org/glassfish/jersey/uri/internal/UriTemplateParser.java
+++ b/core-common/src/main/java/org/glassfish/jersey/uri/internal/UriTemplateParser.java
@@ -395,9 +395,14 @@
                     //               groupCounts.add(1 + skipGroup);
 
                     if (variables.hasLength(0)) {
-                        int len = TEMPLATE_VALUE_PATTERN.pattern().length() - 1;
-                        String pattern = TEMPLATE_VALUE_PATTERN.pattern().substring(0, len) + '{' + variables.getLength(0) + '}';
-                        namePattern = Pattern.compile(pattern);
+                        if (variables.getLength(0) != 0) {
+                            int len = TEMPLATE_VALUE_PATTERN.pattern().length() - 1;
+                            String pattern = TEMPLATE_VALUE_PATTERN.pattern().substring(0, len)
+                                    + '{' + variables.getLength(0) + '}';
+                            namePattern = Pattern.compile(pattern);
+                        } else {
+                            namePattern = TEMPLATE_VALUE_PATTERN;
+                        }
                         templateVariable.setLength(variables.getLength(0));
                     } else {
                         namePattern = (!variables.hasRegexp(0))
@@ -462,7 +467,10 @@
                         if (argIndex != 0) {
                             regexBuilder.append(")");
                         }
-                        regexBuilder.append("{0,1}");
+
+                        if (!variables.hasRegexp(argIndex)) {
+                            regexBuilder.append("{0,1}");
+                        }
 
                         argIndex++;
                         groupCounts.add(2);
@@ -571,6 +579,7 @@
 
             StringBuilder regexBuilder = new StringBuilder();
             State state = State.TEMPLATE;
+            State previousState;
             boolean star = false;
             boolean whiteSpace = false;
             boolean ignoredLastComma = false;
@@ -579,6 +588,7 @@
             int regExpRound = 0;   // (
             boolean reqExpSlash = false; // \
             while ((state.value & (State.ERROR.value | State.EXIT.value)) == 0) {
+                previousState = state;
                 c = ci.next();
                 // "\\{(\\w[-\\w\\.]*)
                 if (Character.isLetterOrDigit(c)) {
@@ -702,8 +712,8 @@
                                 regexps.add(regex);
                             }
                         } else {
-                            regexps.add(null);
-                            lengths.add(null);
+                            regexps.add(previousState == State.REGEXP ? "" : null);
+                            lengths.add(previousState == State.REGEXP ? 0 : null);
                         }
 
                         names.add(nameBuilder.toString());
diff --git a/core-common/src/test/java/org/glassfish/jersey/uri/UriTemplateTest.java b/core-common/src/test/java/org/glassfish/jersey/uri/UriTemplateTest.java
index 7846c11..cdd965b 100644
--- a/core-common/src/test/java/org/glassfish/jersey/uri/UriTemplateTest.java
+++ b/core-common/src/test/java/org/glassfish/jersey/uri/UriTemplateTest.java
@@ -31,6 +31,8 @@
 
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
+import org.opentest4j.AssertionFailedError;
+
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -257,7 +259,7 @@
         final Map<String, String> m = new HashMap<String, String>();
 
         boolean isMatch = t.match(uri, m);
-        assertTrue(isMatch);
+        assertTrue(isMatch, "No match for '" + uri + "' & params '" + Arrays.toString(values) + "`");
         assertEquals(values.length, t.getTemplateVariables().size());
 
         final Iterator<String> names = t.getTemplateVariables().iterator();
@@ -982,6 +984,7 @@
         _testTemplateNames("/{a,b,c}", "a", "b", "c");
         _testMatching("/uri/{a}", "/uri/hello", "hello");
         _testMatching("/uri/{a,b}", "/uri/hello,world", "hello", "world");
+        _testMatching("/uri/{a,b}", "/uri/x", "x", null);
         _testMatching("/uri{?a,b}", "/uri?a=hello&b=world", "hello", "world");
         _testMatching("/uri/{a,b,c}", "/uri/hello,world,!", "hello", "world", "!");
         _testMatching("/uri/{a,b,c}", "/uri/hello,world", "hello", "world", null);
@@ -990,6 +993,12 @@
     }
 
     @Test
+    public void testRegularExpressionIsNotOptional() {
+        Assertions.assertThrows(AssertionFailedError.class,
+                () -> _testMatching("/{name: [a-z0-9]{3,128}}", "/", new String[]{null}));
+    }
+
+    @Test
     void testRfc6570PathLength() {
         _testMatching("/uri/{a:5}", "/uri/hello", "hello");
         _testMatching("/uri/{a:5,b:6}", "/uri/hello,world!", "hello", "world!");
diff --git a/core-server/src/test/java/org/glassfish/jersey/server/model/ExceptionMapperPriorityTest.java b/core-server/src/test/java/org/glassfish/jersey/server/model/ExceptionMapperPriorityTest.java
new file mode 100644
index 0000000..8bed349
--- /dev/null
+++ b/core-server/src/test/java/org/glassfish/jersey/server/model/ExceptionMapperPriorityTest.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.server.model;
+
+import org.glassfish.jersey.internal.ExceptionMapperFactory;
+import org.glassfish.jersey.internal.inject.AbstractBinder;
+import org.glassfish.jersey.internal.inject.CustomAnnotationLiteral;
+import org.glassfish.jersey.internal.inject.InjectionManager;
+import org.glassfish.jersey.internal.inject.Injections;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.TestInjectionManagerFactory;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import jakarta.annotation.Priority;
+import jakarta.inject.Singleton;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.ext.ExceptionMapper;
+
+public class ExceptionMapperPriorityTest {
+    @Test
+    void testExceptionMapperOverridePriority() {
+        for (int order = 0; order != 2; order++) {
+            final ResourceConfig rc = new ResourceConfig();
+            if (order == 0) {
+                rc.register(HigherPriorityExceptionMapper.class, 200);
+                rc.register(LowerPriorityExceptionMapper.class, 100);
+            } else {
+                rc.register(LowerPriorityExceptionMapper.class, 100);
+                rc.register(HigherPriorityExceptionMapper.class, 200);
+            }
+
+            final TestInjectionManagerFactory.BootstrapResult bootstrap = TestInjectionManagerFactory.createInjectionManager(rc);
+            final ExceptionMapperFactory mapperFactory = new ExceptionMapperFactory(bootstrap.injectionManager);
+
+            final ExceptionMapper mapper = mapperFactory.findMapping(new NullPointerException());
+            Assertions.assertTrue(mapper instanceof LowerPriorityExceptionMapper,
+                    "LowerPriorityExceptionMapper should be returned, got " + mapper.getClass().getSimpleName());
+        }
+    }
+
+    @Test
+    void testExceptionMapperPriority() {
+        for (int order = 0; order != 2; order++) {
+            final ResourceConfig rc = new ResourceConfig();
+
+            if (order == 0) {
+                rc.register(HigherPriorityExceptionMapper.class);
+                rc.register(LowerPriorityExceptionMapper.class);
+            } else {
+                rc.register(LowerPriorityExceptionMapper.class);
+                rc.register(HigherPriorityExceptionMapper.class);
+            }
+
+            final TestInjectionManagerFactory.BootstrapResult bootstrap = TestInjectionManagerFactory.createInjectionManager(rc);
+            final ExceptionMapperFactory mapperFactory = new ExceptionMapperFactory(bootstrap.injectionManager);
+
+            final ExceptionMapper mapper = mapperFactory.findMapping(new NullPointerException());
+            Assertions.assertTrue(mapper instanceof HigherPriorityExceptionMapper,
+                    "HigherPriorityExceptionMapper should be returned, got " + mapper.getClass().getSimpleName());
+        }
+    }
+
+    @Test
+    public void testFindPriorityExceptionMapper() {
+        for (int order = 0; order != 2; order++) {
+            int finalOrder = order;
+            final InjectionManager injectionManager = Injections.createInjectionManager(new AbstractBinder() {
+                @Override
+                protected void configure() {
+                    if (finalOrder == 0) {
+                        bind(LowerPriorityExceptionMapper.class).to(ExceptionMapper.class).in(Singleton.class)
+                                .qualifiedBy(CustomAnnotationLiteral.INSTANCE);
+                        bind(HigherPriorityExceptionMapper.class).to(ExceptionMapper.class).in(Singleton.class)
+                                .qualifiedBy(CustomAnnotationLiteral.INSTANCE);
+                        bind(HighDistanceExceptionMapper.class).to(ExceptionMapper.class).in(Singleton.class)
+                                .qualifiedBy(CustomAnnotationLiteral.INSTANCE);
+                    } else if (finalOrder == 1) {
+                        bind(HighDistanceExceptionMapper.class).to(ExceptionMapper.class).in(Singleton.class)
+                                .qualifiedBy(CustomAnnotationLiteral.INSTANCE);
+                        bind(HigherPriorityExceptionMapper.class).to(ExceptionMapper.class).in(Singleton.class)
+                                .qualifiedBy(CustomAnnotationLiteral.INSTANCE);
+                        bind(LowerPriorityExceptionMapper.class).to(ExceptionMapper.class).in(Singleton.class)
+                                .qualifiedBy(CustomAnnotationLiteral.INSTANCE);
+                    }
+                }
+            });
+            injectionManager.completeRegistration();
+            final ExceptionMapperFactory mapperFactory = new ExceptionMapperFactory(injectionManager);
+
+            final ExceptionMapper mapper = mapperFactory.findMapping(new NullPointerException());
+            Assertions.assertTrue(mapper instanceof HigherPriorityExceptionMapper,
+                    "HigherPriorityExceptionMapper should be returned, got " + mapper.getClass().getSimpleName());
+        }
+    }
+
+    @Test
+    public void testFindPriorityExceptionMapperPrioritiesOverUSER() {
+        for (int order = 0; order != 2; order++) {
+            int finalOrder = order;
+            final InjectionManager injectionManager = Injections.createInjectionManager(new AbstractBinder() {
+                @Override
+                protected void configure() {
+                    if (finalOrder == 0) {
+                        bind(LowerOverUSERPriorityExceptionMapper.class).to(ExceptionMapper.class).in(Singleton.class)
+                                .qualifiedBy(CustomAnnotationLiteral.INSTANCE);
+                        bind(HigherOverUSERPriorityExceptionMapper.class).to(ExceptionMapper.class).in(Singleton.class)
+                                .qualifiedBy(CustomAnnotationLiteral.INSTANCE);
+                    } else {
+                        bind(HigherOverUSERPriorityExceptionMapper.class).to(ExceptionMapper.class).in(Singleton.class)
+                                .qualifiedBy(CustomAnnotationLiteral.INSTANCE);
+                        bind(LowerOverUSERPriorityExceptionMapper.class).to(ExceptionMapper.class).in(Singleton.class)
+                                .qualifiedBy(CustomAnnotationLiteral.INSTANCE);
+                    }
+                }
+            });
+            injectionManager.completeRegistration();
+            final ExceptionMapperFactory mapperFactory = new ExceptionMapperFactory(injectionManager);
+
+            final ExceptionMapper mapper = mapperFactory.findMapping(new NullPointerException());
+            Assertions.assertTrue(mapper instanceof HigherOverUSERPriorityExceptionMapper,
+                    "HigherPriorityExceptionMapper should be returned, got " + mapper.getClass().getSimpleName());
+        }
+    }
+
+    abstract static class NPEExceptionMapper implements ExceptionMapper<NullPointerException> {
+        @Override
+        public Response toResponse(NullPointerException exception) {
+            return null;
+        }
+    }
+
+    @Priority(100)
+    static class HigherPriorityExceptionMapper extends NPEExceptionMapper {
+    }
+
+    @Priority(200)
+    static class LowerPriorityExceptionMapper extends NPEExceptionMapper {
+    }
+
+    @Priority(5500)
+    static class HigherOverUSERPriorityExceptionMapper extends NPEExceptionMapper {
+    }
+
+    @Priority(6000)
+    static class LowerOverUSERPriorityExceptionMapper extends NPEExceptionMapper {
+    }
+
+    @Priority(50)
+    static class HighDistanceExceptionMapper implements ExceptionMapper<RuntimeException> {
+        @Override
+        public Response toResponse(RuntimeException exception) {
+            return null;
+        }
+    }
+}
diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientBuilderImpl.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientBuilderImpl.java
index 1a3504e..1e81f52 100644
--- a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientBuilderImpl.java
+++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientBuilderImpl.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2019, 2022 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2023 Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2019, 2021 Payara Foundation and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
@@ -39,7 +39,6 @@
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
-import jakarta.annotation.Priority;
 import javax.net.ssl.HostnameVerifier;
 import javax.net.ssl.SSLContext;
 import jakarta.ws.rs.Priorities;
@@ -61,6 +60,7 @@
 import org.eclipse.microprofile.rest.client.ext.QueryParamStyle;
 import org.eclipse.microprofile.rest.client.ext.ResponseExceptionMapper;
 import org.eclipse.microprofile.rest.client.spi.RestClientListener;
+import org.glassfish.jersey.JerseyPriorities;
 import org.glassfish.jersey.client.ClientConfig;
 import org.glassfish.jersey.client.ClientProperties;
 import org.glassfish.jersey.client.Initializable;
@@ -299,8 +299,7 @@
         } else if (providerPriorityJersey instanceof Integer) {
             return (int) providerPriorityJersey;
         }
-        Priority priority = providerClass.getAnnotation(Priority.class);
-        return priority == null ? -1 : priority.value();
+        return JerseyPriorities.getPriorityValue(providerClass, -1);
     }
 
     @Override
@@ -524,9 +523,7 @@
 
         Integer getPriority() {
             if (priority == null) {
-                priority = Optional.ofNullable(factory.getClass().getAnnotation(Priority.class))
-                        .map(Priority::value)
-                        .orElse(Priorities.USER);
+                priority = JerseyPriorities.getPriorityValue(factory.getClass(), Priorities.USER);
             }
             return priority;
         }
diff --git a/incubator/cdi-inject-weld/src/main/java/org/glassfish/jersey/inject/weld/internal/bean/JerseyBean.java b/incubator/cdi-inject-weld/src/main/java/org/glassfish/jersey/inject/weld/internal/bean/JerseyBean.java
index a56f23b..66594f9 100644
--- a/incubator/cdi-inject-weld/src/main/java/org/glassfish/jersey/inject/weld/internal/bean/JerseyBean.java
+++ b/incubator/cdi-inject-weld/src/main/java/org/glassfish/jersey/inject/weld/internal/bean/JerseyBean.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021, 2022 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2021, 2023 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -23,7 +23,6 @@
 import java.util.HashSet;
 import java.util.Set;
 
-import jakarta.annotation.Priority;
 import jakarta.enterprise.context.Dependent;
 import jakarta.enterprise.context.RequestScoped;
 import jakarta.enterprise.context.spi.CreationalContext;
@@ -36,6 +35,7 @@
 import jakarta.inject.Singleton;
 import jakarta.ws.rs.RuntimeType;
 
+import org.glassfish.jersey.JerseyPriorities;
 import org.glassfish.jersey.internal.inject.Binding;
 import org.glassfish.jersey.internal.inject.PerLookup;
 import org.glassfish.jersey.internal.inject.PerThread;
@@ -161,15 +161,13 @@
             return binding.getRank();
         }
 
+        int defaultValue = 1;
         Class<T> type = binding.getImplementationType();
         if (type != null) {
-            Priority priority = type.getAnnotation(Priority.class);
-            if (priority != null) {
-                return priority.value();
-            }
+            return JerseyPriorities.getPriorityValue(type, defaultValue);
         }
 
-        return 1;
+        return defaultValue;
     }
 
     @Override
diff --git a/inject/cdi2-se/src/main/java/org/glassfish/jersey/inject/cdi/se/bean/JerseyBean.java b/inject/cdi2-se/src/main/java/org/glassfish/jersey/inject/cdi/se/bean/JerseyBean.java
index c6bd602..8f3fc55 100644
--- a/inject/cdi2-se/src/main/java/org/glassfish/jersey/inject/cdi/se/bean/JerseyBean.java
+++ b/inject/cdi2-se/src/main/java/org/glassfish/jersey/inject/cdi/se/bean/JerseyBean.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017, 2022 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 2023 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -22,7 +22,6 @@
 import java.util.HashSet;
 import java.util.Set;
 
-import jakarta.annotation.Priority;
 import jakarta.enterprise.context.Dependent;
 import jakarta.enterprise.context.RequestScoped;
 import jakarta.enterprise.context.spi.CreationalContext;
@@ -34,6 +33,7 @@
 import jakarta.enterprise.util.AnnotationLiteral;
 import jakarta.inject.Singleton;
 
+import org.glassfish.jersey.JerseyPriorities;
 import org.glassfish.jersey.internal.inject.Binding;
 import org.glassfish.jersey.internal.inject.PerLookup;
 import org.glassfish.jersey.internal.inject.PerThread;
@@ -151,16 +151,14 @@
         if (binding.getRank() != null) {
             return binding.getRank();
         }
+        int defaultValue = 1;
 
         Class<T> type = binding.getImplementationType();
         if (type != null) {
-            Priority priority = type.getAnnotation(Priority.class);
-            if (priority != null) {
-                return priority.value();
-            }
+            return JerseyPriorities.getPriorityValue(type, defaultValue);
         }
 
-        return 1;
+        return defaultValue;
     }
 
     @Override