Validate at the boundary, and validate allowlists
Every request body, path parameter and query string is untrusted. In Spring that means Bean Validation annotations on request DTOs and a global exception handler that turns violations into a consistent 400 rather than leaking a stack trace. The important discipline is validating into a DTO, never binding a request directly onto a persistence entity.
Binding straight to an entity is how mass-assignment vulnerabilities happen: a caller supplies a field like role or accountId that was never part of the intended form, and it is silently persisted. An explicit DTO with only the fields the endpoint accepts makes that structurally impossible, which is far better than remembering to check.
Injection: parameterised queries and safe deserialisation
SQL injection remains common in code that builds queries by concatenation, usually in a dynamic search or an ORDER BY clause where a parameter cannot be bound. Named parameters everywhere is the rule; for sort columns, map the user input through an allowlist to a known column name rather than interpolating it.
Deserialisation deserves the same suspicion. Configure the JSON mapper to reject unknown properties, avoid polymorphic type handling driven by data in the payload, and never deserialise into a type that can trigger side effects on construction. This class of bug is less common than SQL injection and considerably more severe when it lands.
Authentication that fails closed
For token-based auth, verify the signature with the expected algorithm rather than trusting the algorithm named in the token header — accepting the header's claim is the classic algorithm-confusion flaw. Check expiry, issuer and audience, and reject anything that fails any of those, with the same generic error so the response does not become an oracle.
Keep access tokens short-lived and refresh tokens revocable. And make the security configuration deny-by-default: any endpoint not explicitly permitted requires authentication. The opposite arrangement means a newly added controller is public until someone remembers to secure it, and eventually someone will not.
Rate limiting, payload limits and error hygiene
Rate limit by authenticated identity where possible and by IP otherwise, with tighter limits on expensive endpoints and on authentication itself — unlimited login attempts are a credential-stuffing invitation. Cap request body size and collection lengths too, because an endpoint that happily accepts a 50MB array is a denial-of-service vector.
Errors should be helpful to legitimate clients and useless to attackers. Return a stable error code and a generic message; log the detail server-side with a correlation ID the client can quote to support. Stack traces and database messages in responses are a reconnaissance gift, and they are still shipped by default in more services than they should be.
Secrets, dependencies and audit logging
Secrets belong in a secret manager, injected at runtime, never in source or a committed properties file. Rotation should be a routine operation rather than an incident response, which in practice means the application must tolerate a credential changing without a redeploy.
Dependency scanning belongs in CI and should fail the build on known-vulnerable versions — the majority of real-world compromises I have reviewed came through a dependency, not through bespoke code. Finally, log security-relevant events: authentication outcomes, authorisation denials, and changes to sensitive records, with actor, action, target and timestamp. That audit trail is what turns an incident from a guess into an investigation.
Key takeaways
- Validate into explicit DTOs, never bind requests onto entities — that is how mass assignment happens
- Parameterise all queries and allowlist sort columns; reject unknown JSON properties and avoid data-driven polymorphic types
- Verify token signatures with the expected algorithm, not the one the token declares, and deny by default
- Scope every query by the requesting principal — broken object-level authorisation is the most common serious API flaw
- Rate limit authentication endpoints, cap body and collection sizes, and never return stack traces
- Keep secrets in a manager with routine rotation, fail CI on vulnerable dependencies, and log actor-action-target audit events
Conclusion
API security is a checklist discipline rather than a clever one. Almost every breach I have reviewed traces back to a missed item on a list like this — usually object-level authorisation or an unpatched dependency. Run the list on every service, and run it again when the service changes shape.
Enjoyed this article?

Vivek Kumar Singh
Technical Expert · Full Stack Cloud Engineer · Tokyo, Japan