Chaturmind
LearnDSASystem DesignInterview PrepDevOpsEngineering GrowthBlog
Start learning
Chaturmind

Structured learning paths for engineers who want to go deep. Written by practitioners.

Learn

  • Java
  • DSA
  • System Design
  • Spring Boot
  • AI / ML
  • DevOps
  • Engineering Growth
  • Java Interview Prep

Company

  • Blog
  • Contact

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Chaturmind. All rights reserved.

Built for engineers who want to go deep.


← Java Interview Prep: 5–8 Years

Advanced Core Java

  • Advanced OOP & Design Scenarios — Interview Questions
  • Advanced Concurrency, Collections & Memory — Interview Questions
  • Modern Java Language Features & Annotations — Interview Questions
  • Class Loading, Reflection, Serialization & Idioms — Interview Questions

Java Design Patterns in Depth

  • Design Patterns Overview & Singleton — Interview Questions
  • Factory & Abstract Factory Patterns — Interview Questions
  • Builder & Prototype Patterns — Interview Questions
  • Adapter & Bridge Patterns — Interview Questions
  • Composite & Decorator Patterns — Interview Questions
  • Facade & Proxy Patterns — Interview Questions
  • Chain of Responsibility & Observer Patterns — Interview Questions
  • Strategy & Template Method Patterns — Interview Questions
  • Command Pattern — Interview Questions

Advanced Spring Boot

  • Logging, Configuration & Actuator (Advanced) — Interview Questions
  • Transactions, Multiple Datasources & Query Tuning — Interview Questions
  • Validation & REST API Design (Advanced) — Interview Questions
  • Reactive, Async & Scheduling in Spring Boot — Interview Questions
  • Deployment, High Availability, Scaling & Caching — Interview Questions
Chaturmind
← Java Interview Prep: 5–8 Years

Advanced Core Java

  • Advanced OOP & Design Scenarios — Interview Questions
  • Advanced Concurrency, Collections & Memory — Interview Questions
  • Modern Java Language Features & Annotations — Interview Questions
  • Class Loading, Reflection, Serialization & Idioms — Interview Questions

Java Design Patterns in Depth

  • Design Patterns Overview & Singleton — Interview Questions
  • Factory & Abstract Factory Patterns — Interview Questions
  • Builder & Prototype Patterns — Interview Questions
  • Adapter & Bridge Patterns — Interview Questions
  • Composite & Decorator Patterns — Interview Questions
  • Facade & Proxy Patterns — Interview Questions
  • Chain of Responsibility & Observer Patterns — Interview Questions
  • Strategy & Template Method Patterns — Interview Questions
  • Command Pattern — Interview Questions

Advanced Spring Boot

  • Logging, Configuration & Actuator (Advanced) — Interview Questions
  • Transactions, Multiple Datasources & Query Tuning — Interview Questions
  • Validation & REST API Design (Advanced) — Interview Questions
  • Reactive, Async & Scheduling in Spring Boot — Interview Questions
  • Deployment, High Availability, Scaling & Caching — Interview Questions
HomeLearnJava Interview PrepJava Interview Prep: 5–8 YearsAdvanced Spring Boot
✓ FreeAdvanced· 9 min read

Validation & REST API Design (Advanced) — Interview Questions

Bean Validation in Spring Boot (Jakarta Validation + Hibernate Validator, @Valid vs @Validated, groups, method validation), custom and cross-field constraints, third-party validation integration, keeping rules consistent across many forms, @RequestBody, ResponseEntity vs returning objects, HTTP methods with safety/idempotency semantics, and best practices for a scalable, maintainable inventory API.

Published September 25, 2026


How to use this lesson

At this level, validation answers should go beyond @NotNull. Cover:

  • where validation lives: at the edges, and in the domain;
  • reusable custom constraints, and cross-field rules;
  • consistent error responses (RFC 9457 Problem Details).

REST answers should mention idempotency, status codes, versioning, pagination, and concurrency control (ETags).

Q1. How does Spring Boot support data validation?

