
# Method validation

Method validation checks the arguments and the return value of a bean's methods on every call, using constraint annotations on the parameters and the method.

## Why you need it

A service is called from many places: controllers, scheduled jobs, other services, tests. Checking its inputs in each caller is repetitive, and one caller will forget. With method validation the service states its rules once, on its own parameters, and every call is checked, whoever makes it.

You do not need it for objects you already validate on the way in, for example a `@Valid()` request body. You also do not need it inside private helper methods: only calls that go through the bean's proxy are checked.

## How to turn it on

1. Annotate the bean class with `@Validated()`.
2. Put constraints on parameters (they check the arguments) and on the method (they check the return value). Use `@Valid()` to check the constraints inside an argument or result object.
3. Have a `MethodValidationPostProcessor` bean. With `spring_boot_dart` it is registered for you. With `spring_core_dart` alone, declare it yourself.

```dart
import 'package:spring_core_dart/spring_core_dart.dart';

// Callers cannot register an empty or malformed address,
// and the service never returns a blank id.
@Service()
@Validated()
class Registration {
  @NotBlank()
  String register(@Email() @NotBlank() String email) => 'id-$email';

  // @Valid() checks the constraints declared inside SignupForm.
  void signUp(@Valid() SignupForm form) {
    // ...
  }
}

// Without spring_boot_dart: the bean that applies method validation.
@Configuration()
class ValidationConfig {
  @Bean()
  MethodValidationPostProcessor methodValidationPostProcessor(
    BeanIntrospector introspector,
  ) => MethodValidationPostProcessor(BeanValidator(introspector: introspector));
}
```

Pass the context's `BeanIntrospector` to the `BeanValidator`: `@Valid()` arguments are application classes, and the default introspector does not know them.

## What a violation looks like

When the arguments are invalid, the method does not run and the call throws a `ConstraintViolationException`. When the return value is invalid, the method has run, and the exception is thrown after it. For a method that returns a `Future`, the future completes with the exception.

```dart
const log = SpringLog('registration');

void main() {
  final context = ApplicationContext.run([appBeans]);
  final registration = context.getBean<Registration>();
  try {
    registration.register('');
  } on ConstraintViolationException catch (e) {
    log.warning(e.message); // register.email: must not be blank
    for (final v in e.constraintViolations) {
      log.info('${v.propertyPath} -> ${v.constraint.name}'); // register.email -> NotBlank
    }
  }
  context.close();
}
```

Property paths start with the method name: `register.email` for a parameter, `register.<return value>` for the result, `signUp.form.name` inside a `@Valid()` argument.

The web starter does not turn a `ConstraintViolationException` into a 400 response by itself. If a client can cause one, handle it in an `@ExceptionHandler`.

## Groups

`@Validated` takes the groups to check. On the class it sets them for every method; on a method it sets them for that method only. A method-level `@Validated` does not turn validation on by itself: the class needs `@Validated()` too.

```dart
abstract final class OnUpdate {}

@Service()
@Validated()
class Users {
  // Only the constraints of the OnUpdate group are checked here.
  @Validated([OnUpdate])
  void update(@Valid() UserForm form) {
    // ...
  }
}
```

### @Validated

| Name | Type | Default | Meaning |
| --- | --- | --- | --- |
| `value` (positional) | `List<Type>` | `const []` | Groups to check. Empty means only the `Default` group. |

Groups are found on the method first, then on a declaration it overrides (the nearest first), then on the bean class.

## Constraints on interfaces and superclasses

Constraints on the parameters and return value of an overridden declaration apply too: an implemented interface, a superclass, a mixin. Callers that only know the interface see the rules where they read them.

```dart
abstract interface class Accounts {
  String register(@NotBlank() String email);
}

@Service()
@Validated()
class DefaultAccounts implements Accounts {
  // The @NotBlank of the interface is checked on this method.
  @override
  String register(String email) => 'registered';
}
```

## MethodValidationPostProcessor

The bean that applies method validation to every bean whose class is annotated `@Validated()`. Despite its name it is an advisor: proxies are generated at build time, and the context applies it like any other advisor.

| Name | Type | Default | Meaning |
| --- | --- | --- | --- |
| `validator` (positional, optional) | `BeanValidator?` | `BeanValidator()` | The validator that checks arguments and results. Its message source and clock apply. |
| `excludeFilters` (positional, optional) | `List<bool Function(List<Object> classAnnotations)>` | `const []` | Tests on a bean's class annotations; a bean is skipped when one returns `true`. |

Other members:

| Member | Meaning |
| --- | --- |
| `advice` | The `MethodValidationInterceptor` that does the checks. |
| `order` | `Ordered.lowestPrecedence`: validation runs innermost, after other advice such as caching. |

`spring_boot_dart` registers one as `methodValidationPostProcessor`, using the application's `BeanValidator` and excluding `@ConfigurationProperties` classes. Declare your own to change the validator or the exclusions; Boot then backs off.

`MethodValidationInterceptor` is the advice itself. You only build one to apply method validation with your own pointcut.

## Limits

- Only calls through the bean, as other beans receive it, are checked. A call from one method of the bean to another (`this.register(...)`) is not.
- On a `@ConfigurationProperties` class, `@Validated()` means "check the bound values at startup", not method validation.

## In Spring (Java)

```java
@Service
@Validated
class Registration {
  @NotBlank String register(@Email @NotBlank String email) { ... }
}

@Bean
static MethodValidationPostProcessor methodValidationPostProcessor() {
  return new MethodValidationPostProcessor();
}
```

Differences that matter:

- Proxies are generated at build time, not created at run time with CGLIB or JDK proxies.
- `MethodValidationPostProcessor` is an advisor bean, not a `BeanPostProcessor`; declaring it as a normal `@Bean` is enough.
- The groups of `@Validated` are a `List<Type>`, such as `@Validated([OnUpdate])`.
- For a method returning a `Future`, a violation of the result completes the future with the exception.

## See also

- [Validation overview](index.md)
- [Constraints](constraints.md)
- [Validator, Errors and DataBinder](validator.md)
- [@Bean](../beans/bean.md)
- [@Configuration](../beans/configuration.md)
- [Configuration properties](../config/index.md)
