Back to Blog

SQL Injection in Java & Spring Boot — How to Detect and Fix It

SQL injection is ranked A03 in the OWASP Top 10 and has caused the biggest data breaches in history. Learn all the vulnerable patterns in Spring Boot — JDBC, JPA, native queries — and how to fix them permanently.

J

JOptimize Team

April 15, 2026· 5 min read

What is SQL Injection?

SQL injection happens when user input is concatenated directly into a SQL query, allowing attackers to manipulate the query logic. It's ranked A03 in the OWASP Top 10 and has been responsible for some of the biggest data breaches in history.

The Classic Vulnerability

// ❌ Vulnerable — NEVER do this @GetMapping("/users") public List<User> searchUsers(@RequestParam String name) { String query = "SELECT * FROM users WHERE name = '" + name + "'"; return jdbcTemplate.query(query, userRowMapper); }

If an attacker passes ' OR '1'='1 as the name parameter, the query becomes:

SELECT * FROM users WHERE name = '' OR '1'='1'

This returns every user in the database. With more sophisticated payloads, attackers can delete tables, extract passwords, or execute OS commands.

SQL Injection in Spring Boot — All the Patterns

1. JDBC Template with String Concatenation

// ❌ Vulnerable String sql = "SELECT * FROM users WHERE email = '" + email + "'"; jdbcTemplate.query(sql, rowMapper); // ✅ Safe — use named parameters String sql = "SELECT * FROM users WHERE email = :email"; namedParameterJdbcTemplate.query(sql, Map.of("email", email), rowMapper); // ✅ Also safe — use ? placeholders String sql = "SELECT * FROM users WHERE email = ?"; jdbcTemplate.query(sql, rowMapper, email);

2. JPA with Native Queries

// ❌ Vulnerable @Query(value = "SELECT * FROM users WHERE name = '" + "' + name + '" + "'", nativeQuery = true) List<User> findByName(String name); // ❌ Also vulnerable — string concatenation in @Query @Repository public interface UserRepository extends JpaRepository<User, Long> { default List<User> search(String term) { String sql = "SELECT * FROM users WHERE name LIKE '%" + term + "%'"; // ... } } // ✅ Safe — use named parameters in @Query @Query(value = "SELECT * FROM users WHERE name = :name", nativeQuery = true) List<User> findByName(@Param("name") String name); // ✅ Even better — use JPQL (not nativeQuery) @Query("SELECT u FROM User u WHERE u.name = :name") List<User> findByName(@Param("name") String name);

3. EntityManager with createNativeQuery

// ❌ Vulnerable public List<User> search(String term) { String sql = "SELECT * FROM users WHERE name LIKE '%" + term + "%'"; return em.createNativeQuery(sql, User.class).getResultList(); } // ✅ Safe public List<User> search(String term) { String sql = "SELECT * FROM users WHERE name LIKE :term"; return em.createNativeQuery(sql, User.class) .setParameter("term", "%" + term + "%") .getResultList(); }

4. Spring Data Derived Queries — Safe by Default

Spring Data derived query methods are safe by default — they always use parameterized queries:

// ✅ Always safe — Spring generates parameterized SQL List<User> findByEmail(String email); List<User> findByNameContaining(String name); Optional<User> findByEmailAndStatus(String email, String status);

Use derived queries whenever possible — they're the safest and most readable option.

The Dangerous Pattern JOptimize Detects

JOptimize specifically detects string concatenation in SQL queries:

joptimize analyze . # → [CRITICAL] SQL Injection risk: query built with string concatenation. # User input can manipulate the query structure. # UserRepository.java:34 # # → [CRITICAL] SQL Injection risk: String.format() used to build SQL query. # ReportService.java:67

Both + concatenation and String.format() in SQL queries are flagged as CRITICAL.

Input Validation — Defense in Depth

Parameterized queries prevent SQL injection, but input validation adds an extra layer:

@RestController public class UserController { @GetMapping("/users/search") public List<User> search(@RequestParam @Size(max = 100) @Pattern(regexp = "[a-zA-Z0-9 ]+") String name) { return userService.search(name); } }

Enable Bean Validation in your Spring Boot app:

@Configuration public class ValidationConfig { @Bean public MethodValidationPostProcessor methodValidationPostProcessor() { return new MethodValidationPostProcessor(); } }

Order By — The Forgotten Vector

Dynamic ORDER BY clauses can't use parameterized queries for column names:

// ❌ Vulnerable — column name can't be parameterized String sql = "SELECT * FROM users ORDER BY " + sortColumn + " " + sortDirection; // ✅ Safe — whitelist allowed columns private static final Set<String> ALLOWED_COLUMNS = Set.of("name", "email", "createdAt"); public List<User> findAll(String sortColumn, String sortDirection) { if (!ALLOWED_COLUMNS.contains(sortColumn)) { throw new IllegalArgumentException("Invalid sort column"); } String direction = "DESC".equalsIgnoreCase(sortDirection) ? "DESC" : "ASC"; String sql = "SELECT * FROM users ORDER BY " + sortColumn + " " + direction; return jdbcTemplate.query(sql, rowMapper); }

Quick Checklist

  • Never concatenate user input into SQL — use ? placeholders or :namedParam
  • Prefer Spring Data derived queries — parameterized by design
  • Use @Query with :params — never with string concatenation
  • Whitelist ORDER BY columns — they can't be parameterized
  • Add @Valid + @Pattern on controller parameters as defense in depth
  • Never use String.format() to build SQL — same vulnerability as concatenation

Want to go deeper on Java security and OWASP vulnerabilities?

Java Security — OWASP Top 10 for Java Developers — hands-on course covering SQL injection, XSS, CSRF, authentication failures and more with Spring Boot examples.

Detect SQL Injection in Your Project

JOptimize scans your entire codebase for SQL injection patterns — JDBC, JPA, native queries, String.format() — and flags them as CRITICAL with the exact file and line:

npm install -g @joptimize/cli joptimize auth jp_live_your_key joptimize analyze .

Or upload your ZIP at joptimize.io.

Want to go deeper?

Master Spring Boot, security, and Java performance with hands-on courses.

Detect issues in your project

JOptimize finds N+1 queries, EAGER collections, and 70+ other issues in your Java codebase — in under 30 seconds.