Short answer: Add spring-boot-starter-validation. That brings Hibernate Validator, the reference implementation of Jakarta Bean Validation 3.x (jakarta.validation.* in Boot 3; it was javax.validation in Boot 2). Then:

  • Annotate DTO fields (@NotBlank, @Email, @Size, @Positive, @Pattern, @Future).
  • Put @Valid on @RequestBody or @ModelAttribute parameters. Violations throw MethodArgumentNotValidException, which Boot turns into a 400.
  • Put @Validated on a class to validate method parameters and return values (@NotNull String id, @Min(1) int page).
public record CreateProductRequest(
        @NotBlank @Size(max = 120) String name,
        @NotNull @Positive BigDecimal price,
        @PositiveOrZero int stock,
        @Pattern(regexp = "[A-Z]{3}-\\d{4}") String sku) { }

@PostMapping("/products")
ResponseEntity<ProductDto> create(@Valid @RequestBody CreateProductRequest req) { ... }

Common trap: since Boot 2.3, validation is not included in the web starter. Without spring-boot-starter-validation, @Valid silently does nothing.

Learn it in depth → Bean Validation with @Valid

Q2. How do you handle form validation in Spring Boot applications?

Short answer:

  • Server-rendered forms (Thymeleaf or MVC): use @Valid @ModelAttribute("form") Form form, BindingResult result. BindingResult must come immediately after the validated parameter. If result.hasErrors(), return the form view, which renders field errors with th:errors.
  • REST: let the exception propagate, and map it in a @RestControllerAdvice to a consistent error body (Problem Details), listing each field's error.
@RestControllerAdvice
class ValidationErrors {
    @ExceptionHandler(MethodArgumentNotValidException.class)
    ProblemDetail invalid(MethodArgumentNotValidException ex) {
        ProblemDetail pd = ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, "Validation failed");
        pd.setProperty("errors", ex.getBindingResult().getFieldErrors().stream()
                .map(f -> Map.of("field", f.getField(), "message", f.getDefaultMessage())).toList());
        return pd;
    }
}

Learn it in depth → Global Exception Handling

Q3. Can you use custom validators? How?

Short answer: Yes, in two ways:

  1. A custom constraint annotation (preferred, and reusable): an annotation with @Constraint(validatedBy = …), plus a ConstraintValidator<A, T>. Validators are Spring beans, so they can inject repositories or services.
  2. Spring's org.springframework.validation.Validator interface (supports plus validate(Object, Errors)), registered with @InitBinder (binder.addValidators(...)) or invoked manually. This is useful for rules that are awkward to express as annotations.
@Target({FIELD, PARAMETER}) @Retention(RUNTIME)
@Constraint(validatedBy = UniqueSkuValidator.class)
public @interface UniqueSku {
    String message() default "SKU already exists";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

@Component
class UniqueSkuValidator implements ConstraintValidator<UniqueSku, String> {
    private final ProductRepository repo;
    UniqueSkuValidator(ProductRepository repo) { this.repo = repo; }
    public boolean isValid(String sku, ConstraintValidatorContext ctx) {
        return sku == null || !repo.existsBySku(sku);          // leave null to @NotNull, one job per constraint
    }
}

Common trap: a uniqueness check in a validator is racy. Two requests can both pass. Keep a unique database constraint as the real guarantee, and map the violation to a 409.

Q4. How do you implement complex validation rules involving several fields?

Short answer: Use a class-level constraint: an annotation on the DTO, and a ConstraintValidator that receives the whole object. Report the error on a specific field, so the UI can highlight it.

@DateRangeValid
public record PromotionRequest(@NotNull LocalDate startDate, @NotNull LocalDate endDate, BigDecimal discountPct, BigDecimal flatDiscount) { }

class DateRangeValidator implements ConstraintValidator<DateRangeValid, PromotionRequest> {
    public boolean isValid(PromotionRequest r, ConstraintValidatorContext ctx) {
        boolean ok = true;
        ctx.disableDefaultConstraintViolation();
        if (r.startDate() != null && r.endDate() != null && !r.endDate().isAfter(r.startDate())) {
            ctx.buildConstraintViolationWithTemplate("endDate must be after startDate").addPropertyNode("endDate").addConstraintViolation();
            ok = false;
        }
        if ((r.discountPct() == null) == (r.flatDiscount() == null)) {          // exactly one of the two
            ctx.buildConstraintViolationWithTemplate("specify either discountPct or flatDiscount").addPropertyNode("discountPct").addConstraintViolation();
            ok = false;
        }
        return ok;
    }
}

Key points to cover:

