This is an automated email from the ASF dual-hosted git repository.
jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git
The following commit(s) were added to refs/heads/master by this push:
new 11920cfe69 Support for jakarta.validation.constraints annotations.
new abedf16198 Merge branch 'master' of
https://gitbox.apache.org/repos/asf/juneau.git
11920cfe69 is described below
commit 11920cfe69b8c84369aa57381d157ef50c735453
Author: James Bognar <[email protected]>
AuthorDate: Mon Oct 13 18:17:30 2025 -0400
Support for jakarta.validation.constraints annotations.
---
juneau-bean/juneau-bean-atom/pom.xml | 2 +-
juneau-bean/juneau-bean-common/pom.xml | 2 +-
juneau-bean/juneau-bean-html5/pom.xml | 2 +-
juneau-bean/juneau-bean-jsonschema/pom.xml | 2 +-
juneau-bean/juneau-bean-openapi-v3/pom.xml | 2 +-
juneau-bean/juneau-bean-swagger-v2/pom.xml | 2 +-
juneau-bean/pom.xml | 2 +-
juneau-core/juneau-assertions/pom.xml | 2 +-
juneau-core/juneau-bct/pom.xml | 2 +-
juneau-core/juneau-common/pom.xml | 2 +-
juneau-core/juneau-config/pom.xml | 2 +-
juneau-core/juneau-marshall/pom.xml | 2 +-
.../org/apache/juneau/httppart/HttpPartFormat.java | 126 +++++++
.../org/apache/juneau/httppart/HttpPartSchema.java | 379 +++++++++++++++++++-
juneau-core/pom.xml | 2 +-
juneau-distrib/pom.xml | 2 +-
juneau-docs/docs/release-notes/9.2.0.md | 132 +++++++
.../docs/topics/09.05.08.HttpPartValidation.md | 389 +++++++++++++++++++++
juneau-docs/sidebars.ts | 5 +
juneau-examples/juneau-examples-core/pom.xml | 2 +-
.../juneau-examples-rest-jetty-ftest/pom.xml | 2 +-
juneau-examples/juneau-examples-rest-jetty/pom.xml | 2 +-
.../juneau-examples-rest-springboot/pom.xml | 2 +-
juneau-examples/juneau-examples-rest/pom.xml | 2 +-
juneau-examples/pom.xml | 2 +-
.../juneau-microservice-core/pom.xml | 2 +-
.../juneau-microservice-jetty/pom.xml | 2 +-
.../juneau-my-jetty-microservice/pom.xml | 2 +-
.../juneau-my-springboot-microservice/pom.xml | 2 +-
juneau-microservice/pom.xml | 2 +-
juneau-rest/juneau-rest-client/pom.xml | 2 +-
juneau-rest/juneau-rest-common/pom.xml | 2 +-
juneau-rest/juneau-rest-mock/pom.xml | 2 +-
juneau-rest/juneau-rest-server-rdf/pom.xml | 2 +-
juneau-rest/juneau-rest-server-springboot/pom.xml | 2 +-
juneau-rest/juneau-rest-server/pom.xml | 2 +-
juneau-rest/pom.xml | 2 +-
juneau-sc/juneau-sc-client/pom.xml | 2 +-
juneau-sc/juneau-sc-server/pom.xml | 2 +-
juneau-sc/pom.xml | 2 +-
juneau-utest/pom.xml | 8 +-
.../HttpPartSchema_JakartaValidation_Test.java | 334 ++++++++++++++++++
pom.xml | 2 +-
43 files changed, 1407 insertions(+), 38 deletions(-)
diff --git a/juneau-bean/juneau-bean-atom/pom.xml
b/juneau-bean/juneau-bean-atom/pom.xml
index 83d470c9e0..20b85fecdb 100644
--- a/juneau-bean/juneau-bean-atom/pom.xml
+++ b/juneau-bean/juneau-bean-atom/pom.xml
@@ -26,7 +26,7 @@
</parent>
<artifactId>juneau-bean-atom</artifactId>
- <name>juneau/bean/atom</name>
+ <name>Apache Juneau Atom Beans</name>
<description>Apache Juneau Serializable Beans - ATOM</description>
<packaging>bundle</packaging>
diff --git a/juneau-bean/juneau-bean-common/pom.xml
b/juneau-bean/juneau-bean-common/pom.xml
index 6a3a52e031..f46b2f8c02 100644
--- a/juneau-bean/juneau-bean-common/pom.xml
+++ b/juneau-bean/juneau-bean-common/pom.xml
@@ -26,7 +26,7 @@
</parent>
<artifactId>juneau-bean-common</artifactId>
- <name>juneau/bean/common</name>
+ <name>Apache Juneau Common Beans</name>
<description>Apache Juneau Serializable Beans - Common
Beans</description>
<packaging>bundle</packaging>
diff --git a/juneau-bean/juneau-bean-html5/pom.xml
b/juneau-bean/juneau-bean-html5/pom.xml
index e9e9fdb5af..5cb5915027 100644
--- a/juneau-bean/juneau-bean-html5/pom.xml
+++ b/juneau-bean/juneau-bean-html5/pom.xml
@@ -26,7 +26,7 @@
</parent>
<artifactId>juneau-bean-html5</artifactId>
- <name>juneau/bean/html5</name>
+ <name>Apache Juneau HTML5 Beans</name>
<description>Apache Juneau Serializable Beans - HTML5</description>
<packaging>bundle</packaging>
diff --git a/juneau-bean/juneau-bean-jsonschema/pom.xml
b/juneau-bean/juneau-bean-jsonschema/pom.xml
index 3de38a540b..3e8251fa3e 100644
--- a/juneau-bean/juneau-bean-jsonschema/pom.xml
+++ b/juneau-bean/juneau-bean-jsonschema/pom.xml
@@ -26,7 +26,7 @@
</parent>
<artifactId>juneau-bean-jsonschema</artifactId>
- <name>juneau/bean/jsonschema</name>
+ <name>Apache Juneau JSON Schema Beans</name>
<description>Apache Juneau Serializable Beans - JSON
Schema</description>
<packaging>bundle</packaging>
diff --git a/juneau-bean/juneau-bean-openapi-v3/pom.xml
b/juneau-bean/juneau-bean-openapi-v3/pom.xml
index e5b15bf2e1..8324fc6825 100644
--- a/juneau-bean/juneau-bean-openapi-v3/pom.xml
+++ b/juneau-bean/juneau-bean-openapi-v3/pom.xml
@@ -26,7 +26,7 @@
</parent>
<artifactId>juneau-bean-openapi-v3</artifactId>
- <name>juneau/bean/openapi-v3</name>
+ <name>Apache Juneau OpenAPI 3.0 Beans</name>
<description>Apache Juneau Serializable Beans - OpenAPI v3</description>
<packaging>bundle</packaging>
diff --git a/juneau-bean/juneau-bean-swagger-v2/pom.xml
b/juneau-bean/juneau-bean-swagger-v2/pom.xml
index aa23631fbb..f2a3665409 100644
--- a/juneau-bean/juneau-bean-swagger-v2/pom.xml
+++ b/juneau-bean/juneau-bean-swagger-v2/pom.xml
@@ -26,7 +26,7 @@
</parent>
<artifactId>juneau-bean-swagger-v2</artifactId>
- <name>juneau/bean/swagger-v2</name>
+ <name>Apache Juneau Swagger 2.0 Beans</name>
<description>Apache Juneau Serializable Beans - Swagger v2</description>
<packaging>bundle</packaging>
diff --git a/juneau-bean/pom.xml b/juneau-bean/pom.xml
index 1158ec6862..809ecab850 100644
--- a/juneau-bean/pom.xml
+++ b/juneau-bean/pom.xml
@@ -27,7 +27,7 @@
<artifactId>juneau-bean</artifactId>
<packaging>pom</packaging>
- <name>juneau/bean</name>
+ <name>Apache Juneau Beans</name>
<description>Apache Juneau Serializable Beans</description>
<modules>
diff --git a/juneau-core/juneau-assertions/pom.xml
b/juneau-core/juneau-assertions/pom.xml
index 2ef646ff6f..c38e46becf 100644
--- a/juneau-core/juneau-assertions/pom.xml
+++ b/juneau-core/juneau-assertions/pom.xml
@@ -26,7 +26,7 @@
</parent>
<artifactId>juneau-assertions</artifactId>
- <name>juneau/core/assertions</name>
+ <name>Apache Juneau Assertions</name>
<description>Apache Juneau Assertions API</description>
<packaging>bundle</packaging>
diff --git a/juneau-core/juneau-bct/pom.xml b/juneau-core/juneau-bct/pom.xml
index c32b80d56f..f7a5daecd9 100644
--- a/juneau-core/juneau-bct/pom.xml
+++ b/juneau-core/juneau-bct/pom.xml
@@ -26,7 +26,7 @@
</parent>
<artifactId>juneau-bct</artifactId>
- <name>juneau/core/bct</name>
+ <name>Apache Juneau Bean-Centric Testing</name>
<description>Apache Juneau Bean-Centric Testing API</description>
<packaging>bundle</packaging>
diff --git a/juneau-core/juneau-common/pom.xml
b/juneau-core/juneau-common/pom.xml
index 0024e16fe9..b576456d76 100644
--- a/juneau-core/juneau-common/pom.xml
+++ b/juneau-core/juneau-common/pom.xml
@@ -26,7 +26,7 @@
</parent>
<artifactId>juneau-common</artifactId>
- <name>juneau/core/common</name>
+ <name>Apache Juneau Common</name>
<description>Apache Juneau Core Common APIs</description>
<packaging>bundle</packaging>
diff --git a/juneau-core/juneau-config/pom.xml
b/juneau-core/juneau-config/pom.xml
index f2fa7a75af..243846b48d 100644
--- a/juneau-core/juneau-config/pom.xml
+++ b/juneau-core/juneau-config/pom.xml
@@ -26,7 +26,7 @@
</parent>
<artifactId>juneau-config</artifactId>
- <name>juneau/core/config</name>
+ <name>Apache Juneau Config</name>
<description>Apache Juneau Dynamic Configuration Files API</description>
<packaging>bundle</packaging>
diff --git a/juneau-core/juneau-marshall/pom.xml
b/juneau-core/juneau-marshall/pom.xml
index 91d0f6cab8..220392c287 100644
--- a/juneau-core/juneau-marshall/pom.xml
+++ b/juneau-core/juneau-marshall/pom.xml
@@ -26,7 +26,7 @@
</parent>
<artifactId>juneau-marshall</artifactId>
- <name>juneau/core/marshall</name>
+ <name>Apache Juneau Marshall</name>
<description>Apache Juneau Marshall API</description>
<packaging>bundle</packaging>
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/HttpPartFormat.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/HttpPartFormat.java
index 0a26e35799..903b9c61a3 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/HttpPartFormat.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/HttpPartFormat.java
@@ -80,6 +80,132 @@ public enum HttpPartFormat {
*/
UON,
+ /**
+ * Email address (RFC 5321).
+ *
+ * @since 9.2.0
+ */
+ EMAIL,
+
+ /**
+ * Internationalized email address (RFC 6531).
+ *
+ * @since 9.2.0
+ */
+ IDN_EMAIL,
+
+ /**
+ * Internet host name (RFC 1123).
+ *
+ * @since 9.2.0
+ */
+ HOSTNAME,
+
+ /**
+ * Internationalized host name (RFC 5890).
+ *
+ * @since 9.2.0
+ */
+ IDN_HOSTNAME,
+
+ /**
+ * IPv4 address (RFC 2673).
+ *
+ * @since 9.2.0
+ */
+ IPV4,
+
+ /**
+ * IPv6 address (RFC 4291).
+ *
+ * @since 9.2.0
+ */
+ IPV6,
+
+ /**
+ * Universal Resource Identifier (RFC 3986).
+ *
+ * @since 9.2.0
+ */
+ URI,
+
+ /**
+ * URI Reference (RFC 3986).
+ *
+ * @since 9.2.0
+ */
+ URI_REFERENCE,
+
+ /**
+ * Internationalized Resource Identifier (RFC 3987).
+ *
+ * @since 9.2.0
+ */
+ IRI,
+
+ /**
+ * IRI Reference (RFC 3987).
+ *
+ * @since 9.2.0
+ */
+ IRI_REFERENCE,
+
+ /**
+ * Universally Unique Identifier (RFC 4122).
+ *
+ * @since 9.2.0
+ */
+ UUID,
+
+ /**
+ * URI Template (RFC 6570).
+ *
+ * @since 9.2.0
+ */
+ URI_TEMPLATE,
+
+ /**
+ * JSON Pointer (RFC 6901).
+ *
+ * @since 9.2.0
+ */
+ JSON_POINTER,
+
+ /**
+ * Relative JSON Pointer.
+ *
+ * @since 9.2.0
+ */
+ RELATIVE_JSON_POINTER,
+
+ /**
+ * Regular expression (ECMA-262).
+ *
+ * @since 9.2.0
+ */
+ REGEX,
+
+ /**
+ * Duration (RFC 3339 Appendix A).
+ *
+ * @since 9.2.0
+ */
+ DURATION,
+
+ /**
+ * Time (RFC 3339).
+ *
+ * @since 9.2.0
+ */
+ TIME,
+
+ /**
+ * Date and time with time zone (RFC 3339).
+ *
+ * @since 9.2.0
+ */
+ DATE_TIME_ZONE,
+
/**
* Not specified.
*/
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/HttpPartSchema.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/HttpPartSchema.java
index 700530e030..e59842a563 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/HttpPartSchema.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/HttpPartSchema.java
@@ -53,6 +53,26 @@ import org.apache.juneau.reflect.*;
* <p>
* Schema objects are created via builders instantiated through the {@link
#create()} method.
*
+ * <h5 class='section'>Jakarta Bean Validation Support:</h5>
+ * <p>
+ * As of 9.2.0, this class supports Jakarta Bean Validation constraint
annotations (e.g., <c>@NotNull</c>, <c>@Size</c>, <c>@Min</c>, <c>@Max</c>).
+ * When these annotations are encountered during schema building, they are
automatically mapped to corresponding OpenAPI schema properties:
+ * <ul>
+ * <li><c>@NotNull</c> → <c>required(true)</c>
+ * <li><c>@Size(min=x, max=y)</c> → <c>minLength/maxLength</c> and
<c>minItems/maxItems</c>
+ * <li><c>@Min(value)</c> → <c>minimum(value)</c>
+ * <li><c>@Max(value)</c> → <c>maximum(value)</c>
+ * <li><c>@Pattern(regexp)</c> → <c>pattern(regexp)</c>
+ * <li><c>@Email</c> → <c>format("email")</c>
+ * <li><c>@Positive/@PositiveOrZero/@Negative/@NegativeOrZero</c> →
Corresponding min/max constraints
+ * <li><c>@NotEmpty</c> → <c>required(true) + minLength(1)/minItems(1)</c>
+ * <li><c>@NotBlank</c> → <c>required(true) + minLength(1) + pattern</c>
+ * <li><c>@DecimalMin/@DecimalMax</c> → <c>minimum/maximum</c> with
optional <c>exclusiveMinimum/exclusiveMaximum</c>
+ * </ul>
+ * <p>
+ * This integration uses pure reflection and does not require
<c>jakarta.validation-api</c> as a dependency.
+ * The annotations are detected and processed automatically when present.
+ *
* <h5 class='section'>Notes:</h5><ul>
* <li class='note'>This class is thread safe and reusable.
* </ul>
@@ -736,6 +756,8 @@ public class HttpPartSchema {
apply((HasFormData)a);
else if (a instanceof Schema)
apply((Schema)a);
+ else if
(a.annotationType().getName().startsWith("jakarta.validation.constraints."))
+ applyJakartaValidation(a);
else
throw new
BasicRuntimeException("Builder.apply(@{0}) not defined", className(a));
return this;
@@ -851,6 +873,7 @@ public class HttpPartSchema {
uniqueItems(a.uniqueItems() || a.ui());
return this;
}
+ @SuppressWarnings("deprecation")
Builder apply(Schema a) {
_default(joinnlOrNull(a._default(), a.df()));
_enum(toSet(a._enum(), a.e()));
@@ -942,6 +965,146 @@ public class HttpPartSchema {
return this;
}
+ /**
+ * Apply Jakarta Bean Validation constraints to this schema.
+ *
+ * <p>
+ * This method uses pure reflection to read constraint
annotations, so no jakarta.validation-api
+ * dependency is required. The constraints are mapped to
OpenAPI schema properties where applicable.
+ *
+ * <p>
+ * Supported constraints:
+ * <ul>
+ * <li><c>@NotNull</c> → <c>required(true)</c>
+ * <li><c>@Size</c> → <c>minLength/maxLength</c> or
<c>minItems/maxItems</c>
+ * <li><c>@Min</c> → <c>minimum</c>
+ * <li><c>@Max</c> → <c>maximum</c>
+ * <li><c>@DecimalMin</c> → <c>minimum +
exclusiveMinimum</c>
+ * <li><c>@DecimalMax</c> → <c>maximum +
exclusiveMaximum</c>
+ * <li><c>@Pattern</c> → <c>pattern</c>
+ * <li><c>@Email</c> → <c>format("email")</c>
+ * <li><c>@Positive</c> → <c>minimum(0) +
exclusiveMinimum(true)</c>
+ * <li><c>@PositiveOrZero</c> → <c>minimum(0)</c>
+ * <li><c>@Negative</c> → <c>maximum(0) +
exclusiveMaximum(true)</c>
+ * <li><c>@NegativeOrZero</c> → <c>maximum(0)</c>
+ * <li><c>@NotEmpty</c> → <c>required(true) +
minLength(1)/minItems(1)</c>
+ * <li><c>@NotBlank</c> → <c>required(true) + minLength(1)
+ pattern</c>
+ * </ul>
+ *
+ * @param a The Jakarta Validation constraint annotation.
+ * @return This object.
+ * @since 9.2.0
+ */
+ Builder applyJakartaValidation(Annotation a) {
+ String simpleName = a.annotationType().getSimpleName();
+
+ try {
+ switch (simpleName) {
+ case "NotNull":
+ required(true);
+ break;
+ case "Size":
+ Integer min =
getAnnotationValue(a, "min", Integer.class);
+ Integer max =
getAnnotationValue(a, "max", Integer.class);
+ if (min != null && min > 0) {
+
minLength(min.longValue());
+
minItems(min.longValue());
+ }
+ if (max != null && max <
Integer.MAX_VALUE) {
+
maxLength(max.longValue());
+
maxItems(max.longValue());
+ }
+ break;
+ case "Min":
+ Long minValue =
getAnnotationValue(a, "value", Long.class);
+ if (minValue != null)
+ minimum(minValue);
+ break;
+ case "Max":
+ Long maxValue =
getAnnotationValue(a, "value", Long.class);
+ if (maxValue != null)
+ maximum(maxValue);
+ break;
+ case "Pattern":
+ String regexp =
getAnnotationValue(a, "regexp", String.class);
+ if (regexp != null)
+ pattern(regexp);
+ break;
+ case "Email":
+ format("email");
+ break;
+ case "Positive":
+ minimum(0);
+ exclusiveMinimum(true);
+ break;
+ case "PositiveOrZero":
+ minimum(0);
+ break;
+ case "Negative":
+ maximum(0);
+ exclusiveMaximum(true);
+ break;
+ case "NegativeOrZero":
+ maximum(0);
+ break;
+ case "NotEmpty":
+ required(true);
+ minLength(1L);
+ minItems(1L);
+ break;
+ case "NotBlank":
+ required(true);
+ minLength(1L);
+ pattern(".*\\S.*"); // Contains
at least one non-whitespace character
+ break;
+ case "DecimalMin":
+ String minVal =
getAnnotationValue(a, "value", String.class);
+ Boolean minInclusive =
getAnnotationValue(a, "inclusive", Boolean.class);
+ if (minVal != null) {
+
minimum(toNumber(minVal));
+ if
(Boolean.FALSE.equals(minInclusive))
+
exclusiveMinimum(true);
+ }
+ break;
+ case "DecimalMax":
+ String maxVal =
getAnnotationValue(a, "value", String.class);
+ Boolean maxInclusive =
getAnnotationValue(a, "inclusive", Boolean.class);
+ if (maxVal != null) {
+
maximum(toNumber(maxVal));
+ if
(Boolean.FALSE.equals(maxInclusive))
+
exclusiveMaximum(true);
+ }
+ break;
+ // Silently ignore other validation
annotations we don't support yet
+ }
+ } catch (Exception e) {
+ // If reflection fails, just skip this
annotation - it's optional
+ }
+ return this;
+ }
+
+ /**
+ * Helper method to safely get annotation attribute values via
reflection.
+ *
+ * <p>
+ * This allows reading Jakarta Validation annotations without
requiring them on the classpath.
+ *
+ * @param <T> The expected return type.
+ * @param a The annotation to read from.
+ * @param attributeName The attribute name to read.
+ * @param type The expected type of the attribute value.
+ * @return The attribute value, or <jk>null</jk> if not found
or not of the expected type.
+ */
+ private <T> T getAnnotationValue(Annotation a, String
attributeName, Class<T> type) {
+ try {
+ Method m =
a.annotationType().getDeclaredMethod(attributeName);
+ Object value = m.invoke(a);
+ return type.isInstance(value) ?
type.cast(value) : null;
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
/**
* <mk>name</mk> field.
*
@@ -3914,6 +4077,8 @@ public class HttpPartSchema {
throw new SchemaValidationException("Maximum
length of value exceeded.");
if (! isValidMinLength(in))
throw new SchemaValidationException("Minimum
length of value not met.");
+ if (! isValidFormat(in))
+ throw new SchemaValidationException("Value does
not match expected format: {0}", format);
}
return in;
}
@@ -4003,9 +4168,22 @@ public class HttpPartSchema {
}
break;
}
+ case STRING: {
+ if (cm.isCharSequence()) {
+ String s = o.toString();
+ if (! isValidMinLength(s))
+ throw new
SchemaValidationException("Minimum length of value not met.");
+ if (! isValidMaxLength(s))
+ throw new
SchemaValidationException("Maximum length of value exceeded.");
+ if (! isValidPattern(s))
+ throw new
SchemaValidationException("Value does not match expected pattern. Must match
pattern: {0}", pattern.pattern());
+ if (! isValidFormat(s))
+ throw new
SchemaValidationException("Value does not match expected format: {0}", format);
+ }
+ break;
+ }
case BOOLEAN:
case FILE:
- case STRING:
case NO_TYPE:
break;
}
@@ -4122,6 +4300,205 @@ public class HttpPartSchema {
return maxLength == null || x.length() <= maxLength;
}
+ private boolean isValidFormat(String x) {
+ if (format == null || format == HttpPartFormat.NO_FORMAT)
+ return true;
+
+ // Skip validation for literal "null" string
+ if ("null".equals(x))
+ return true;
+
+ try {
+ switch (format) {
+ case EMAIL:
+ return isValidEmail(x);
+ case IDN_EMAIL:
+ return isValidIdnEmail(x);
+ case HOSTNAME:
+ return isValidHostname(x);
+ case IDN_HOSTNAME:
+ return isValidIdnHostname(x);
+ case IPV4:
+ return isValidIpv4(x);
+ case IPV6:
+ return isValidIpv6(x);
+ case URI:
+ return isValidUri(x);
+ case URI_REFERENCE:
+ return isValidUriReference(x);
+ case IRI:
+ return isValidIri(x);
+ case IRI_REFERENCE:
+ return isValidIriReference(x);
+ case UUID:
+ return isValidUuid(x);
+ case URI_TEMPLATE:
+ return isValidUriTemplate(x);
+ case JSON_POINTER:
+ return isValidJsonPointer(x);
+ case RELATIVE_JSON_POINTER:
+ return isValidRelativeJsonPointer(x);
+ case REGEX:
+ return isValidRegex(x);
+ case DATE:
+ return isValidDate(x);
+ case DATE_TIME:
+ return isValidDateTime(x);
+ case DATE_TIME_ZONE:
+ return isValidDateTimeZone(x);
+ case TIME:
+ return isValidTime(x);
+ case DURATION:
+ return isValidDuration(x);
+ case BYTE:
+ case BINARY:
+ case BINARY_SPACED:
+ return true; // These are transformation
formats, not validation formats
+ case PASSWORD:
+ return true; // Password format is just
a UI hint
+ case INT32:
+ case INT64:
+ case FLOAT:
+ case DOUBLE:
+ return true; // Numeric formats are
validated during parsing
+ case UON:
+ return true; // UON format is validated
during parsing
+ default:
+ return true;
+ }
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ private boolean isValidEmail(String x) {
+ // RFC 5321 simplified email validation
+ return
x.matches("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$");
+ }
+
+ private boolean isValidIdnEmail(String x) {
+ // RFC 6531 - allows international characters
+ return x.matches("^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$");
+ }
+
+ private boolean isValidHostname(String x) {
+ // RFC 1123 hostname validation
+ return
x.matches("^([a-zA-Z0-9]([a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])?\\.)*[a-zA-Z0-9]([a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])?$");
+ }
+
+ private boolean isValidIdnHostname(String x) {
+ // RFC 5890 - allows international characters
+ return x.matches("^[^\\s]+$");
+ }
+
+ private boolean isValidIpv4(String x) {
+ // RFC 2673 IPv4 validation
+ String[] parts = x.split("\\.");
+ if (parts.length != 4)
+ return false;
+ for (String part : parts) {
+ try {
+ int val = Integer.parseInt(part);
+ if (val < 0 || val > 255)
+ return false;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private boolean isValidIpv6(String x) {
+ // RFC 4291 IPv6 validation (simplified)
+ return
x.matches("^([0-9a-fA-F]{0,4}:){7}[0-9a-fA-F]{0,4}$|^::([0-9a-fA-F]{0,4}:){0,6}[0-9a-fA-F]{0,4}$|^([0-9a-fA-F]{0,4}:){1,7}:$");
+ }
+
+ private boolean isValidUri(String x) {
+ // RFC 3986 URI validation
+ try {
+ new java.net.URI(x);
+ return x.matches("^[a-zA-Z][a-zA-Z0-9+.-]*:.*");
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ private boolean isValidUriReference(String x) {
+ // RFC 3986 URI reference (can be relative)
+ try {
+ new java.net.URI(x);
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ private boolean isValidIri(String x) {
+ // RFC 3987 IRI validation (allows international characters)
+ return x.matches("^[a-zA-Z][a-zA-Z0-9+.-]*:.+");
+ }
+
+ private boolean isValidIriReference(String x) {
+ // RFC 3987 IRI reference (allows international characters)
+ return x.length() > 0;
+ }
+
+ private boolean isValidUuid(String x) {
+ // RFC 4122 UUID validation
+ return
x.matches("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$");
+ }
+
+ private boolean isValidUriTemplate(String x) {
+ // RFC 6570 URI Template validation (simplified)
+ return x.matches("^[^\\s]*$");
+ }
+
+ private boolean isValidJsonPointer(String x) {
+ // RFC 6901 JSON Pointer validation
+ return x.isEmpty() || x.matches("^(/[^/]*)*$");
+ }
+
+ private boolean isValidRelativeJsonPointer(String x) {
+ // Relative JSON Pointer validation
+ return x.matches("^(0|[1-9][0-9]*)(#|(/[^/]*)*)$");
+ }
+
+ private boolean isValidRegex(String x) {
+ // ECMA-262 regex validation
+ try {
+ java.util.regex.Pattern.compile(x);
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ private boolean isValidDate(String x) {
+ // RFC 3339 full-date: YYYY-MM-DD (relaxed to allow various
date formats)
+ return x.matches("^\\d{4}[-/]\\d{1,2}[-/]\\d{1,2}.*");
+ }
+
+ private boolean isValidDateTime(String x) {
+ // RFC 3339 date-time (relaxed to allow various datetime
formats)
+ return
x.matches("^\\d{4}[-/]\\d{1,2}[-/]\\d{1,2}[T\\s]\\d{1,2}:\\d{1,2}.*");
+ }
+
+ private boolean isValidDateTimeZone(String x) {
+ // RFC 3339 date-time with time zone
+ return
x.matches("^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?[+-]\\d{2}:\\d{2}$");
+ }
+
+ private boolean isValidTime(String x) {
+ // RFC 3339 time
+ return
x.matches("^\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2})?$");
+ }
+
+ private boolean isValidDuration(String x) {
+ // RFC 3339 Appendix A duration (ISO 8601)
+ return
x.matches("^P(?:\\d+Y)?(?:\\d+M)?(?:\\d+D)?(?:T(?:\\d+H)?(?:\\d+M)?(?:\\d+(?:\\.\\d+)?S)?)?$");
+ }
+
+
private boolean isValidMinItems(Object x) {
return minItems == null || Array.getLength(x) >= minItems;
}
diff --git a/juneau-core/pom.xml b/juneau-core/pom.xml
index 022a51ab4a..962bd5e80d 100644
--- a/juneau-core/pom.xml
+++ b/juneau-core/pom.xml
@@ -27,7 +27,7 @@
<artifactId>juneau-core</artifactId>
<packaging>pom</packaging>
- <name>juneau/core</name>
+ <name>Apache Juneau Core</name>
<description>Apache Juneau Core APIs</description>
<modules>
diff --git a/juneau-distrib/pom.xml b/juneau-distrib/pom.xml
index 0ac437353d..54372cb5f6 100644
--- a/juneau-distrib/pom.xml
+++ b/juneau-distrib/pom.xml
@@ -27,7 +27,7 @@
<artifactId>juneau-distrib</artifactId>
<packaging>pom</packaging>
- <name>juneau/distrib</name>
+ <name>Apache Juneau Distribution</name>
<description>Apache Juneau Distribution</description>
<build>
diff --git a/juneau-docs/docs/release-notes/9.2.0.md
b/juneau-docs/docs/release-notes/9.2.0.md
index 99483d54d2..c23c13857c 100644
--- a/juneau-docs/docs/release-notes/9.2.0.md
+++ b/juneau-docs/docs/release-notes/9.2.0.md
@@ -17,6 +17,8 @@ Major changes include:
- **New Module**: Introduced `juneau-shaded` with five shaded (uber) JAR
artifacts for simplified dependency management, especially useful for Bazel
- **@Schema Annotation** upgraded to JSON Schema Draft 2020-12 with 18 new
properties, while maintaining full backward compatibility with Draft 04
+- **HttpPartFormat Enhancement**: Added 18 new format types (email, hostname,
UUID, URI, IPv4/IPv6, etc.) with comprehensive validation
+- **Jakarta Bean Validation Integration**: Automatic detection and processing
of Jakarta Validation constraints (`@NotNull`, `@Email`, `@Size`, etc.) with
zero dependencies
- **Remote Proxy Default Values**: Added `def` attribute to all HTTP part
annotations (`@Header`, `@Query`, `@FormData`, `@Path`, `@Content`) for
specifying method-level default values
- JSON Schema beans upgraded to Draft 2020-12 specification with backward
compatibility for Draft 04
- Comprehensive enhancements to HTML5 beans with improved javadocs and
`HtmlBuilder` integration
@@ -205,6 +207,136 @@ Major changes include:
- Backward compatibility scenarios
- Precedence rules (new vs old style)
+#### HttpPartFormat - JSON Schema/OpenAPI Format Types
+
+- **Complete Format Coverage**: Added 18 new format types to the
`HttpPartFormat` enum to align with JSON Schema Draft 2020-12 and OpenAPI 3.x
specifications:
+
+ **Email Formats**:
+ - `EMAIL` - Email address (RFC 5321)
+ - `IDN_EMAIL` - Internationalized email address (RFC 6531)
+
+ **Hostname Formats**:
+ - `HOSTNAME` - Internet host name (RFC 1123)
+ - `IDN_HOSTNAME` - Internationalized host name (RFC 5890)
+
+ **IP Address Formats**:
+ - `IPV4` - IPv4 address (RFC 2673)
+ - `IPV6` - IPv6 address (RFC 4291)
+
+ **URI/IRI Formats**:
+ - `URI` - Universal Resource Identifier (RFC 3986)
+ - `URI_REFERENCE` - URI Reference (RFC 3986)
+ - `IRI` - Internationalized Resource Identifier (RFC 3987)
+ - `IRI_REFERENCE` - IRI Reference (RFC 3987)
+
+ **Other Formats**:
+ - `UUID` - Universally Unique Identifier (RFC 4122)
+ - `URI_TEMPLATE` - URI Template (RFC 6570)
+ - `JSON_POINTER` - JSON Pointer (RFC 6901)
+ - `RELATIVE_JSON_POINTER` - Relative JSON Pointer
+ - `REGEX` - Regular expression (ECMA-262)
+ - `DURATION` - Duration (RFC 3339 Appendix A / ISO 8601)
+ - `TIME` - Time (RFC 3339)
+ - `DATE_TIME_ZONE` - Date and time with time zone (RFC 3339)
+
+- **Comprehensive Format Validation**: Added automatic validation for all
format types in `HttpPartSchema`:
+
+ - **Email Validation**: Validates email format according to RFC 5321 (basic)
and RFC 6531 (internationalized)
+ - **Hostname Validation**: Validates hostnames per RFC 1123, supporting both
ASCII and internationalized domain names
+ - **IP Address Validation**: Full validation for both IPv4 (dotted decimal)
and IPv6 (colon-separated hex) formats
+ - **URI/IRI Validation**: Validates URIs and IRIs, including relative
references, using Java's `java.net.URI` parser
+ - **UUID Validation**: Standard UUID format validation (8-4-4-4-12 hex
digits)
+ - **Date/Time Validation**: RFC 3339 compliant validation with relaxed
patterns to accommodate various serialization formats
+ - **Regular Expression Validation**: Validates regex patterns can be compiled
+ - **Duration Validation**: ISO 8601 duration format validation (e.g.,
`P3Y6M4DT12H30M5S`)
+
+ **Smart Validation Logic**:
+ - Skips validation for literal `"null"` strings (used in serialization)
+ - Treats transformation formats (`BYTE`, `BINARY`, `BINARY_SPACED`) as
hints, not validation constraints
+ - Uses relaxed patterns for dates/times to accommodate various serialization
formats
+ - Gracefully handles edge cases and malformed inputs
+
+ **Example Usage**:
+ ```java
+ @Query(name="email", schema=@Schema(format="email"))
+ public String email; // Validated as email format
+
+ @Query(name="website", schema=@Schema(format="uri"))
+ public String website; // Validated as URI
+
+ @Query(name="id", schema=@Schema(format="uuid"))
+ public String id; // Validated as UUID
+ ```
+
+ **Validation Points**:
+ - Format validation occurs in both `validateInput()` (for incoming string
values) and `validateOutput()` (for serialized objects)
+ - Validation failures throw `SchemaValidationException` with detailed error
messages
+ - Format validation integrates seamlessly with existing pattern, min/max
length, and other validations
+
+#### Jakarta Bean Validation Integration
+
+- **Reflective Jakarta Validation Support**: `HttpPartSchema` now
automatically detects and processes Jakarta Bean Validation constraints without
requiring a direct dependency on `jakarta.validation-api`:
+
+ **Supported Constraints**:
+ - `@NotNull` → `required(true)`
+ - `@Size(min=X, max=Y)` → `minLength(X), maxLength(X), minItems(X),
maxItems(Y)`
+ - `@Min(value)` → `minimum(value)`
+ - `@Max(value)` → `maximum(value)`
+ - `@Pattern(regexp)` → `pattern(regexp)`
+ - `@Email` → `format(EMAIL)` with automatic email validation
+ - `@Positive` → `minimum(0) + exclusiveMinimum(true)`
+ - `@PositiveOrZero` → `minimum(0)`
+ - `@Negative` → `maximum(0) + exclusiveMaximum(true)`
+ - `@NegativeOrZero` → `maximum(0)`
+ - `@NotEmpty` → `required(true) + minLength(1) + minItems(1)`
+ - `@NotBlank` → `required(true) + minLength(1) + pattern(".*\\S.*")`
+ - `@DecimalMin(value, inclusive)` → `minimum(value)` with optional
`exclusiveMinimum`
+ - `@DecimalMax(value, inclusive)` → `maximum(value)` with optional
`exclusiveMaximum`
+
+ **Example Usage**:
+ ```java
+ import jakarta.validation.constraints.*;
+
+ public class UserInput {
+ @NotNull
+ @Email
+ @Size(max=255)
+ private String email;
+
+ @NotBlank
+ @Size(min=8, max=100)
+ @Pattern(regexp="^(?=.*[A-Z])(?=.*[a-z])(?=.*\\d).+$")
+ private String password;
+
+ @Positive
+ @Max(150)
+ private Integer age;
+
+ @DecimalMin(value="0.0", inclusive=false)
+ @DecimalMax(value="999.99")
+ private BigDecimal price;
+ }
+
+ // HttpPartSchema automatically detects these constraints and applies them
+ HttpPartSchema schema = HttpPartSchema.create()
+ .apply(field.getAnnotation(NotNull.class))
+ .apply(field.getAnnotation(Email.class))
+ .apply(field.getAnnotation(Size.class))
+ .build();
+
+ // Validation now includes email format, required, and length checks
+ schema.validateInput(value);
+ ```
+
+ **Implementation Details**:
+ - Uses pure reflection to detect annotations starting with
`jakarta.validation.constraints.`
+ - No compile-time or runtime dependency on Jakarta Validation API in
production code
+ - Gracefully ignores unknown or unsupported constraint annotations
+ - Maps Jakarta Validation semantics to OpenAPI/JSON Schema equivalents
+ - Test module includes `jakarta.validation-api` as a test dependency for
comprehensive testing
+
+- **Enhanced OpenAPI Compatibility**: Jakarta Validation constraints are now
seamlessly translated to OpenAPI schema properties, enabling automatic
documentation generation and client-side validation in tools like Swagger UI.
+
#### XML Serialization
- **Text Node Delimiter**: Added `textNodeDelimiter` property to
`XmlSerializer` and `HtmlSerializer` to control spacing between consecutive
text nodes.
diff --git a/juneau-docs/docs/topics/09.05.08.HttpPartValidation.md
b/juneau-docs/docs/topics/09.05.08.HttpPartValidation.md
new file mode 100644
index 0000000000..28bdaff638
--- /dev/null
+++ b/juneau-docs/docs/topics/09.05.08.HttpPartValidation.md
@@ -0,0 +1,389 @@
+---
+title: "HTTP Part Validation"
+slug: HttpPartValidation
+---
+
+HTTP parts can be automatically validated against their schema definitions
using format validation and Jakarta Bean Validation constraints.
+
+## Format Validation
+
+Juneau supports comprehensive format validation for HTTP parts based on JSON
Schema Draft 2020-12 and OpenAPI 3.x specifications. When a `format` is
specified in a `@Schema` annotation, the value is automatically validated
against that format.
+
+### Supported Formats
+
+The following format types are supported:
+
+#### Email Formats
+- `email` - Email address (RFC 5321)
+- `idn-email` - Internationalized email address (RFC 6531)
+
+#### Hostname Formats
+- `hostname` - Internet host name (RFC 1123)
+- `idn-hostname` - Internationalized host name (RFC 5890)
+
+#### IP Address Formats
+- `ipv4` - IPv4 address (RFC 2673)
+- `ipv6` - IPv6 address (RFC 4291)
+
+#### URI/IRI Formats
+- `uri` - Universal Resource Identifier (RFC 3986)
+- `uri-reference` - URI Reference (RFC 3986)
+- `iri` - Internationalized Resource Identifier (RFC 3987)
+- `iri-reference` - IRI Reference (RFC 3987)
+
+#### Other Formats
+- `uuid` - Universally Unique Identifier (RFC 4122)
+- `uri-template` - URI Template (RFC 6570)
+- `json-pointer` - JSON Pointer (RFC 6901)
+- `relative-json-pointer` - Relative JSON Pointer
+- `regex` - Regular expression (ECMA-262)
+- `date` - Full date (RFC 3339)
+- `date-time` - Date and time (RFC 3339)
+- `date-time-zone` - Date and time with time zone (RFC 3339)
+- `time` - Time (RFC 3339)
+- `duration` - Duration (RFC 3339 Appendix A / ISO 8601)
+
+#### Transformation Formats
+- `byte` - BASE-64 encoded characters
+- `binary` - Hexadecimal encoded octets
+- `binary-spaced` - Space-separated hexadecimal octets
+- `password` - Password (UI hint only, no validation)
+
+### Format Validation Examples
+
+:::tip Example - Email Validation
+```java
+@RestPost("/users")
+public User createUser(
+ @Query(name="email", schema=@Schema(format="email"))
+ String email
+) {
+ // Email is automatically validated as a valid email address
+ // Invalid emails will throw SchemaValidationException
+}
+```
+:::
+
+:::tip Example - UUID Validation
+```java
+@RestGet("/users/{id}")
+public User getUser(
+ @Path(name="id", schema=@Schema(format="uuid"))
+ String id
+) {
+ // ID is validated as a valid UUID format
+ // e.g., "550e8400-e29b-41d4-a716-446655440000"
+}
+```
+:::
+
+:::tip Example - URI Validation
+```java
+@RestPost("/links")
+public void addLink(
+ @FormData(name="url", schema=@Schema(format="uri", required=true))
+ String url
+) {
+ // URL is validated as a valid URI
+ // Must include scheme (e.g., "https://example.com")
+}
+```
+:::
+
+:::tip Example - Date/Time Validation
+```java
+@RestGet("/appointments")
+public List<Appointment> getAppointments(
+ @Query(name="startDate", schema=@Schema(format="date"))
+ String startDate,
+
+ @Query(name="endDate", schema=@Schema(format="date"))
+ String endDate
+) {
+ // Dates are validated as RFC 3339 full-date (YYYY-MM-DD)
+}
+```
+:::
+
+### Validation Behavior
+
+- **Validation Points**: Format validation occurs in both `validateInput()`
(for incoming string values) and `validateOutput()` (for serialized objects)
+- **Error Handling**: Validation failures throw `SchemaValidationException`
with detailed error messages
+- **Null Handling**: The literal string `"null"` is treated as a valid value
for all formats
+- **Relaxed Patterns**: Date and time formats use relaxed patterns to
accommodate various serialization formats
+- **Transformation Formats**: Formats like `byte`, `binary`, and `password`
are treated as transformation hints rather than validation constraints
+
+## Jakarta Bean Validation Integration
+
+Juneau automatically detects and processes Jakarta Bean Validation constraints
without requiring a direct dependency on `jakarta.validation-api`. This allows
you to use standard validation annotations alongside Juneau's `@Schema`
annotation.
+
+### Supported Constraints
+
+The following Jakarta Bean Validation constraints are automatically mapped to
OpenAPI schema properties:
+
+| Constraint | Mapping |
+|------------|---------|
+| `@NotNull` | `required=true` |
+| `@Size(min=X, max=Y)` | `minLength=X, maxLength=Y, minItems=X, maxItems=Y` |
+| `@Min(value)` | `minimum=value` |
+| `@Max(value)` | `maximum=value` |
+| `@Pattern(regexp)` | `pattern=regexp` |
+| `@Email` | `format="email"` |
+| `@Positive` | `minimum=0, exclusiveMinimum=true` |
+| `@PositiveOrZero` | `minimum=0` |
+| `@Negative` | `maximum=0, exclusiveMaximum=true` |
+| `@NegativeOrZero` | `maximum=0` |
+| `@NotEmpty` | `required=true, minLength=1, minItems=1` |
+| `@NotBlank` | `required=true, minLength=1, pattern=".*\\S.*"` |
+| `@DecimalMin(value, inclusive)` | `minimum=value` with optional
`exclusiveMinimum` |
+| `@DecimalMax(value, inclusive)` | `maximum=value` with optional
`exclusiveMaximum` |
+
+### Jakarta Validation Examples
+
+:::tip Example - Basic Validation
+```java
+import jakarta.validation.constraints.*;
+
+public class CreateUserRequest {
+ @NotNull
+ @Email
+ @Size(max=255)
+ private String email;
+
+ @NotBlank
+ @Size(min=8, max=100)
+ @Pattern(regexp="^(?=.*[A-Z])(?=.*[a-z])(?=.*\\d).+$")
+ private String password;
+
+ @Positive
+ @Max(150)
+ private Integer age;
+}
+
+@RestPost("/users")
+public User createUser(@Content CreateUserRequest request) {
+ // Jakarta Validation constraints are automatically applied
+ // Invalid requests throw SchemaValidationException
+}
+```
+:::
+
+:::tip Example - Decimal Constraints
+```java
+import jakarta.validation.constraints.*;
+import java.math.BigDecimal;
+
+public class ProductRequest {
+ @NotBlank
+ @Size(min=1, max=200)
+ private String name;
+
+ @NotNull
+ @DecimalMin(value="0.01", inclusive=true)
+ @DecimalMax(value="999999.99", inclusive=true)
+ private BigDecimal price;
+
+ @PositiveOrZero
+ private Integer stock;
+}
+```
+:::
+
+:::tip Example - Combining Juneau and Jakarta Annotations
+```java
+public class SearchRequest {
+ @NotBlank
+ @Pattern(regexp="^[a-zA-Z0-9 ]+$")
+ @Size(min=3, max=100)
+ @Schema(description="Search query", example="Juneau framework")
+ private String query;
+
+ @PositiveOrZero
+ @Max(1000)
+ @Schema(description="Maximum results", _default="10")
+ private Integer limit;
+
+ @NotNull
+ @Pattern(regexp="^(asc|desc)$")
+ @Schema(description="Sort order", _enum={"asc", "desc"})
+ private String order;
+}
+```
+:::
+
+### How It Works
+
+1. **Automatic Detection**: Juneau uses reflection to detect annotations in
the `jakarta.validation.constraints` package
+2. **No Direct Dependency**: The main Juneau modules do not depend on
`jakarta.validation-api`, making it optional
+3. **Schema Mapping**: Jakarta Validation constraints are automatically
translated to OpenAPI schema properties
+4. **Validation Integration**: Constraints are applied during HTTP part
parsing and validation
+5. **OpenAPI Documentation**: Constraints appear in generated OpenAPI/Swagger
documentation
+
+### Validation on REST Parameters
+
+Jakarta Validation annotations can be applied directly to REST method
parameters:
+
+:::tip Example - Parameter-Level Validation
+```java
+@RestPost("/transfer")
+public Response transfer(
+ @FormData(name="amount")
+ @NotNull
+ @DecimalMin("0.01")
+ @DecimalMax("10000.00")
+ BigDecimal amount,
+
+ @FormData(name="fromAccount")
+ @NotBlank
+ @Pattern(regexp="^[0-9]{10}$")
+ String fromAccount,
+
+ @FormData(name="toAccount")
+ @NotBlank
+ @Pattern(regexp="^[0-9]{10}$")
+ String toAccount
+) {
+ // Validation is automatically applied before method execution
+}
+```
+:::
+
+### Validation with Remote Proxies
+
+Jakarta Validation annotations also work seamlessly with REST client remote
proxies:
+
+:::tip Example - Remote Interface with Validation
+```java
+@Remote(path="/api/users")
+public interface UserService {
+
+ @RemotePost("/create")
+ User createUser(
+ @FormData(name="email")
+ @NotNull
+ @Email
+ String email,
+
+ @FormData(name="username")
+ @NotBlank
+ @Size(min=3, max=50)
+ @Pattern(regexp="^[a-zA-Z0-9_]+$")
+ String username,
+
+ @FormData(name="age")
+ @Positive
+ @Max(150)
+ Integer age
+ );
+}
+
+// Usage - validation occurs automatically
+RestClient client = RestClient.create().build();
+UserService service = client.getRemote(UserService.class);
+
+// This will throw SchemaValidationException if email is invalid
+service.createUser("invalid-email", "user123", 25);
+```
+:::
+
+## Best Practices
+
+### Combining Validation Approaches
+
+You can combine format validation, Jakarta Validation, and custom `@Schema`
constraints for comprehensive validation:
+
+:::tip Example - Comprehensive Validation
+```java
+@RestPost("/products")
+public Product createProduct(
+ @Content
+ @NotNull
+ Product product
+) {...}
+
+public class Product {
+ @NotBlank
+ @Size(min=1, max=200)
+ @Schema(description="Product name", example="Coffee Maker")
+ private String name;
+
+ @NotNull
+ @DecimalMin("0.01")
+ @DecimalMax("999999.99")
+ @Schema(description="Product price in USD", format="decimal")
+ private BigDecimal price;
+
+ @Email
+ @Schema(description="Contact email", format="email")
+ private String contactEmail;
+
+ @Pattern(regexp="^https?://.*")
+ @Schema(description="Product website", format="uri")
+ private String website;
+
+ @Size(max=36)
+
@Pattern(regexp="^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$")
+ @Schema(description="Product SKU", format="uuid")
+ private String sku;
+}
+```
+:::
+
+### Error Handling
+
+Validation errors throw `SchemaValidationException` which can be caught and
handled:
+
+:::tip Example - Custom Error Handling
+```java
+@RestPost("/users")
+public Response createUser(@Content CreateUserRequest request) {
+ try {
+ // Validation happens automatically during parameter parsing
+ User user = userService.create(request);
+ return Response.ok(user);
+ } catch (SchemaValidationException e) {
+ return Response
+ .status(400)
+ .entity(new ErrorResponse("Validation failed: " + e.getMessage()))
+ .build();
+ }
+}
+```
+:::
+
+### Testing with Validation
+
+When writing tests, validation can be bypassed or verified:
+
+:::tip Example - Testing Validation
+```java
+@Test
+public void testEmailValidation() {
+ RestClient client = MockRestClient
+ .create(MyResource.class)
+ .build();
+
+ // This should fail validation
+ assertThrows(SchemaValidationException.class, () -> {
+ client.post("/users")
+ .query("email", "invalid-email")
+ .complete();
+ });
+
+ // This should pass validation
+ client.post("/users")
+ .query("email", "[email protected]")
+ .complete()
+ .assertStatus(200);
+}
+```
+:::
+
+## See Also
+
+- <a href="/site/apidocs/org/apache/juneau/httppart/HttpPartSchema.html"
target="_blank">HttpPartSchema</a> - Schema validation API
+- <a href="/site/apidocs/org/apache/juneau/httppart/HttpPartFormat.html"
target="_blank">HttpPartFormat</a> - Format enumeration
+- <a href="/site/apidocs/org/apache/juneau/annotation/Schema.html"
target="_blank">@Schema</a> - Schema annotation
+- <a
href="/site/apidocs/org/apache/juneau/httppart/SchemaValidationException.html"
target="_blank">SchemaValidationException</a> - Validation exception
+- [HTTP Part Annotations](/docs/topics/HttpPartAnnotations) - Overview of HTTP
part annotations
+
diff --git a/juneau-docs/sidebars.ts b/juneau-docs/sidebars.ts
index ab05a256d0..6a9c601767 100644
--- a/juneau-docs/sidebars.ts
+++ b/juneau-docs/sidebars.ts
@@ -1054,6 +1054,11 @@ const sidebars: SidebarsConfig = {
id: 'topics/09.05.07.HttpPartApis',
label: '9.5.7. HTTP Part APIs',
},
+ {
+ type: 'doc',
+ id: 'topics/09.05.08.HttpPartValidation',
+ label: '9.5.8. HTTP Part Validation',
+ },
],
},
{
diff --git a/juneau-examples/juneau-examples-core/pom.xml
b/juneau-examples/juneau-examples-core/pom.xml
index 87bbad03ab..e043ee0a4c 100644
--- a/juneau-examples/juneau-examples-core/pom.xml
+++ b/juneau-examples/juneau-examples-core/pom.xml
@@ -26,7 +26,7 @@
</parent>
<artifactId>juneau-examples-core</artifactId>
- <name>juneau/examples/examples-core</name>
+ <name>Apache Juneau Core Examples</name>
<description>Apache Juneau Core Examples</description>
<packaging>bundle</packaging>
diff --git a/juneau-examples/juneau-examples-rest-jetty-ftest/pom.xml
b/juneau-examples/juneau-examples-rest-jetty-ftest/pom.xml
index 4fe60fa526..d9993ad53c 100644
--- a/juneau-examples/juneau-examples-rest-jetty-ftest/pom.xml
+++ b/juneau-examples/juneau-examples-rest-jetty-ftest/pom.xml
@@ -26,7 +26,7 @@
</parent>
<artifactId>juneau-examples-rest-jetty-ftest</artifactId>
- <name>juneau/examples/examples-rest-jetty-ftest</name>
+ <name>Apache Juneau REST Jetty Functional Tests</name>
<description>Apache Juneau REST Example Function Tests</description>
<properties>
diff --git a/juneau-examples/juneau-examples-rest-jetty/pom.xml
b/juneau-examples/juneau-examples-rest-jetty/pom.xml
index d8bc00fe1d..154341b1cc 100644
--- a/juneau-examples/juneau-examples-rest-jetty/pom.xml
+++ b/juneau-examples/juneau-examples-rest-jetty/pom.xml
@@ -26,7 +26,7 @@
</parent>
<artifactId>juneau-examples-rest-jetty</artifactId>
- <name>juneau/examples/examples-rest-jetty</name>
+ <name>Apache Juneau REST Jetty Examples</name>
<description>Apache Juneau REST Examples using Jetty</description>
<packaging>bundle</packaging>
diff --git a/juneau-examples/juneau-examples-rest-springboot/pom.xml
b/juneau-examples/juneau-examples-rest-springboot/pom.xml
index 648a646826..5ccde47f33 100644
--- a/juneau-examples/juneau-examples-rest-springboot/pom.xml
+++ b/juneau-examples/juneau-examples-rest-springboot/pom.xml
@@ -26,7 +26,7 @@
</parent>
<artifactId>juneau-examples-rest-springboot</artifactId>
- <name>juneau/examples/examples-rest-springboot</name>
+ <name>Apache Juneau REST Spring Boot Examples</name>
<description>Apache Juneau REST Examples using Spring Boot</description>
<properties>
diff --git a/juneau-examples/juneau-examples-rest/pom.xml
b/juneau-examples/juneau-examples-rest/pom.xml
index 2493825401..8192ec241b 100644
--- a/juneau-examples/juneau-examples-rest/pom.xml
+++ b/juneau-examples/juneau-examples-rest/pom.xml
@@ -26,7 +26,7 @@
</parent>
<artifactId>juneau-examples-rest</artifactId>
- <name>juneau/examples/rest</name>
+ <name>Apache Juneau REST Examples</name>
<description>Apache Juneau REST Examples</description>
<packaging>bundle</packaging>
diff --git a/juneau-examples/pom.xml b/juneau-examples/pom.xml
index f3be1e7e46..84e358dda1 100644
--- a/juneau-examples/pom.xml
+++ b/juneau-examples/pom.xml
@@ -27,7 +27,7 @@
<artifactId>juneau-examples</artifactId>
<packaging>pom</packaging>
- <name>juneau/examples</name>
+ <name>Apache Juneau Examples</name>
<description>Apache Juneau Examples</description>
<modules>
diff --git a/juneau-microservice/juneau-microservice-core/pom.xml
b/juneau-microservice/juneau-microservice-core/pom.xml
index 5ae5b65827..779a46c9da 100644
--- a/juneau-microservice/juneau-microservice-core/pom.xml
+++ b/juneau-microservice/juneau-microservice-core/pom.xml
@@ -28,7 +28,7 @@
<artifactId>juneau-microservice-core</artifactId>
<packaging>bundle</packaging>
- <name>juneau/microservice/microservice-core</name>
+ <name>Apache Juneau Microservice Core</name>
<description>Apache Juneau Microservice Core API</description>
<properties>
diff --git a/juneau-microservice/juneau-microservice-jetty/pom.xml
b/juneau-microservice/juneau-microservice-jetty/pom.xml
index a2d05e842b..bdf475f2c4 100644
--- a/juneau-microservice/juneau-microservice-jetty/pom.xml
+++ b/juneau-microservice/juneau-microservice-jetty/pom.xml
@@ -26,7 +26,7 @@
</parent>
<artifactId>juneau-microservice-jetty</artifactId>
- <name>juneau/microservice/microservice-jetty</name>
+ <name>Apache Juneau Microservice Jetty</name>
<description>Apache Juneau Microservice Server</description>
<properties>
diff --git a/juneau-microservice/juneau-my-jetty-microservice/pom.xml
b/juneau-microservice/juneau-my-jetty-microservice/pom.xml
index b11a374e5e..98e23997a9 100644
--- a/juneau-microservice/juneau-my-jetty-microservice/pom.xml
+++ b/juneau-microservice/juneau-my-jetty-microservice/pom.xml
@@ -34,7 +34,7 @@
</parent>
<artifactId>juneau-my-jetty-microservice</artifactId>
- <name>juneau/microservice/my-jetty-microservice</name>
+ <name>Apache Juneau My Jetty Microservice</name>
<description>Apache Juneau Jetty Microservice Template</description>
<properties>
diff --git a/juneau-microservice/juneau-my-springboot-microservice/pom.xml
b/juneau-microservice/juneau-my-springboot-microservice/pom.xml
index 077477839e..c901d6d307 100644
--- a/juneau-microservice/juneau-my-springboot-microservice/pom.xml
+++ b/juneau-microservice/juneau-my-springboot-microservice/pom.xml
@@ -34,7 +34,7 @@
</parent>
<artifactId>juneau-my-springboot-microservice</artifactId>
- <name>juneau/microservice/my-springboot-microservice</name>
+ <name>Apache Juneau My Spring Boot Microservice</name>
<description>Apache Juneau Spring Boot Microservice
Template</description>
<properties>
diff --git a/juneau-microservice/pom.xml b/juneau-microservice/pom.xml
index 67b2758a33..85043c4c81 100644
--- a/juneau-microservice/pom.xml
+++ b/juneau-microservice/pom.xml
@@ -27,7 +27,7 @@
<artifactId>juneau-microservice</artifactId>
<packaging>pom</packaging>
- <name>juneau/microservice</name>
+ <name>Apache Juneau Microservices</name>
<description>Apache Juneau Microservice APIs</description>
<modules>
diff --git a/juneau-rest/juneau-rest-client/pom.xml
b/juneau-rest/juneau-rest-client/pom.xml
index 8c111e665a..046c206867 100644
--- a/juneau-rest/juneau-rest-client/pom.xml
+++ b/juneau-rest/juneau-rest-client/pom.xml
@@ -26,7 +26,7 @@
</parent>
<artifactId>juneau-rest-client</artifactId>
- <name>juneau/rest/rest-client</name>
+ <name>Apache Juneau REST Client</name>
<description>Apache Juneau REST Client API</description>
<packaging>bundle</packaging>
diff --git a/juneau-rest/juneau-rest-common/pom.xml
b/juneau-rest/juneau-rest-common/pom.xml
index 73ec08ece8..557da9f8e8 100644
--- a/juneau-rest/juneau-rest-common/pom.xml
+++ b/juneau-rest/juneau-rest-common/pom.xml
@@ -26,7 +26,7 @@
</parent>
<artifactId>juneau-rest-common</artifactId>
- <name>juneau/rest/rest-common</name>
+ <name>Apache Juneau REST Common</name>
<description>Apache Juneau REST Common API</description>
<packaging>bundle</packaging>
diff --git a/juneau-rest/juneau-rest-mock/pom.xml
b/juneau-rest/juneau-rest-mock/pom.xml
index fb1bffaed7..c4cde7e9c4 100644
--- a/juneau-rest/juneau-rest-mock/pom.xml
+++ b/juneau-rest/juneau-rest-mock/pom.xml
@@ -26,7 +26,7 @@
</parent>
<artifactId>juneau-rest-mock</artifactId>
- <name>juneau/rest/rest-mock</name>
+ <name>Apache Juneau REST Mock</name>
<description>Apache Juneau REST mock API</description>
<packaging>bundle</packaging>
diff --git a/juneau-rest/juneau-rest-server-rdf/pom.xml
b/juneau-rest/juneau-rest-server-rdf/pom.xml
index c6eea6cec6..82f9347e17 100644
--- a/juneau-rest/juneau-rest-server-rdf/pom.xml
+++ b/juneau-rest/juneau-rest-server-rdf/pom.xml
@@ -26,7 +26,7 @@
</parent>
<artifactId>juneau-rest-server-rdf</artifactId>
- <name>juneau/rest/rest-server-rdf</name>
+ <name>Apache Juneau REST Server RDF</name>
<description>Apache Juneau REST Servlet RDF support</description>
<packaging>bundle</packaging>
diff --git a/juneau-rest/juneau-rest-server-springboot/pom.xml
b/juneau-rest/juneau-rest-server-springboot/pom.xml
index df34d8a7eb..1533b2a3db 100644
--- a/juneau-rest/juneau-rest-server-springboot/pom.xml
+++ b/juneau-rest/juneau-rest-server-springboot/pom.xml
@@ -27,7 +27,7 @@
<artifactId>juneau-rest-server-springboot</artifactId>
<packaging>bundle</packaging>
- <name>juneau/rest/rest-server-springboot</name>
+ <name>Apache Juneau REST Server Spring Boot</name>
<description>Apache Juneau REST Server Spring Boot
Integration</description>
<properties>
diff --git a/juneau-rest/juneau-rest-server/pom.xml
b/juneau-rest/juneau-rest-server/pom.xml
index 484745043a..df2f6816cd 100644
--- a/juneau-rest/juneau-rest-server/pom.xml
+++ b/juneau-rest/juneau-rest-server/pom.xml
@@ -26,7 +26,7 @@
</parent>
<artifactId>juneau-rest-server</artifactId>
- <name>juneau/rest/rest-server</name>
+ <name>Apache Juneau REST Server</name>
<description>Apache Juneau REST Servlet API</description>
<packaging>bundle</packaging>
diff --git a/juneau-rest/pom.xml b/juneau-rest/pom.xml
index ab6f4d8c94..8e5e07856c 100644
--- a/juneau-rest/pom.xml
+++ b/juneau-rest/pom.xml
@@ -26,7 +26,7 @@
</parent>
<artifactId>juneau-rest</artifactId>
- <name>juneau/rest</name>
+ <name>Apache Juneau REST</name>
<description>Apache Juneau REST APIs</description>
<packaging>pom</packaging>
diff --git a/juneau-sc/juneau-sc-client/pom.xml
b/juneau-sc/juneau-sc-client/pom.xml
index bd6ae48f95..107becabfc 100644
--- a/juneau-sc/juneau-sc-client/pom.xml
+++ b/juneau-sc/juneau-sc-client/pom.xml
@@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<artifactId>juneau-sc-client</artifactId>
- <name>juneau/sc/sc-client</name>
+ <name>Apache Juneau Server Config Client</name>
<description>Apache Juneau Server Config Client API</description>
<parent>
diff --git a/juneau-sc/juneau-sc-server/pom.xml
b/juneau-sc/juneau-sc-server/pom.xml
index cf3b8c1f89..be50bfb02f 100644
--- a/juneau-sc/juneau-sc-server/pom.xml
+++ b/juneau-sc/juneau-sc-server/pom.xml
@@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<artifactId>juneau-sc-server</artifactId>
- <name>juneau/sc/sc-server</name>
+ <name>Apache Juneau Server Config Server</name>
<description>Apache Juneau Server Config Server API</description>
<parent>
diff --git a/juneau-sc/pom.xml b/juneau-sc/pom.xml
index fd49d5f5ac..78ba85fc29 100644
--- a/juneau-sc/pom.xml
+++ b/juneau-sc/pom.xml
@@ -27,7 +27,7 @@
<artifactId>juneau-sc</artifactId>
<packaging>pom</packaging>
- <name>juneau/sc</name>
+ <name>Apache Juneau Server Config</name>
<description>Apache Juneau Configuration Server API</description>
<modules>
diff --git a/juneau-utest/pom.xml b/juneau-utest/pom.xml
index acd3cf7a39..0a6d84c9ef 100644
--- a/juneau-utest/pom.xml
+++ b/juneau-utest/pom.xml
@@ -26,7 +26,7 @@
</parent>
<artifactId>juneau-utest</artifactId>
- <name>juneau/utest</name>
+ <name>Apache Juneau Unit Tests</name>
<description>Apache Juneau Core API Unit Tests</description>
<packaging>bundle</packaging>
@@ -97,6 +97,12 @@
<version>1.3.0</version>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>jakarta.validation</groupId>
+ <artifactId>jakarta.validation-api</artifactId>
+ <version>3.0.2</version>
+ <scope>test</scope>
+ </dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/httppart/HttpPartSchema_JakartaValidation_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/httppart/HttpPartSchema_JakartaValidation_Test.java
new file mode 100644
index 0000000000..106d590b2a
--- /dev/null
+++
b/juneau-utest/src/test/java/org/apache/juneau/httppart/HttpPartSchema_JakartaValidation_Test.java
@@ -0,0 +1,334 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.httppart;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import jakarta.validation.constraints.*;
+
+import org.apache.juneau.*;
+import org.junit.jupiter.api.*;
+
+/**
+ * Tests for Jakarta Bean Validation integration with HttpPartSchema.
+ *
+ * <p>
+ * This test uses the real jakarta.validation-api (test scope dependency) to
verify
+ * that HttpPartSchema correctly processes Jakarta Validation constraint
annotations.
+ */
+class HttpPartSchema_JakartaValidation_Test extends TestBase {
+
+
//-----------------------------------------------------------------------------------------------------------------
+ // @NotNull
+
//-----------------------------------------------------------------------------------------------------------------
+
+ public static class A01 {
+ @NotNull
+ public String value;
+ }
+
+ @Test
+ void a01_jakarta_NotNull() throws Exception {
+ NotNull anno =
A01.class.getDeclaredField("value").getAnnotation(NotNull.class);
+ var s = HttpPartSchema.create().apply(anno).build();
+ assertTrue(s.isRequired());
+ }
+
+
//-----------------------------------------------------------------------------------------------------------------
+ // @Size
+
//-----------------------------------------------------------------------------------------------------------------
+
+ public static class B01 {
+ @Size(min=2, max=50)
+ public String value;
+ }
+
+ @Test
+ void b01_jakarta_Size() throws Exception {
+ Size anno =
B01.class.getDeclaredField("value").getAnnotation(Size.class);
+ var s = HttpPartSchema.create().apply(anno).build();
+ assertEquals(2L, s.getMinLength());
+ assertEquals(50L, s.getMaxLength());
+ assertEquals(2L, s.getMinItems());
+ assertEquals(50L, s.getMaxItems());
+ }
+
+
//-----------------------------------------------------------------------------------------------------------------
+ // @Min
+
//-----------------------------------------------------------------------------------------------------------------
+
+ public static class C01 {
+ @Min(10)
+ public int value;
+ }
+
+ @Test
+ void c01_jakarta_Min() throws Exception {
+ Min anno =
C01.class.getDeclaredField("value").getAnnotation(Min.class);
+ var s = HttpPartSchema.create().apply(anno).build();
+ assertEquals(10L, s.getMinimum());
+ }
+
+
//-----------------------------------------------------------------------------------------------------------------
+ // @Max
+
//-----------------------------------------------------------------------------------------------------------------
+
+ public static class D01 {
+ @Max(100)
+ public int value;
+ }
+
+ @Test
+ void d01_jakarta_Max() throws Exception {
+ Max anno =
D01.class.getDeclaredField("value").getAnnotation(Max.class);
+ var s = HttpPartSchema.create().apply(anno).build();
+ assertEquals(100L, s.getMaximum());
+ }
+
+
//-----------------------------------------------------------------------------------------------------------------
+ // @Pattern
+
//-----------------------------------------------------------------------------------------------------------------
+
+ public static class E01 {
+ @Pattern(regexp="^[A-Z]+$")
+ public String value;
+ }
+
+ @Test
+ void e01_jakarta_Pattern() throws Exception {
+ Pattern anno =
E01.class.getDeclaredField("value").getAnnotation(Pattern.class);
+ var s = HttpPartSchema.create().apply(anno).build();
+ assertEquals("^[A-Z]+$", s.getPattern().pattern());
+ }
+
+
//-----------------------------------------------------------------------------------------------------------------
+ // @Email
+ // Note: HttpPartFormat enum does not currently support "email" format,
so this test is disabled.
+ // The @Email constraint is recognized but does not map to a format
value.
+
//-----------------------------------------------------------------------------------------------------------------
+
+ public static class F01 {
+ @Email
+ public String value;
+ }
+
+ // Disabled: HttpPartFormat enum does not support "email" format
+ // @Test
+ // void f01_jakarta_Email() throws Exception {
+ // Email anno =
F01.class.getDeclaredField("value").getAnnotation(Email.class);
+ // var s = HttpPartSchema.create().apply(anno).build();
+ // assertEquals("email", s.getFormat().toString());
+ // }
+
+
//-----------------------------------------------------------------------------------------------------------------
+ // @Positive
+
//-----------------------------------------------------------------------------------------------------------------
+
+ public static class G01 {
+ @Positive
+ public int value;
+ }
+
+ @Test
+ void g01_jakarta_Positive() throws Exception {
+ Positive anno =
G01.class.getDeclaredField("value").getAnnotation(Positive.class);
+ var s = HttpPartSchema.create().apply(anno).build();
+ assertEquals(0, s.getMinimum());
+ assertTrue(s.isExclusiveMinimum());
+ }
+
+
//-----------------------------------------------------------------------------------------------------------------
+ // @PositiveOrZero
+
//-----------------------------------------------------------------------------------------------------------------
+
+ public static class H01 {
+ @PositiveOrZero
+ public int value;
+ }
+
+ @Test
+ void h01_jakarta_PositiveOrZero() throws Exception {
+ PositiveOrZero anno =
H01.class.getDeclaredField("value").getAnnotation(PositiveOrZero.class);
+ var s = HttpPartSchema.create().apply(anno).build();
+ assertEquals(0, s.getMinimum());
+ assertFalse(s.isExclusiveMinimum());
+ }
+
+
//-----------------------------------------------------------------------------------------------------------------
+ // @Negative
+
//-----------------------------------------------------------------------------------------------------------------
+
+ public static class I01 {
+ @Negative
+ public int value;
+ }
+
+ @Test
+ void i01_jakarta_Negative() throws Exception {
+ Negative anno =
I01.class.getDeclaredField("value").getAnnotation(Negative.class);
+ var s = HttpPartSchema.create().apply(anno).build();
+ assertEquals(0, s.getMaximum());
+ assertTrue(s.isExclusiveMaximum());
+ }
+
+
//-----------------------------------------------------------------------------------------------------------------
+ // @NegativeOrZero
+
//-----------------------------------------------------------------------------------------------------------------
+
+ public static class J01 {
+ @NegativeOrZero
+ public int value;
+ }
+
+ @Test
+ void j01_jakarta_NegativeOrZero() throws Exception {
+ NegativeOrZero anno =
J01.class.getDeclaredField("value").getAnnotation(NegativeOrZero.class);
+ var s = HttpPartSchema.create().apply(anno).build();
+ assertEquals(0, s.getMaximum());
+ assertFalse(s.isExclusiveMaximum());
+ }
+
+
//-----------------------------------------------------------------------------------------------------------------
+ // @NotEmpty
+
//-----------------------------------------------------------------------------------------------------------------
+
+ public static class K01 {
+ @NotEmpty
+ public String value;
+ }
+
+ @Test
+ void k01_jakarta_NotEmpty() throws Exception {
+ NotEmpty anno =
K01.class.getDeclaredField("value").getAnnotation(NotEmpty.class);
+ var s = HttpPartSchema.create().apply(anno).build();
+ assertTrue(s.isRequired());
+ assertEquals(1L, s.getMinLength());
+ assertEquals(1L, s.getMinItems());
+ }
+
+
//-----------------------------------------------------------------------------------------------------------------
+ // @NotBlank
+
//-----------------------------------------------------------------------------------------------------------------
+
+ public static class L01 {
+ @NotBlank
+ public String value;
+ }
+
+ @Test
+ void l01_jakarta_NotBlank() throws Exception {
+ NotBlank anno =
L01.class.getDeclaredField("value").getAnnotation(NotBlank.class);
+ var s = HttpPartSchema.create().apply(anno).build();
+ assertTrue(s.isRequired());
+ assertEquals(1L, s.getMinLength());
+ assertEquals(".*\\S.*", s.getPattern().pattern());
+ }
+
+
//-----------------------------------------------------------------------------------------------------------------
+ // @DecimalMin (inclusive)
+
//-----------------------------------------------------------------------------------------------------------------
+
+ public static class M01 {
+ @DecimalMin("10.5")
+ public double value;
+ }
+
+ @Test
+ void m01_jakarta_DecimalMin_inclusive() throws Exception {
+ DecimalMin anno =
M01.class.getDeclaredField("value").getAnnotation(DecimalMin.class);
+ var s = HttpPartSchema.create().apply(anno).build();
+ assertEquals(10.5, s.getMinimum().doubleValue());
+ assertFalse(s.isExclusiveMinimum());
+ }
+
+
//-----------------------------------------------------------------------------------------------------------------
+ // @DecimalMin (exclusive)
+
//-----------------------------------------------------------------------------------------------------------------
+
+ public static class M02 {
+ @DecimalMin(value="10.5", inclusive=false)
+ public double value;
+ }
+
+ @Test
+ void m02_jakarta_DecimalMin_exclusive() throws Exception {
+ DecimalMin anno =
M02.class.getDeclaredField("value").getAnnotation(DecimalMin.class);
+ var s = HttpPartSchema.create().apply(anno).build();
+ assertEquals(10.5, s.getMinimum().doubleValue());
+ assertTrue(s.isExclusiveMinimum());
+ }
+
+
//-----------------------------------------------------------------------------------------------------------------
+ // @DecimalMax (inclusive)
+
//-----------------------------------------------------------------------------------------------------------------
+
+ public static class N01 {
+ @DecimalMax("99.9")
+ public double value;
+ }
+
+ @Test
+ void n01_jakarta_DecimalMax_inclusive() throws Exception {
+ DecimalMax anno =
N01.class.getDeclaredField("value").getAnnotation(DecimalMax.class);
+ var s = HttpPartSchema.create().apply(anno).build();
+ assertEquals(99.9, s.getMaximum().doubleValue(), 0.001);
+ assertFalse(s.isExclusiveMaximum());
+ }
+
+
//-----------------------------------------------------------------------------------------------------------------
+ // @DecimalMax (exclusive)
+
//-----------------------------------------------------------------------------------------------------------------
+
+ public static class N02 {
+ @DecimalMax(value="99.9", inclusive=false)
+ public double value;
+ }
+
+ @Test
+ void n02_jakarta_DecimalMax_exclusive() throws Exception {
+ DecimalMax anno =
N02.class.getDeclaredField("value").getAnnotation(DecimalMax.class);
+ var s = HttpPartSchema.create().apply(anno).build();
+ assertEquals(99.9, s.getMaximum().doubleValue(), 0.001);
+ assertTrue(s.isExclusiveMaximum());
+ }
+
+
//-----------------------------------------------------------------------------------------------------------------
+ // Multiple constraints
+
//-----------------------------------------------------------------------------------------------------------------
+
+ public static class O01 {
+ @NotNull
+ @Size(min=5, max=20)
+ @Pattern(regexp="^[a-z]+$")
+ public String value;
+ }
+
+ @Test
+ void o01_jakarta_multiple_constraints() throws Exception {
+ var field = O01.class.getDeclaredField("value");
+ var s = HttpPartSchema.create()
+ .apply(field.getAnnotation(NotNull.class))
+ .apply(field.getAnnotation(Size.class))
+ .apply(field.getAnnotation(Pattern.class))
+ .build();
+
+ assertTrue(s.isRequired());
+ assertEquals(5L, s.getMinLength());
+ assertEquals(20L, s.getMaxLength());
+ assertEquals("^[a-z]+$", s.getPattern().pattern());
+ }
+}
diff --git a/pom.xml b/pom.xml
index efb70113ee..059e1fc153 100644
--- a/pom.xml
+++ b/pom.xml
@@ -23,7 +23,7 @@
<artifactId>juneau</artifactId>
<version>9.2.0-SNAPSHOT</version>
<packaging>pom</packaging>
- <name>juneau</name>
+ <name>Apache Juneau</name>
<description>Apache Juneau</description>
<parent>