Sitemap

Java/Spring Exception Cheat Sheet

5 min readSep 26, 2025

--

รวม exception ตัวเป้ง ๆ ที่เจอบ่อยใน Spring Boot + Spring Data JPA + Hibernate เอาแบบเล่าให้ฟังง่าย ๆ: คืออะไร → ทำไมมันโผล่มา → จะรับมือยังไง → ป้องกันยังไงดี

1) NullPointerException (NPE)

คืออะไร: เรียก method/field บนตัวแปร null
ทำไมเจอ: repo คืน null, mapping หลุด, bean ไม่ถูก inject

ตัวอย่างพัง

String nickUpper = user.getNickname().toUpperCase(); // ถ้า nickname เป็น null → NPE

วิธีแก้

String nickUpper = Optional.ofNullable(user.getNickname())
.orElse(user.getName())
.toUpperCase();

กันไว้: ใส่ @NotNull ใน DTO/Entity, ออกแบบ method ให้คืน Optional หรือ collection ว่างแทน null

2) DataIntegrityViolationException (Spring Data)

คืออะไร: Spring wrap error จาก DB constraint (Unique/NotNull/FK)
ทำไมเจอ: insert email ซ้ำ, ลบ parent ที่ยังมี child

ตัวอย่างพัง

userRepo.save(new User("a@a.com")); // email unique ชน

วิธีแก้ (map เป็น 409)

@RestControllerAdvice
class ApiErrors {
@ExceptionHandler(DataIntegrityViolationException.class)
ResponseEntity<?> handle(DataIntegrityViolationException e){
return ResponseEntity.status(409).body(Map.of("error","Duplicate/constraint violation"));
}
}

กันไว้: ทำ unique index ใน DB จริง ๆ + เช็คซ้ำก่อน save ถ้าเสี่ยง race

3) LazyInitializationException (Hibernate)

คืออะไร: เรียกใช้ relation LAZY หลัง transaction ปิดแล้ว
ทำไมเจอ: ส่ง Entity ถึง Controller แล้ว JSON serializer เรียก getter ตอน session ปิด

ตัวอย่างพัง

@GetMapping("/orders/{id}")
public Order getOrder(@PathVariable Long id){
return orderRepo.findById(id).orElseThrow(); // คืน entity ตรง ๆ
// Serializer จะไปเรียก order.getItems() ตอน tx/session ปิด → LazyInitializationException
}

วิธีแก้ 1: ใช้ DTO + fetch join

@Query("select o from Order o join fetch o.items where o.id=:id")
Order findByIdWithItems(@Param("id") Long id);

@GetMapping("/orders/{id}")
public OrderDto getOrder(@PathVariable Long id){
Order o = orderRepo.findByIdWithItems(id);
return OrderDto.from(o); // map ก่อนออกนอก service
}

วิธีแก้ 2: ครอบด้วย @Transactional ที่ service layer

กันไว้: อย่าปล่อย Entity ออกนอก service, ดึงข้อมูลให้ครบตั้งแต่ query แรก

4) MethodArgumentNotValidException (Spring MVC)

คืออะไร: Binding request → DTO แล้ว validation ไม่ผ่าน
ทำไมเจอ: field ผิด constraint (@NotBlank, @Min)

ตัวอย่างพัง

record CreateUserReq(@NotBlank String email, @Min(1) int age) {}

@PostMapping("/users")
public void create(@Valid @RequestBody CreateUserReq req) { }
// ถ้า email ว่าง → โดน MethodArgumentNotValidException

วิธีแก้ (Global Handler → 400)

@RestControllerAdvice
class GlobalHandlers {
@ExceptionHandler(MethodArgumentNotValidException.class)
ResponseEntity<?> bad(MethodArgumentNotValidException e){
var fields = e.getBindingResult().getFieldErrors().stream()
.collect(Collectors.toMap(f->f.getField(), f->f.getDefaultMessage(), (a,b)->a));
return ResponseEntity.badRequest().body(Map.of("errors", fields));
}
}

กันไว้: ใส่ annotation validation ที่ DTO และเปิด @Validated

5) HttpMessageNotReadableException (Spring MVC)

คืออะไร: Spring อ่าน JSON body ไม่ได้
ทำไมเจอ: JSON พัง, type mismatch

ตัวอย่างพัง

POST /users
Content-Type: application/json
{
"age": "abc" // ต้องเป็น number
}

วิธีแก้: Handler map → 400 พร้อมบอก field/problem, เขียนเอกสาร API ให้ชัด
กันไว้: ใช้ schema/OpenAPI, เพิ่ม test payload ผิดรูป

6) IllegalArgumentException

คืออะไร: ส่ง argument ไม่ถูกต้องให้เมธอด
ทำไมเจอ: ค่าติดลบ/format ผิด