  • Validation groups (@Validated(OnCreate.class)) apply different rules to create and update.
  • @GroupSequence orders the cheap checks before the expensive ones.
  • Rules that need database state or business context (credit limits, stock) belong in the service or domain layer, not in annotations.

Q5. Can you integrate third-party libraries for validation? How?

Short answer: Yes:

  • Other Jakarta Validation providers or constraint libraries: add the dependency, and their annotations work with @Valid/@Validated, since they plug into the same Validator. For example, Hibernate Validator's extra constraints (@URL, @CreditCardNumber, @UUID), or libraries like libphonenumber wrapped in a custom constraint.
  • Non-annotation libraries (the YAVI or Vavr validation style, JSON Schema validators for payload contracts, OpenAPI request validators): call them from a custom ConstraintValidator, a Spring Validator, or a filter or interceptor.
  • Wrap them behind your own constraint annotation, so the rest of the code isn't coupled to the library.

Q6. How do you keep validation rules consistent across many forms?

Short answer:

  • Define each rule once, as a reusable custom or composed constraint (@ValidPhone, @ValidPincode built from @Pattern plus @Size), and reuse it on every DTO.
  • Share the DTOs, or at least the constraint annotations, in a common module.
  • Centralise the messages in ValidationMessages.properties, which is also where i18n comes from.
  • One global error handler, so every endpoint returns the same error format.
  • Validate at the service boundary too (@Validated on services), so non-HTTP entry points (Kafka consumers, batch jobs) follow the same rules.
  • Put business invariants in the domain model.
  • Test the constraints directly, with Validator unit tests.
  • Expose the rules to the frontend (the OpenAPI schema), so client-side checks mirror the server's, but never replace them.

Q7. What does @RequestBody do?

Short answer: It binds the HTTP request body to a method parameter, through an HttpMessageConverter chosen by the Content-Type: Jackson for JSON, or JAXB/Jackson XML. It's usually combined with @Valid.

Key points to cover:

  • A missing or empty body gives a 400 (required = true by default).
  • Malformed JSON gives HttpMessageNotReadableException, which is also a 400.
  • An unsupported content type gives a 415.
  • Configure Jackson with care. For example, FAIL_ON_UNKNOWN_PROPERTIES is false by default in Boot, so misspelt fields are silently ignored.
  • Never bind request bodies straight to JPA entities: that allows mass assignment. Use request DTOs.

Q8. What's the difference between returning a ResponseEntity and returning an object directly?

Short answer:

  • Returning an object: Spring serialises it with status 200 (or the status given by @ResponseStatus). It's simple, and good for the standard path.
  • ResponseEntity<T>: full control of the status, headers and body per call. For example:
    • 201 Created with a Location header;
    • 204 No Content;
    • 304 with an ETag;
    • cache headers;
    • conditional 404s.
@PostMapping("/products")
ResponseEntity<ProductDto> create(@Valid @RequestBody CreateProductRequest req, UriComponentsBuilder uri) {
    ProductDto p = service.create(req);
    return ResponseEntity.created(uri.path("/products/{id}").build(p.id())).body(p);
}
@GetMapping("/products/{id}")
ResponseEntity<ProductDto> get(@PathVariable UUID id) {
    return service.find(id).map(ResponseEntity::ok).orElse(ResponseEntity.notFound().build());
}

Key points to cover:

  • A common, clean style returns plain objects, and throws domain exceptions mapped by @RestControllerAdvice. Use ResponseEntity where the status or headers depend on the outcome.

Q9. What are the HTTP methods?

Short answer:

