Use Nullness to determine if an endpoint parameter is optional

Closes gh-46593
This commit is contained in:
Stéphane Nicoll
2025-08-11 16:24:02 +02:00
parent 373e34e8bc
commit 94349f87e7
3 changed files with 18 additions and 5 deletions
@@ -413,7 +413,8 @@ Operations on an endpoint receive input through their parameters.
When exposed over the web, the values for these parameters are taken from the URL's query parameters and from the JSON request body.
When exposed over JMX, the parameters are mapped to the parameters of the MBean's operations.
Parameters are required by default.
They can be made optional by annotating them with either javadoc:org.springframework.boot.actuate.endpoint.annotation.OptionalParameter[format=annotation] or javadoc:org.springframework.lang.Nullable[format=annotation].
They can be made optional by annotating them with either javadoc:org.springframework.boot.actuate.endpoint.annotation.OptionalParameter[format=annotation] or JSpecify's javadoc:org.jspecify.annotations.Nullable[format=annotation].
Kotlin null safety is also supported.
You can map each root property in the JSON request body to a parameter of the endpoint.
Consider the following JSON request body:
@@ -547,7 +548,7 @@ NOTE: Range requests are not supported when using Jersey.
==== Web Endpoint Security
An operation on a web endpoint or a web-specific endpoint extension can receive the current javadoc:java.security.Principal[] or javadoc:org.springframework.boot.actuate.endpoint.SecurityContext[] as a method parameter.
The former is typically used in conjunction with either javadoc:org.springframework.boot.actuate.endpoint.annotation.OptionalParameter[format=annotation] or javadoc:org.springframework.lang.Nullable[format=annotation] to provide different behavior for authenticated and unauthenticated users.
The former is typically used in conjunction with either javadoc:org.springframework.boot.actuate.endpoint.annotation.OptionalParameter[format=annotation] or javadoc:org.jspecify.annotations.Nullable[format=annotation] to provide different behavior for authenticated and unauthenticated users.
The latter is typically used to perform authorization checks by using its `isUserInRole(String)` method.
@@ -21,6 +21,7 @@ import java.lang.reflect.Parameter;
import java.util.function.Predicate;
import org.springframework.boot.actuate.endpoint.invoke.OperationParameter;
import org.springframework.core.Nullness;
/**
* {@link OperationParameter} created from an {@link OperationMethod}.
@@ -63,10 +64,8 @@ class OperationMethodParameter implements OperationParameter {
return !isOptional();
}
@SuppressWarnings("deprecation")
private boolean isOptional() {
return this.parameter.getAnnotationsByType(org.springframework.lang.Nullable.class).length > 0
|| this.optional.test(this.parameter);
return Nullness.NULLABLE == Nullness.forParameter(this.parameter) || this.optional.test(this.parameter);
}
@Override
@@ -43,6 +43,9 @@ class OperationMethodParameterTests {
private final Method example = ReflectionUtils.findMethod(getClass(), "example", String.class, String.class);
private final Method exampleJSpecifyNullable = ReflectionUtils.findMethod(getClass(), "exampleJSpecifyNullable",
String.class, String.class);
private final Method exampleSpringNullable = ReflectionUtils.findMethod(getClass(), "exampleSpringNullable",
String.class, String.class);
@@ -76,6 +79,13 @@ class OperationMethodParameterTests {
assertThat(parameter.isMandatory()).isFalse();
}
@Test
void isMandatoryWhenJSpecifyNullableAnnotationShouldReturnFalse() {
OperationMethodParameter parameter = new OperationMethodParameter("name",
this.exampleJSpecifyNullable.getParameters()[1], this::isOptionalParameter);
assertThat(parameter.isMandatory()).isFalse();
}
@Test
void isMandatoryWhenSpringNullableAnnotationShouldReturnFalse() {
OperationMethodParameter parameter = new OperationMethodParameter("name",
@@ -99,6 +109,9 @@ class OperationMethodParameterTests {
void example(String one, @TestOptional String two) {
}
void exampleJSpecifyNullable(String one, @org.jspecify.annotations.Nullable String two) {
}
@SuppressWarnings("deprecation")
void exampleSpringNullable(String one, @org.springframework.lang.Nullable String two) {
}