ตัวอย่างพัง/แก้

public Money withdraw(BigDecimal amount){
if (amount == null || amount.signum() <= 0)
throw new IllegalArgumentException("amount must be positive");
// ...
}

กันไว้: ใช้ Bean Validation ที่ boundary (@Min, @Pattern)

7) IllegalStateException

คืออะไร: เรียกเมธอดใน state ที่ไม่พร้อม
ทำไมเจอ: ใช้งาน service ก่อน init, iterator หมดแล้วยัง next()

ตัวอย่างพัง/แก้

@PostConstruct 
void init(){
ready = true;
}

public void run(){
if(!ready) throw new IllegalStateException("not ready");
/*...*/
}

กันไว้: guard/check state ชัด ๆ

8) OptimisticLockException

คืออะไร: update พร้อมกันชนกัน (มี @Version)
ทำไมเจอ: อ่านพร้อมกัน → update พร้อมกัน → version เปลี่ยน

ตัวอย่างพัง

@Entity class Account { 
@Id
Long id;

@Version
Long v;

BigDecimal balance;
}
// Tx A และ Tx B โหลด v=1 พร้อมกัน → A อัปเดต v=2 แล้ว
// B พยายามอัปเดตที่ v=1 → OptimisticLockException

วิธีแก้: จับแล้ว retry หรือแจ้ง user ให้ refresh
กันไว้: ใส่ @Version ใน entity สำคัญ + ดีไซน์ flow ให้ idempotent

9) TransactionRequiredException (JPA)

คืออะไร: เรียก JPA op ที่ต้องใช้ transaction แต่ไม่มี
ทำไมเจอ: persist/remove/merge นอก @Transactional

ตัวอย่างพัง/แก้

@Transactional
public void create(User u){
em.persist(u);
}

กันไว้: วาง @Transactional ที่ service layer

10) RollbackException (JPA) / UnexpectedRollbackException (Spring)

คืออะไร: commit ไม่ผ่าน → rollback
ทำไมเจอ: constraint fail, deadlock, inner tx mark rollback-only

ตัวอย่างพัง/แก้ (บังคับ rollback checked)

@Transactional(rollbackFor = Exception.class)
public void placeOrder() throws Exception {
/* ... ถ้าโยน checked ก็ rollback */
}

กันไว้: ทำ transaction ให้สั้น, index/constraint ให้พร้อม, แยกงานเสี่ยงออก batch

11) ConcurrencyFailureException (Spring)

คืออะไร: ปัญหา concurrency เช่น deadlock/lock timeout
วิธีแก้: retry ด้วย backoff สำหรับงาน idempotent

Get Chiwa Kantawong (Pea)’s stories in your inbox

Join Medium for free to get updates from this writer.

ตัวอย่างแนวทาง

for (int i=0; i<3; i++) {
try {
service.doWork();
break;
}
catch (ConcurrencyFailureException e){ Thread.sleep(100L * (i+1)); }
}

กันไว้: ลด long-running tx, เลือก isolation/lock ให้เหมาะ

12) ConstraintViolationException (Hibernate)

คืออะไร: DB constraint fail (ตัว Hibernate เอง)
วิธีแก้: map เป็น 409/400 คล้าย DataIntegrityViolationException

ตัวอย่าง handler

@ExceptionHandler(org.hibernate.exception.ConstraintViolationException.class)
ResponseEntity<?> h(org.hibernate.exception.ConstraintViolationException e){
return ResponseEntity.status(409).body(Map.of("constraint", e.getConstraintName()));
}

กันไว้: constraint ที่ DB + validation บน

13) EntityNotFoundException (JPA)

คืออะไร: อ้าง entity ที่ไม่มีจริง
ทำไมเจอ: getReference() ที่ lazy proxy แล้วไม่มีตัวตน

ตัวอย่างพัง/แก้

User u = em.find(User.class, id);
if (u == null) throw new EntityNotFoundException("User not found");

กันไว้: ใช้ findById() ที่คืน Optional แล้วเช็คก่อน

14) NoSuchBeanDefinitionException (Spring)

คืออะไร: หา bean ไม่เจอใน context
ทำไมเจอ: ลืม @Service/@Component, scan path ผิด

ตัวอย่างพัง/แก้

@Service 
class UserService { }
// @SpringBootApplication(scanBasePackages="com.zengcode") ให้โดนสแกนแน่ ๆ

กันไว้: ใช้ test context load (@SpringBootTest) ใน CI

15) BeanCreationException (Spring)

คืออะไร: bean สร้างไม่สำเร็จตอน startup
ทำไมเจอ: circular dependency, property config ผิด

ตัวอย่างพัง/แก้ (เลี่ยงวงจร)