  • GET: read.
  • POST: create, or process.
  • PUT: replace, or create at a known URI.
  • PATCH: partial update.
  • DELETE: remove.
  • HEAD: GET without the body.
  • OPTIONS: capabilities, and CORS preflight.
  • TRACE and CONNECT are rarely used in APIs.

The two properties that matter in design are safe (no state change) and idempotent (repeating the call has the same effect as calling once):

MethodSafeIdempotent
GET, HEAD, OPTIONS✅✅
PUT, DELETE❌✅
POST❌❌ (make it idempotent with an Idempotency-Key header)
PATCH❌Not guaranteed

Q10. When should you use each HTTP method in a REST API?

Short answer:

  • GET /products?category=x&page=2: retrieval, with filters and pagination. It's cacheable, and must have no side effects.
  • POST /products: create, where the server assigns the ID. Returns 201 with Location. Also used for actions that don't map to CRUD (POST /orders/{id}/cancel).
  • PUT /products/{id}: full replacement, sending the whole representation. Idempotent, so it's safe to retry.
  • PATCH /products/{id}: partial update (JSON Merge Patch or JSON Patch). Combine it with optimistic concurrency (If-Match: <etag> returning 412 on conflict).
  • DELETE /products/{id}: returns 204. Repeating it gives 204 or 404, and the state is the same either way.

Common trap: using GET for operations that change state. Crawlers, prefetchers and retries will trigger them.

Q11. You're designing new REST endpoints for a complex product inventory system. Which best practices would you follow for scalability, maintainability and performance?

Short answer:

  • Resource modelling:
    • Nouns and hierarchy (/warehouses/{id}/stock), with consistent naming.
    • Actions as sub-resources, or POST commands.
    • Contract first with OpenAPI.
  • Scalability:
    • Stateless services, horizontally scaled.
    • Pagination everywhere, preferring cursor or keyset for large sets.
    • Rate limiting.
    • Asynchronous processing for bulk imports: 202 Accepted plus a status resource.
  • Performance:
    • Caching: ETag/Cache-Control for catalogue reads, and Redis for hot data.
    • Sparse fieldsets or projections (?fields=id,name,stock).
    • Compression.
    • Avoid N+1 queries and chatty APIs, and provide bulk endpoints.
  • Correctness under concurrency:
    • Optimistic locking (@Version, exposed as ETags) for stock updates.
    • Idempotency keys on POSTs.
    • Atomic stock reservation.
  • Maintainability:
    • Versioning (/v1, or media-type versioning), with additive, backward-compatible changes, and deprecation headers.
    • Consistent Problem Details errors.
    • DTOs separate from entities.
    • Validation at the edge.
  • Operations:
    • Authentication (OAuth2 or JWT), and authorisation per operation.
    • Observability: metrics per endpoint, and tracing.
    • Contract tests against consumers.

Learn it in depth → API Contract Design

Follow-up questions this topic invites — and their answers

Q: What's the difference between @Valid and @Validated? A: @Valid (Jakarta) triggers validation and cascades into nested objects. @Validated (Spring) supports validation groups, and on a class it enables method-level validation through a proxy. Use @Valid on request bodies and nested fields, and @Validated for groups and service-method validation.

Q: How do you validate a list in a request body? A: Use List<@Valid ItemDto> items in the DTO (container element constraints), or @Valid @RequestBody List<ItemDto>, with @Validated on the controller in older versions. Add @Size(max = 500) to limit the batch.

Q: PUT or PATCH for updating stock quantity? A: Neither, if it's a delta. Use an action such as POST /products/{id}/stock-adjustments {delta: -3, reason} with an idempotency key. It's auditable, and safe under concurrency. Use PUT or PATCH to set fields, with an ETag or version check.

Q: How do you return validation errors from service-layer (@Validated) validation? A: Method validation throws ConstraintViolationException (or HandlerMethodValidationException for controller method validation in Spring 6.1+). Map both in your @RestControllerAdvice to the same Problem Details format.

Previous

Transactions, Multiple Datasources & Query Tuning — Interview Questions

Next

Reactive, Async & Scheduling in Spring Boot — Interview Questions

AI Tutor

Lesson: Validation & REST API Design (Advanced) — Interview Questions

Quick actions

AI responses can be inaccurate. Verify critical information.