.
+ */
+
+package com.unboundid.scim2.common.exceptions;
+
+import com.unboundid.scim2.common.annotations.NotNull;
+import com.unboundid.scim2.common.annotations.Nullable;
+import com.unboundid.scim2.common.messages.ErrorResponse;
+
+
+/**
+ * This class represents a SCIM exception pertaining to the
+ * {@code HTTP 413 CONTENT TOO LARGE} error response code. This exception type
+ * should be thrown when a client sends a request with a JSON body that exceeds
+ * the threshold for an acceptable payload size. This is most likely to occur
+ * when processing bulk operations that contain many different requests, but can
+ * also occur for other general endpoints.
+ *
+ *
+ * The following is an example of a ContentTooLargeException presented to a SCIM
+ * client:
+ *
+ * {
+ * "schemas": [ "urn:ietf:params:scim:api:messages:2.0:Error" ],
+ * "detail": "The size of the request exceeds the maxPayloadSize (1048576)",
+ * "status": "413"
+ * }
+ *
+ *
+ * To create the above error as an exception, use the following Java code:
+ *
+ * throw new ContentTooLargeException(
+ * "The size of the request exceeds the maxPayloadSize (" + MAX_SIZE + ")"
+ * );
+ *
+ *
+ * This exception type does not have a {@code scimType} value.
+ *
+ * @since 5.1.0
+ */
+public class ContentTooLargeException extends ScimException
+{
+ private static final int CONTENT_TOO_LARGE_HTTP_STATUS = 413;
+
+ /**
+ * Returns the {@code 413 CONTENT TOO LARGE} HTTP status code value.
+ *
+ * @return The HTTP status value.
+ */
+ public static int statusInt()
+ {
+ return CONTENT_TOO_LARGE_HTTP_STATUS;
+ }
+
+ /**
+ * Returns the {@code 413 CONTENT TOO LARGE} HTTP status code string value.
+ *
+ * @return The HTTP status value as a string.
+ */
+ @NotNull
+ public static String status()
+ {
+ return "413";
+ }
+
+ /**
+ * Create a new {@code ContentTooLargeException} with the provided message.
+ *
+ * @param errorMessage The error message for this SCIM exception.
+ */
+ public ContentTooLargeException(@Nullable final String errorMessage)
+ {
+ super(CONTENT_TOO_LARGE_HTTP_STATUS, errorMessage);
+ }
+
+ /**
+ * Create a new {@code ContentTooLargeException} from the provided
+ * information.
+ *
+ * @param errorMessage The error message for this SCIM exception.
+ * @param cause The cause (which is saved for later retrieval by the
+ * {@link #getCause()} method). A {@code null} value is
+ * permitted, and indicates that the cause is nonexistent
+ * or unknown.
+ */
+ public ContentTooLargeException(@Nullable final String errorMessage,
+ @Nullable final Throwable cause)
+ {
+ super(CONTENT_TOO_LARGE_HTTP_STATUS, null, errorMessage, cause);
+ }
+
+ /**
+ * Create a new {@code ContentTooLargeException} from the provided
+ * information.
+ *
+ * @param scimError The SCIM error response.
+ * @param cause The cause (which is saved for later retrieval by the
+ * {@link #getCause()} method). A {@code null} value is
+ * permitted, and indicates that the cause is nonexistent
+ * or unknown.
+ */
+ public ContentTooLargeException(@NotNull final ErrorResponse scimError,
+ @Nullable final Throwable cause)
+ {
+ super(scimError, cause);
+ }
+}
diff --git a/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/exceptions/ForbiddenException.java b/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/exceptions/ForbiddenException.java
index 5e0ad103..643898b7 100644
--- a/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/exceptions/ForbiddenException.java
+++ b/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/exceptions/ForbiddenException.java
@@ -66,6 +66,8 @@
*/
public class ForbiddenException extends ScimException
{
+ private static final int FORBIDDEN_HTTP_STATUS = 403;
+
/**
* The SCIM detailed error keyword that indicates the provided filter in a
* GET search request contained sensitive or confidential information. See
@@ -74,6 +76,29 @@ public class ForbiddenException extends ScimException
@NotNull
public static final String SENSITIVE = "sensitive";
+ /**
+ * Returns the {@code 403 FORBIDDEN} HTTP status code value.
+ *
+ * @return The HTTP status value.
+ * @since 5.1.0
+ */
+ public static int statusInt()
+ {
+ return FORBIDDEN_HTTP_STATUS;
+ }
+
+ /**
+ * Returns the {@code 403 FORBIDDEN} HTTP status code string value.
+ *
+ * @return The HTTP status value as a string.
+ * @since 5.1.0
+ */
+ @NotNull
+ public static String status()
+ {
+ return "403";
+ }
+
/**
* Create a new {@code ForbiddenException} from the provided information.
*
@@ -81,7 +106,7 @@ public class ForbiddenException extends ScimException
*/
public ForbiddenException(@Nullable final String errorMessage)
{
- super(403, null, errorMessage);
+ super(FORBIDDEN_HTTP_STATUS, null, errorMessage);
}
/**
@@ -99,7 +124,7 @@ public ForbiddenException(@Nullable final String errorMessage,
@Nullable final String scimType,
@Nullable final Throwable cause)
{
- super(403, scimType, errorMessage, cause);
+ super(FORBIDDEN_HTTP_STATUS, scimType, errorMessage, cause);
}
/**
diff --git a/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/exceptions/MethodNotAllowedException.java b/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/exceptions/MethodNotAllowedException.java
index 54f34683..e9b922ba 100644
--- a/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/exceptions/MethodNotAllowedException.java
+++ b/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/exceptions/MethodNotAllowedException.java
@@ -64,9 +64,32 @@
*/
public class MethodNotAllowedException extends ScimException
{
- private static final int METHOD_NOT_ALLOWED_CODE = 405;
+ private static final int METHOD_NOT_ALLOWED_HTTP_STATUS = 405;
+ /**
+ * Returns the {@code 405 METHOD NOT ALLOWED} HTTP status code value.
+ *
+ * @return The HTTP status value.
+ * @since 5.1.0
+ */
+ public static int statusInt()
+ {
+ return METHOD_NOT_ALLOWED_HTTP_STATUS;
+ }
+
+ /**
+ * Returns the {@code 405 METHOD NOT ALLOWED} HTTP status code string value.
+ *
+ * @return The HTTP status value as a string.
+ * @since 5.1.0
+ */
+ @NotNull
+ public static String status()
+ {
+ return "405";
+ }
+
/**
* Create a new {@code MethodNotAllowedException} from the provided
* information.
@@ -75,7 +98,7 @@ public class MethodNotAllowedException extends ScimException
*/
public MethodNotAllowedException(@Nullable final String errorMessage)
{
- super(METHOD_NOT_ALLOWED_CODE, errorMessage);
+ super(METHOD_NOT_ALLOWED_HTTP_STATUS, errorMessage);
}
diff --git a/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/exceptions/NotImplementedException.java b/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/exceptions/NotImplementedException.java
index 40b0269e..e3363ed8 100644
--- a/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/exceptions/NotImplementedException.java
+++ b/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/exceptions/NotImplementedException.java
@@ -67,6 +67,31 @@
*/
public class NotImplementedException extends ScimException
{
+ private static final int NOT_IMPLEMENTED_HTTP_STATUS = 501;
+
+ /**
+ * Returns the {@code 501 NOT IMPLEMENTED} HTTP status code value.
+ *
+ * @return The HTTP status value.
+ * @since 5.1.0
+ */
+ public static int statusInt()
+ {
+ return NOT_IMPLEMENTED_HTTP_STATUS;
+ }
+
+ /**
+ * Returns the {@code 501 NOT IMPLEMENTED} HTTP status code string value.
+ *
+ * @return The HTTP status value as a string.
+ * @since 5.1.0
+ */
+ @NotNull
+ public static String status()
+ {
+ return "501";
+ }
+
/**
* Create a new {@code NotImplementedException} from the provided information.
*
@@ -74,7 +99,7 @@ public class NotImplementedException extends ScimException
*/
public NotImplementedException(@Nullable final String errorMessage)
{
- super(501, null, errorMessage);
+ super(NOT_IMPLEMENTED_HTTP_STATUS, null, errorMessage);
}
/**
@@ -92,7 +117,7 @@ public NotImplementedException(@Nullable final String errorMessage,
@Nullable final String scimType,
@Nullable final Throwable cause)
{
- super(501, scimType, errorMessage, cause);
+ super(NOT_IMPLEMENTED_HTTP_STATUS, scimType, errorMessage, cause);
}
/**
diff --git a/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/exceptions/NotModifiedException.java b/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/exceptions/NotModifiedException.java
index c02c6029..6afb5e05 100644
--- a/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/exceptions/NotModifiedException.java
+++ b/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/exceptions/NotModifiedException.java
@@ -64,12 +64,37 @@
*/
public class NotModifiedException extends ScimException
{
+ private static final int NOT_MODIFIED_HTTP_STATUS = 304;
+
/**
* Represents the ETag version value of the resource.
*/
@Nullable
private final String version;
+ /**
+ * Returns the {@code 304 NOT MODIFIED} HTTP status code value.
+ *
+ * @return The HTTP status value.
+ * @since 5.1.0
+ */
+ public static int statusInt()
+ {
+ return NOT_MODIFIED_HTTP_STATUS;
+ }
+
+ /**
+ * Returns the {@code 304 NOT MODIFIED} HTTP status code string value.
+ *
+ * @return The HTTP status value as a string.
+ * @since 5.1.0
+ */
+ @NotNull
+ public static String status()
+ {
+ return "304";
+ }
+
/**
* Create a new {@code NotModifiedException} from the provided information.
*
@@ -77,7 +102,7 @@ public class NotModifiedException extends ScimException
*/
public NotModifiedException(@Nullable final String errorMessage)
{
- super(304, null, errorMessage);
+ super(NOT_MODIFIED_HTTP_STATUS, null, errorMessage);
version = null;
}
@@ -93,7 +118,7 @@ public NotModifiedException(@Nullable final String errorMessage)
public NotModifiedException(@Nullable final String errorMessage,
@Nullable final Throwable cause)
{
- super(304, null, errorMessage, cause);
+ super(NOT_MODIFIED_HTTP_STATUS, null, errorMessage, cause);
version = null;
}
@@ -113,7 +138,7 @@ public NotModifiedException(@Nullable final String errorMessage,
@Nullable final String version,
@Nullable final Throwable cause)
{
- super(304, scimType, errorMessage, cause);
+ super(NOT_MODIFIED_HTTP_STATUS, scimType, errorMessage, cause);
this.version = version;
}
diff --git a/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/exceptions/PreconditionFailedException.java b/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/exceptions/PreconditionFailedException.java
index 1809d673..e441f5b2 100644
--- a/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/exceptions/PreconditionFailedException.java
+++ b/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/exceptions/PreconditionFailedException.java
@@ -67,12 +67,37 @@
*/
public class PreconditionFailedException extends ScimException
{
+ private static final int PRECONDITION_FAILED_HTTP_STATUS = 412;
+
/**
* Represents the ETag version value of the resource.
*/
@Nullable
private final String version;
+ /**
+ * Returns the {@code 412 PRECONDITION FAILED} HTTP status code value.
+ *
+ * @return The HTTP status value.
+ * @since 5.1.0
+ */
+ public static int statusInt()
+ {
+ return PRECONDITION_FAILED_HTTP_STATUS;
+ }
+
+ /**
+ * Returns the {@code 412 PRECONDITION FAILED} HTTP status code string value.
+ *
+ * @return The HTTP status value as a string.
+ * @since 5.1.0
+ */
+ @NotNull
+ public static String status()
+ {
+ return "412";
+ }
+
/**
* Create a new {@code PreconditionFailedException} from the provided
* information.
@@ -81,7 +106,7 @@ public class PreconditionFailedException extends ScimException
*/
public PreconditionFailedException(@Nullable final String errorMessage)
{
- super(412, null, errorMessage);
+ super(PRECONDITION_FAILED_HTTP_STATUS, null, errorMessage);
this.version = null;
}
@@ -98,7 +123,7 @@ public PreconditionFailedException(@Nullable final String errorMessage)
public PreconditionFailedException(@Nullable final String errorMessage,
@Nullable final Throwable cause)
{
- super(412, null, errorMessage, cause);
+ super(PRECONDITION_FAILED_HTTP_STATUS, null, errorMessage, cause);
this.version = null;
}
@@ -119,7 +144,7 @@ public PreconditionFailedException(@Nullable final String errorMessage,
@Nullable final String version,
@Nullable final Throwable cause)
{
- super(412, scimType, errorMessage, cause);
+ super(PRECONDITION_FAILED_HTTP_STATUS, scimType, errorMessage, cause);
this.version = version;
}
diff --git a/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/exceptions/RateLimitException.java b/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/exceptions/RateLimitException.java
new file mode 100644
index 00000000..aaddef5b
--- /dev/null
+++ b/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/exceptions/RateLimitException.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2026 Ping Identity Corporation
+ *
+ * Licensed 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.
+ */
+/*
+ * Copyright 2026 Ping Identity Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License (GPLv2 only)
+ * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see .
+ */
+
+package com.unboundid.scim2.common.exceptions;
+
+import com.unboundid.scim2.common.annotations.NotNull;
+import com.unboundid.scim2.common.annotations.Nullable;
+import com.unboundid.scim2.common.messages.ErrorResponse;
+
+
+/**
+ * This class represents a SCIM exception pertaining to the
+ * {@code HTTP 429 TOO MANY REQUESTS} error response code. This exception type
+ * is thrown when a client exceeds a defined rate limit of an HTTP service, and
+ * occurs commonly when a client sends excessive requests within a brief period
+ * of time. By denying excessive request volumes, the service defends against
+ * expensive traffic that could affect availability. This also defends against
+ * malicious actors that attempt to overwhelm the service with attacks such as a
+ * distributed denial of service.
+ *
+ *
+ * The following is an example of a rate limit exceeded exception presented to
+ * a SCIM client:
+ *
+ * {
+ * "schemas": [ "urn:ietf:params:scim:api:messages:2.0:Error" ],
+ * "detail": "Too many requests. Please try again later.",
+ * "status": "429"
+ * }
+ *
+ *
+ * This exception can be created with the following Java code:
+ *
+ * throw new RateLimitException("Too many requests. Please try again later.");
+ *
+ *
+ * This exception type does not have a {@code scimType} value.
+ *
+ * @since 5.1.0
+ */
+public class RateLimitException extends ScimException
+{
+ private static final int TOO_MANY_REQUESTS_HTTP_STATUS = 429;
+
+ /**
+ * Returns the {@code 429 TOO MANY REQUESTS} HTTP status code value.
+ *
+ * @return The HTTP status value.
+ */
+ public static int statusInt()
+ {
+ return TOO_MANY_REQUESTS_HTTP_STATUS;
+ }
+
+ /**
+ * Returns the {@code 429 TOO MANY REQUESTS} HTTP status code string value.
+ *
+ * @return The HTTP status value as a string.
+ */
+ @NotNull
+ public static String status()
+ {
+ return "429";
+ }
+
+ /**
+ * Create a new {@code RateLimitException} from the provided information.
+ *
+ * @param errorMessage The error message for this SCIM exception.
+ */
+ public RateLimitException(@Nullable final String errorMessage)
+ {
+ super(TOO_MANY_REQUESTS_HTTP_STATUS, errorMessage);
+ }
+
+ /**
+ * Create a new {@code RateLimitException} from the provided information.
+ *
+ * @param scimError The SCIM error response.
+ * @param cause The cause (which is saved for later retrieval by the
+ * {@link #getCause()} method). A {@code null} value
+ * is permitted, and indicates that the cause is
+ * nonexistent or unknown.
+ */
+ public RateLimitException(@NotNull final ErrorResponse scimError,
+ @Nullable final Throwable cause)
+ {
+ super(scimError, cause);
+ }
+}
diff --git a/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/exceptions/ResourceConflictException.java b/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/exceptions/ResourceConflictException.java
index 81b8c336..5f6f9099 100644
--- a/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/exceptions/ResourceConflictException.java
+++ b/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/exceptions/ResourceConflictException.java
@@ -81,12 +81,37 @@
*/
public class ResourceConflictException extends ScimException
{
+ private static final int CONFLICT_HTTP_STATUS = 409;
+
/**
* The SCIM detailed error keyword that indicates a uniqueness conflict.
*/
@NotNull
public static final String UNIQUENESS = "uniqueness";
+ /**
+ * Returns the {@code 409 CONFLICT} HTTP status code value.
+ *
+ * @return The HTTP status value.
+ * @since 5.1.0
+ */
+ public static int statusInt()
+ {
+ return CONFLICT_HTTP_STATUS;
+ }
+
+ /**
+ * Returns the {@code 409 CONFLICT} HTTP status code string value.
+ *
+ * @return The HTTP status value as a string.
+ * @since 5.1.0
+ */
+ @NotNull
+ public static String status()
+ {
+ return "409";
+ }
+
/**
* Create a new {@code ResourceConflictException} from the provided
* information. This constructor sets the {@code scimType} field to
@@ -96,7 +121,7 @@ public class ResourceConflictException extends ScimException
*/
public ResourceConflictException(@Nullable final String errorMessage)
{
- super(409, null, errorMessage);
+ super(CONFLICT_HTTP_STATUS, null, errorMessage);
}
/**
@@ -110,7 +135,7 @@ public ResourceConflictException(@Nullable final String errorMessage)
public ResourceConflictException(@Nullable final String errorMessage,
@Nullable final String scimType)
{
- super(409, scimType, errorMessage);
+ super(CONFLICT_HTTP_STATUS, scimType, errorMessage);
}
/**
@@ -143,7 +168,7 @@ public ResourceConflictException(@Nullable final String errorMessage,
@Nullable final String scimType,
@Nullable final Throwable cause)
{
- super(409, scimType, errorMessage, cause);
+ super(CONFLICT_HTTP_STATUS, scimType, errorMessage, cause);
}
/**
diff --git a/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/exceptions/ResourceNotFoundException.java b/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/exceptions/ResourceNotFoundException.java
index c2d367bf..dcb4dd48 100644
--- a/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/exceptions/ResourceNotFoundException.java
+++ b/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/exceptions/ResourceNotFoundException.java
@@ -65,6 +65,31 @@
*/
public class ResourceNotFoundException extends ScimException
{
+ private static final int NOT_FOUND_HTTP_STATUS = 404;
+
+ /**
+ * Returns the {@code 404 NOT FOUND} HTTP status code value.
+ *
+ * @return The HTTP status value.
+ * @since 5.1.0
+ */
+ public static int statusInt()
+ {
+ return NOT_FOUND_HTTP_STATUS;
+ }
+
+ /**
+ * Returns the {@code 404 NOT FOUND} HTTP status code string value.
+ *
+ * @return The HTTP status value as a string.
+ * @since 5.1.0
+ */
+ @NotNull
+ public static String status()
+ {
+ return "404";
+ }
+
/**
* Create a new {@code ResourceNotFoundException} from the provided
* information.
@@ -73,7 +98,7 @@ public class ResourceNotFoundException extends ScimException
*/
public ResourceNotFoundException(@Nullable final String errorMessage)
{
- super(404, null, errorMessage);
+ super(NOT_FOUND_HTTP_STATUS, null, errorMessage);
}
/**
@@ -91,7 +116,7 @@ public ResourceNotFoundException(@Nullable final String errorMessage,
@Nullable final String scimType,
@Nullable final Throwable cause)
{
- super(404, scimType, errorMessage, cause);
+ super(NOT_FOUND_HTTP_STATUS, scimType, errorMessage, cause);
}
/**
diff --git a/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/exceptions/ScimException.java b/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/exceptions/ScimException.java
index 8f39da33..ed7ba40a 100644
--- a/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/exceptions/ScimException.java
+++ b/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/exceptions/ScimException.java
@@ -67,6 +67,8 @@
* {@code HTTP 405}: {@link MethodNotAllowedException}
* {@code HTTP 409}: {@link ResourceConflictException}
* {@code HTTP 412}: {@link PreconditionFailedException}
+ * {@code HTTP 413}: {@link ContentTooLargeException}
+ * {@code HTTP 429}: {@link RateLimitException}
* {@code HTTP 500}: {@link ServerErrorException}
* {@code HTTP 501}: {@link NotImplementedException}
*
@@ -90,7 +92,7 @@
* a dedicated class defined in the SCIM SDK, the constructors on this class may
* be used directly:
*
- * throw new ScimException(429, "Detailed error message");
+ * throw new ScimException(418, "Detailed error message");
*
*
* For more details on a particular exception type, see the class-level
@@ -263,6 +265,8 @@ public static ScimException createException(
case 405 -> new MethodNotAllowedException(scimError, cause);
case 409 -> new ResourceConflictException(scimError, cause);
case 412 -> new PreconditionFailedException(scimError, null, cause);
+ case 413 -> new ContentTooLargeException(scimError, cause);
+ case 429 -> new RateLimitException(scimError, cause);
case 500 -> new ServerErrorException(scimError, cause);
case 501 -> new NotImplementedException(scimError, cause);
default -> new ScimException(scimError, cause);
diff --git a/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/exceptions/ServerErrorException.java b/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/exceptions/ServerErrorException.java
index 8fe40ae5..b97cc415 100644
--- a/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/exceptions/ServerErrorException.java
+++ b/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/exceptions/ServerErrorException.java
@@ -62,6 +62,32 @@
*/
public class ServerErrorException extends ScimException
{
+ private static final int INTERNAL_SERVER_ERROR_HTTP_STATUS = 500;
+
+ /**
+ * Returns the {@code 500 INTERNAL SERVER ERROR} HTTP status code value.
+ *
+ * @return The HTTP status value.
+ * @since 5.1.0
+ */
+ public static int statusInt()
+ {
+ return INTERNAL_SERVER_ERROR_HTTP_STATUS;
+ }
+
+ /**
+ * Returns the {@code 500 INTERNAL SERVER ERROR} HTTP status code string
+ * value.
+ *
+ * @return The HTTP status value as a string.
+ * @since 5.1.0
+ */
+ @NotNull
+ public static String status()
+ {
+ return "500";
+ }
+
/**
* Create a new {@code ServerErrorException} from the provided information.
*
@@ -69,7 +95,7 @@ public class ServerErrorException extends ScimException
*/
public ServerErrorException(@Nullable final String errorMessage)
{
- super(500, null, errorMessage);
+ super(INTERNAL_SERVER_ERROR_HTTP_STATUS, null, errorMessage);
}
/**
@@ -86,7 +112,7 @@ public ServerErrorException(@Nullable final String errorMessage,
@Nullable final String scimType,
@Nullable final Throwable cause)
{
- super(500, scimType, errorMessage, cause);
+ super(INTERNAL_SERVER_ERROR_HTTP_STATUS, scimType, errorMessage, cause);
}
/**
diff --git a/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/exceptions/UnauthorizedException.java b/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/exceptions/UnauthorizedException.java
index 772b8fc3..8deccb8d 100644
--- a/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/exceptions/UnauthorizedException.java
+++ b/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/exceptions/UnauthorizedException.java
@@ -66,6 +66,31 @@
*/
public class UnauthorizedException extends ScimException
{
+ private static final int UNAUTHORIZED_HTTP_STATUS = 401;
+
+ /**
+ * Returns the {@code 401 UNAUTHORIZED} HTTP status code value.
+ *
+ * @return The HTTP status value.
+ * @since 5.1.0
+ */
+ public static int statusInt()
+ {
+ return UNAUTHORIZED_HTTP_STATUS;
+ }
+
+ /**
+ * Returns the {@code 401 UNAUTHORIZED} HTTP status code string value.
+ *
+ * @return The HTTP status value as a string.
+ * @since 5.1.0
+ */
+ @NotNull
+ public static String status()
+ {
+ return "401";
+ }
+
/**
* Create a new {@code UnauthorizedException} from the provided information.
*
@@ -73,7 +98,7 @@ public class UnauthorizedException extends ScimException
*/
public UnauthorizedException(@Nullable final String errorMessage)
{
- super(401, null, errorMessage);
+ super(UNAUTHORIZED_HTTP_STATUS, null, errorMessage);
}
/**
@@ -90,7 +115,7 @@ public UnauthorizedException(@Nullable final String errorMessage,
@Nullable final String scimType,
@Nullable final Throwable cause)
{
- super(401, scimType, errorMessage, cause);
+ super(UNAUTHORIZED_HTTP_STATUS, scimType, errorMessage, cause);
}
/**
diff --git a/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/types/GroupResource.java b/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/types/GroupResource.java
index cb4f85d4..cefb2fae 100644
--- a/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/types/GroupResource.java
+++ b/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/types/GroupResource.java
@@ -163,8 +163,9 @@ public GroupResource setMembers(@Nullable final List members)
* Alternate version of {@link #setMembers(List)} that accepts individual
* Member objects that are not contained in a list.
*
- * @param member The first member to add. This must not be {@code null}.
- * @param members An optional set of additional arguments. Any {@code null}
+ * @param member The first member of the group. This must not be
+ * {@code null}.
+ * @param members An optional field for additional members. Any {@code null}
* values will be ignored.
* @return This object.
*
diff --git a/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/types/ServiceProviderConfigResource.java b/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/types/ServiceProviderConfigResource.java
index 8a9aa7da..b0dd7004 100644
--- a/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/types/ServiceProviderConfigResource.java
+++ b/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/types/ServiceProviderConfigResource.java
@@ -62,7 +62,7 @@
* },
* "bulk": {
* "supported": true,
- * "maxOperations": 1000,
+ * "maxOperations": 100,
* "maxPayloadSize": 1048576
* },
* "filter": {
@@ -104,7 +104,7 @@
* The above JSON response indicates that this SCIM service:
*
* - Supports SCIM PATCH requests.
- *
- Supports SCIM bulk requests with up to 1000 operations in a request.
+ *
- Supports SCIM bulk requests with up to 100 operations in a request.
*
- Supports SCIM filtering and will return a maximum of 200 results.
*
- Supports password change API requests.
*
- Supports sorting the result set when multiple resources are returned.
diff --git a/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/utils/ApiConstants.java b/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/utils/ApiConstants.java
index 868fb5c1..f8833aec 100644
--- a/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/utils/ApiConstants.java
+++ b/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/utils/ApiConstants.java
@@ -79,6 +79,22 @@ public class ApiConstants
@NotNull
public static final String ME_ENDPOINT = "Me";
+ /**
+ * An HTTP POST to this endpoint is used as a means to consolidate multiple
+ * write requests into a single API call. For more information, see
+ * {@link com.unboundid.scim2.common.bulk.BulkRequest BulkRequest}.
+ */
+ @NotNull
+ public static final String BULK_ENDPOINT = "Bulk";
+
+ /**
+ * An identifier that may be used by a bulk operation to reference another
+ * operation within the same bulk request. For more information, see
+ * {@link com.unboundid.scim2.common.bulk.BulkOperation BulkOperation}.
+ */
+ @NotNull
+ public static final String BULK_PREFIX = "bulkId:";
+
/**
* An HTTP POST to this endpoint is used to retrieve information about
* resource schemas supported by a SCIM service provider.
diff --git a/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/utils/BulkStatusDeserializer.java b/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/utils/BulkStatusDeserializer.java
new file mode 100644
index 00000000..5faa6f29
--- /dev/null
+++ b/scim2-sdk-common/src/main/java/com/unboundid/scim2/common/utils/BulkStatusDeserializer.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2026 Ping Identity Corporation
+ *
+ * Licensed 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.
+ */
+/*
+ * Copyright 2026 Ping Identity Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License (GPLv2 only)
+ * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see .
+ */
+
+package com.unboundid.scim2.common.utils;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.unboundid.scim2.common.annotations.NotNull;
+import com.unboundid.scim2.common.annotations.Nullable;
+
+import java.io.IOException;
+
+/**
+ * Deserializer for the {@code status} field of a bulk operation contained
+ * within a bulk response. The primary form is:
+ *
+ * {
+ * "status": "200"
+ * }
+ *
+ *
+ * However, a single reference in the RFC displays the following form, which is
+ * likely an artifact from the SCIM 1.1 standard that is not used:
+ *
+ * {
+ * "status": {
+ * "code": "200"
+ * }
+ * }
+ *
+ *
+ * To ensure broader compatibility, this deserializer looks for both forms when
+ * parsing the {@code status} value. Note that when the SCIM SDK serializes
+ * objects into JSON, it always prints strings of the first form.
+ */
+public class BulkStatusDeserializer extends JsonDeserializer
+{
+ /**
+ * Implementation of the bulk status deserializer. See the class-level Javadoc
+ * for more information.
+ *
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ @NotNull
+ public String deserialize(@NotNull final JsonParser p,
+ @Nullable final DeserializationContext ctxt)
+ throws IOException
+ {
+ final JsonNode statusNode = JsonUtils.getObjectReader().readTree(p);
+
+ // Check for { "status": "200" }.
+ if (statusNode.isTextual())
+ {
+ return statusNode.asText();
+ }
+
+ // Check for the "status.code" sub-attribute.
+ JsonNode nested = statusNode.path("code");
+ if (nested.isTextual())
+ {
+ return nested.asText();
+ }
+
+ throw new IOException(
+ "Could not parse the 'status' field of the bulk operation response."
+ );
+ }
+}
diff --git a/scim2-sdk-common/src/test/java/com/unboundid/scim2/common/bulk/BulkResourceMapperTest.java b/scim2-sdk-common/src/test/java/com/unboundid/scim2/common/bulk/BulkResourceMapperTest.java
new file mode 100644
index 00000000..5e25e11a
--- /dev/null
+++ b/scim2-sdk-common/src/test/java/com/unboundid/scim2/common/bulk/BulkResourceMapperTest.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright 2026 Ping Identity Corporation
+ *
+ * Licensed 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.
+ */
+/*
+ * Copyright 2026 Ping Identity Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License (GPLv2 only)
+ * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see .
+ */
+
+package com.unboundid.scim2.common.bulk;
+
+import com.fasterxml.jackson.databind.node.NullNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.unboundid.scim2.common.BaseScimResource;
+import com.unboundid.scim2.common.GenericScimResource;
+import com.unboundid.scim2.common.ScimResource;
+import com.unboundid.scim2.common.annotations.Schema;
+import com.unboundid.scim2.common.types.GroupResource;
+import com.unboundid.scim2.common.types.UserResource;
+import com.unboundid.scim2.common.utils.JsonUtils;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.Test;
+
+import java.util.Set;
+
+import static com.unboundid.scim2.common.bulk.BulkResourceMapper.SCHEMAS_MAP;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+
+/**
+ * Unit tests for the {@link BulkResourceMapper} class. This functionality is
+ * also exercised in {@code EndpointTestCase#testBulkRequestJsonProcessing}.
+ */
+public class BulkResourceMapperTest
+{
+ /**
+ * Reset the bulk resource mapper to its default settings.
+ */
+ @AfterMethod
+ public void tearDown()
+ {
+ BulkResourceMapper.initialize();
+ }
+
+ /**
+ * Basic validation for the resource mapper.
+ */
+ @Test
+ public void testBasic()
+ {
+ // Clear the map to start.
+ BulkResourceMapper.clear();
+
+ // Call the add() method and ensure it is registered appropriately for a
+ // class defined with the @Schema annotation.
+ BulkResourceMapper.add(ClassWithAnnotation.class);
+ assertThat(SCHEMAS_MAP).hasSize(1);
+
+ // Now that the value is present in the map, querying the map for a schema
+ // list of "urn:pingidentity:example" should return the correct class.
+ Class clazz =
+ BulkResourceMapper.get(Set.of("urn:pingidentity:example"));
+ assertThat(clazz).isEqualTo(ClassWithAnnotation.class);
+
+ // Query again with a JSON node.
+ var arrayNode = JsonUtils.getJsonNodeFactory().arrayNode()
+ .add("urn:pingidentity:example");
+ clazz = BulkResourceMapper.get(arrayNode);
+ assertThat(clazz).isEqualTo(ClassWithAnnotation.class);
+
+ // A class that is not annotated with @Schema is not compatible with add()
+ // since there is no information to fetch.
+ assertThatThrownBy(() -> BulkResourceMapper.add(NoAnnotation.class))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("Requested schema for the")
+ .hasMessageContaining("NoAnnotation class, which does not have a valid")
+ .hasMessageContaining("@Schema annotation");
+
+ // It should be possible to register the un-annotated class with the put()
+ // method.
+ BulkResourceMapper.clear();
+ BulkResourceMapper.put(Set.of("urn:pingidentity:put"), NoAnnotation.class);
+ assertThat(SCHEMAS_MAP).hasSize(1);
+
+ arrayNode.removeAll();
+ arrayNode.add("urn:pingidentity:put");
+ Class clazzNoAnnotation = BulkResourceMapper.get(arrayNode);
+ assertThat(clazzNoAnnotation).isEqualTo(NoAnnotation.class);
+
+ // Trying to fetch an unregistered or invalid JsonNode should just return
+ // the default GenericScimResource value.
+ arrayNode.removeAll();
+ arrayNode.add("urn:notFound");
+ assertThat(BulkResourceMapper.get(arrayNode))
+ .isEqualTo(GenericScimResource.class);
+ assertThat(BulkResourceMapper.get(NullNode.getInstance()))
+ .isEqualTo(GenericScimResource.class);
+ }
+
+ /**
+ * Ensures the mapper returns expected objects when a JsonNode is provided to
+ * the {@link BulkResourceMapper#asScimResource} method. This is the primary
+ * way to interface with the BulkResourceMapper.
+ */
+ @Test
+ public void testJsonNodeConversion() throws Exception
+ {
+ final var reader = JsonUtils.getObjectReader().forType(ObjectNode.class);
+
+ // A user JSON should result in a UserResource.
+ String userJson = """
+ {
+ "schemas": [ "urn:ietf:params:scim:schemas:core:2.0:User" ],
+ "userName": "simpleUser"
+ }""";
+ ObjectNode userNode = reader.readValue(userJson);
+ ScimResource resource = BulkResourceMapper.asScimResource(userNode);
+ assertThat(resource).isInstanceOfSatisfying(UserResource.class,
+ user -> assertThat(user.getUserName()).isEqualTo("simpleUser"));
+
+ // Group JSON objects should result in a GroupResource.
+ String groupJson = """
+ {
+ "schemas": [ "urn:ietf:params:scim:schemas:core:2.0:Group" ],
+ "displayName": "simpleGroup"
+ }""";
+ ObjectNode groupNode = reader.readValue(groupJson);
+ ScimResource groupResource = BulkResourceMapper.asScimResource(groupNode);
+ assertThat(groupResource).isInstanceOfSatisfying(GroupResource.class,
+ group -> assertThat(group.getDisplayName()).isEqualTo("simpleGroup"));
+
+ // An unregistered schema should be returned as a GenericScimResource.
+ String customJson = """
+ {
+ "schemas": [
+ "urn:ietf:params:scim:schemas:core:2.0:User",
+ "urn:example:customExtension"
+ ],
+ "userName": "customName"
+ }""";
+ var gen = BulkResourceMapper.asScimResource(reader.readValue(customJson));
+ assertThat(gen).isInstanceOfSatisfying(GenericScimResource.class, gsr -> {
+ assertThat(gsr.getObjectNode().get("userName").asText())
+ .isEqualTo("customName");
+ });
+
+ // Register the custom class that was just attempted.
+ BulkResourceMapper.put(
+ Set.of("urn:ietf:params:scim:schemas:core:2.0:User",
+ "urn:example:customExtension"),
+ UserSubClass.class
+ );
+
+ // Attempt reading the value again.
+ ObjectNode customUser = reader.readValue(customJson);
+ ScimResource customResource = BulkResourceMapper.asScimResource(customUser);
+ assertThat(customResource).isInstanceOfSatisfying(UserSubClass.class,
+ user -> assertThat(user.getUserName()).isEqualTo("customName"));
+ assertThat(resource).isNotInstanceOf(GenericScimResource.class);
+
+ // The "schemas" value should be treated as a set. Ensure the mapping still
+ // works when the values are out of order.
+ String outOfOrderJson = """
+ {
+ "schemas": [
+ "urn:example:customExtension",
+ "urn:ietf:params:scim:schemas:core:2.0:User"
+ ],
+ "userName": "customName"
+ }""";
+ ObjectNode outOfOrderUser = reader.readValue(outOfOrderJson);
+ var outOfOrderResource = BulkResourceMapper.asScimResource(outOfOrderUser);
+ assertThat(outOfOrderResource).isInstanceOfSatisfying(UserSubClass.class,
+ user -> assertThat(user.getUserName()).isEqualTo("customName"));
+ assertThat(outOfOrderResource).isEqualTo(customResource);
+ }
+
+ /**
+ * A custom class definition with a {@code @Schema} annotation.
+ */
+ @Schema(id = "urn:pingidentity:example", description = "", name = "")
+ private static class ClassWithAnnotation extends BaseScimResource {}
+
+ /**
+ * A custom class definition without a {@code @Schema} annotation.
+ */
+ private static class NoAnnotation extends BaseScimResource {}
+
+ private static class UserSubClass extends UserResource
+ {
+ UserSubClass()
+ {
+ super.setSchemaUrns("urn:ietf:params:scim:schemas:core:2.0:User",
+ "urn:example:customExtension");
+ }
+ }
+}
diff --git a/scim2-sdk-common/src/test/java/com/unboundid/scim2/common/exceptions/ScimExceptionTest.java b/scim2-sdk-common/src/test/java/com/unboundid/scim2/common/exceptions/ScimExceptionTest.java
index 9dce1bb7..48492343 100644
--- a/scim2-sdk-common/src/test/java/com/unboundid/scim2/common/exceptions/ScimExceptionTest.java
+++ b/scim2-sdk-common/src/test/java/com/unboundid/scim2/common/exceptions/ScimExceptionTest.java
@@ -35,11 +35,13 @@
import com.unboundid.scim2.common.messages.ErrorResponse;
import org.testng.annotations.Test;
+import javax.naming.SizeLimitExceededException;
import java.net.ConnectException;
import java.nio.BufferOverflowException;
import java.nio.file.AccessDeniedException;
import java.sql.SQLClientInfoException;
import java.text.ParseException;
+import java.util.concurrent.TimeoutException;
import static org.assertj.core.api.Assertions.assertThat;
@@ -79,6 +81,10 @@ public void testScimException()
assertThat(e).isInstanceOf(ResourceConflictException.class);
e = ScimException.createException(new ErrorResponse(412), null);
assertThat(e).isInstanceOf(PreconditionFailedException.class);
+ e = ScimException.createException(new ErrorResponse(413), null);
+ assertThat(e).isInstanceOf(ContentTooLargeException.class);
+ e = ScimException.createException(new ErrorResponse(429), null);
+ assertThat(e).isInstanceOf(RateLimitException.class);
e = ScimException.createException(new ErrorResponse(500), null);
assertThat(e).isInstanceOf(ServerErrorException.class);
e = ScimException.createException(new ErrorResponse(501), null);
@@ -94,6 +100,9 @@ public void testScimException()
public void testNotModifiedException()
{
final int errorCode = 304;
+ assertThat(errorCode).isEqualTo(NotModifiedException.statusInt());
+ assertThat(errorCode)
+ .asString().isEqualTo(NotModifiedException.status());
NotModifiedException e =
new NotModifiedException("The resource has not been modified.");
@@ -129,6 +138,9 @@ public void testNotModifiedException()
public void testBadRequestException()
{
final int errorCode = 400;
+ assertThat(errorCode).isEqualTo(BadRequestException.statusInt());
+ assertThat(errorCode)
+ .asString().isEqualTo(BadRequestException.status());
BadRequestException e =
new BadRequestException("Detailed message explaining the error.");
@@ -182,6 +194,9 @@ public void testBadRequestException()
public void testUnauthorizedException()
{
final int errorCode = 401;
+ assertThat(errorCode).isEqualTo(UnauthorizedException.statusInt());
+ assertThat(errorCode)
+ .asString().isEqualTo(UnauthorizedException.status());
UnauthorizedException e = new UnauthorizedException(
"The client is not authorized to perform the operation.");
@@ -213,6 +228,9 @@ public void testUnauthorizedException()
public void testForbiddenException()
{
final int errorCode = 403;
+ assertThat(errorCode).isEqualTo(ForbiddenException.statusInt());
+ assertThat(errorCode)
+ .asString().isEqualTo(ForbiddenException.status());
ForbiddenException e = new ForbiddenException("Access denied.");
assertThat(e.getScimError().getStatus()).isEqualTo(errorCode);
@@ -249,6 +267,9 @@ public void testForbiddenException()
public void testNotFoundException()
{
final int errorCode = 404;
+ assertThat(errorCode).isEqualTo(ResourceNotFoundException.statusInt());
+ assertThat(errorCode)
+ .asString().isEqualTo(ResourceNotFoundException.status());
ResourceNotFoundException e =
new ResourceNotFoundException("The requested resource was not found.");
@@ -271,6 +292,9 @@ public void testNotFoundException()
public void testMethodNotAllowedException()
{
final int errorCode = 405;
+ assertThat(errorCode).isEqualTo(MethodNotAllowedException.statusInt());
+ assertThat(errorCode)
+ .asString().isEqualTo(MethodNotAllowedException.status());
MethodNotAllowedException e = new MethodNotAllowedException(
"The /.search endpoint only supports POST requests.");
@@ -295,6 +319,9 @@ public void testMethodNotAllowedException()
public void testResourceConflictException()
{
final int errorCode = 409;
+ assertThat(errorCode).isEqualTo(ResourceConflictException.statusInt());
+ assertThat(errorCode)
+ .asString().isEqualTo(ResourceConflictException.status());
ResourceConflictException e =
new ResourceConflictException("Detailed error message.");
@@ -332,6 +359,9 @@ public void testResourceConflictException()
public void testPreconditionFailedException()
{
final int errorCode = 412;
+ assertThat(errorCode).isEqualTo(PreconditionFailedException.statusInt());
+ assertThat(errorCode)
+ .asString().isEqualTo(PreconditionFailedException.status());
PreconditionFailedException e = new PreconditionFailedException(
"Failed to update. The resource changed on the server.");
@@ -373,6 +403,68 @@ public void testPreconditionFailedException()
assertThat(e.getVersion()).isEqualTo("serverVersion");
}
+ /**
+ * Tests for {@link ContentTooLargeException}.
+ */
+ @Test
+ public void testContentTooLargeException()
+ {
+ final int errorCode = 413;
+ assertThat(errorCode).isEqualTo(ContentTooLargeException.statusInt());
+ assertThat(errorCode)
+ .asString().isEqualTo(ContentTooLargeException.status());
+
+ ContentTooLargeException e =
+ new ContentTooLargeException("The request size exceeds the limit.");
+ assertThat(e.getScimError().getStatus()).isEqualTo(errorCode);
+ assertThat(e.getScimError().getScimType()).isNull();
+ assertThat(e.getMessage()).isEqualTo("The request size exceeds the limit.");
+ assertThat(e.getCause()).isNull();
+
+ e = new ContentTooLargeException("Size limit exceeded",
+ new SizeLimitExceededException());
+ assertThat(e.getScimError().getStatus()).isEqualTo(errorCode);
+ assertThat(e.getScimError().getScimType()).isNull();
+ assertThat(e.getMessage()).isEqualTo("Size limit exceeded");
+ assertThat(e.getCause()).isInstanceOf(SizeLimitExceededException.class);
+
+ var errorResponse = new ErrorResponse(errorCode);
+ errorResponse.setDetail("Limit exceeded.");
+ e = new ContentTooLargeException(errorResponse,
+ new NegativeArraySizeException());
+ assertThat(e.getScimError().getStatus()).isEqualTo(errorCode);
+ assertThat(e.getScimError().getScimType()).isNull();
+ assertThat(e.getMessage()).isEqualTo("Limit exceeded.");
+ assertThat(e.getCause()).isInstanceOf(NegativeArraySizeException.class);
+ }
+
+ /**
+ * Tests for {@link RateLimitException}.
+ */
+ @Test
+ public void testRateLimitException()
+ {
+ final int errorCode = 429;
+ assertThat(errorCode).isEqualTo(RateLimitException.statusInt());
+ assertThat(errorCode)
+ .asString().isEqualTo(RateLimitException.status());
+
+ RateLimitException e =
+ new RateLimitException("Too many requests. Please try again later.");
+ assertThat(e.getScimError().getStatus()).isEqualTo(errorCode);
+ assertThat(e.getScimError().getScimType()).isNull();
+ assertThat(e.getMessage()).contains("Please try again later.");
+ assertThat(e.getCause()).isNull();
+
+ var errorResponse = new ErrorResponse(errorCode);
+ errorResponse.setDetail("You're sending it too fast.");
+ e = new RateLimitException(errorResponse, new TimeoutException());
+ assertThat(e.getScimError().getStatus()).isEqualTo(errorCode);
+ assertThat(e.getScimError().getScimType()).isNull();
+ assertThat(e.getMessage()).isEqualTo("You're sending it too fast.");
+ assertThat(e.getCause()).isInstanceOf(TimeoutException.class);
+ }
+
/**
* Tests for {@link ServerErrorException}.
*/
@@ -380,6 +472,9 @@ public void testPreconditionFailedException()
public void testServerErrorException()
{
final int errorCode = 500;
+ assertThat(errorCode).isEqualTo(ServerErrorException.statusInt());
+ assertThat(errorCode)
+ .asString().isEqualTo(ServerErrorException.status());
ServerErrorException e =
new ServerErrorException("An unexpected error occurred.");
@@ -403,6 +498,9 @@ public void testServerErrorException()
public void testNotImplementedException()
{
final int errorCode = 501;
+ assertThat(errorCode).isEqualTo(NotImplementedException.statusInt());
+ assertThat(errorCode)
+ .asString().isEqualTo(NotImplementedException.status());
NotImplementedException e =
new NotImplementedException("The requested endpoint is not supported.");
diff --git a/scim2-sdk-server/pom.xml b/scim2-sdk-server/pom.xml
index d20d383b..4ddec755 100644
--- a/scim2-sdk-server/pom.xml
+++ b/scim2-sdk-server/pom.xml
@@ -34,7 +34,7 @@
com.unboundid.product.scim2
scim2-parent
- 5.0.1-SNAPSHOT
+ 5.1.0-SNAPSHOT
../pom.xml
scim2-sdk-server
diff --git a/scim2-ubid-extensions/pom.xml b/scim2-ubid-extensions/pom.xml
index 9564f751..565abc55 100644
--- a/scim2-ubid-extensions/pom.xml
+++ b/scim2-ubid-extensions/pom.xml
@@ -34,7 +34,7 @@
com.unboundid.product.scim2
scim2-parent
- 5.0.1-SNAPSHOT
+ 5.1.0-SNAPSHOT
../pom.xml
scim2-ubid-extensions