เคสฮิตมาก ๆ คือ circular dependency = Bean A ต้องการ Bean B แต่ Bean B ก็ดันต้องการ Bean A อีกที → Spring งง ไม่รู้จะสร้างใครก่อน

อีกเคส ที่เจอบ่อยคือ config ผิด เช่น property ที่จำเป็นไม่มีค่า หรือ constructor ที่บังคับพารามิเตอร์ แต่ Spring หาค่าให้ไม่ได้

@Service 
class A {
A(B b){}
}

@Service
class B {
B(A a){}
}
// แก้: เปลี่ยนเป็น interface แยก หรือใช้ @Lazy/Provider ฉีดเฉพาะที่จำเป็น

กันไว้: constructor injection อย่างมีแบบแผน, แยก config ออก

  1. ทบทวนการออกแบบ — ส่วนมากการที่ A รู้จัก B และ B ก็รู้จัก A → คือ code smell → ควรแตกออกเป็น interface หรือ service ชั้นกลาง
  2. ถ้าเลี่ยงไม่ได้จริง ๆ:

3. ใช้ @Lazy ให้ Spring ฉีดตอนใช้งานจริง ไม่ใช่ตอน start

16) ApplicationContextException (Spring)

คืออะไร: context โหลดไม่ขึ้น
ทำไมเจอ: profile/env ผิด, bean fail

แนวแก้: ตรวจ application-*.yml, เปิด log startup, เขียน integration test โหลด context

17) AccessDeniedException (Spring Security)

คืออะไร: ไม่มีสิทธิ์เข้าถึง
ทำไมเจอ: role/authority ไม่พอ

ตัวอย่าง handler (403)

@ExceptionHandler(AccessDeniedException.class)
ResponseEntity<?> denied(){ return ResponseEntity.status(403).build(); }

กันไว้: ใส่ @PreAuthorize("hasRole('ADMIN')") ให้ชัด + test ด้วย mock user

18) AuthenticationException (Spring Security)

คืออะไร: auth fail (token invalid/expired, credential ผิด)
วิธีแก้: handler → 401, ชี้สาเหตุคร่าว ๆ

ตัวอย่าง

@ExceptionHandler(AuthenticationException.class)
ResponseEntity<?> auth(AuthenticationException e){
return ResponseEntity.status(401).body(Map.of("error","auth failed"));
}

กันไว้: จัดการ token expiry/refresh ให้ถูกต้อง

19) SQLException (JDBC)

คืออะไร: error จาก driver/connection/SQL
ทำไมเจอ: DB down, syntax ผิด

ตัวอย่าง handler

@ExceptionHandler(SQLException.class)
ResponseEntity<?> sql(SQLException e){
return ResponseEntity.status(500)
.body(Map.of("state", e.getSQLState(), "code", e.getErrorCode()));
}

กันไว้: ใช้ connection pool ดี ๆ, ตรวจ health, lint/validate SQL ก่อน deploy

20) Checked vs Unchecked + Rollback Rules

คืออะไร: Spring @Transactional จะ rollback อัตโนมัติแค่ Unchecked (RuntimeException)
ทริก: ถ้าต้องการ rollback Checked ด้วย → ระบุ rollbackFor

ตัวอย่าง

@Transactional(rollbackFor = Exception.class)
public void importCsv() throws Exception { /* ถ้าโยน IOException ก็ rollback */ }

กันไว้: ให้เมธอด service โยน unchecked ที่จำเป็น หรือกำหนด policy ชัดเจนใน annotation

Global Exception Handler (แนะนำให้มี)

  • ใช้ @RestControllerAdvice รวมศูนย์ mapping → JSON ที่ frontend อ่านง่าย
  • Mapping ไว้คร่าว ๆ: NPE→500, DataIntegrity→409, LazyInit→500/แก้query, MethodArgumentNotValid→400, HttpMessageNotReadable→400, OptimisticLock→409

Checklist โปรดักชัน

  • ✅ เปิด Bean Validation (@Valid, @Validated)
  • ✅ Unique/FK constraint ใน DB จริง ๆ
  • ✅ ใช้ @Transactional ที่ service, readOnly=true สำหรับ read
  • ✅ ใช้ fetch join / DTO projection ลด LazyInitializationException
  • ✅ ใส่ @Version กัน race
  • ✅ จุดเสี่ยงทำ retry + ตั้ง timeout
  • ✅ Global Exception Handler ส่งรูปแบบ error มาตรฐาน
  • ✅ Integration Test: context load, security flow, SQL correctness

--

--

Chiwa Kantawong (Pea)
Chiwa Kantawong (Pea)

Written by Chiwa Kantawong (Pea)

Software Engineering บนดาวเคราะห์ใบนึง Youtube : https://www.youtube.com/@ThaiJavaZone