Java/Spring Exception Cheat Sheet
รวม 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 ออก
- ทบทวนการออกแบบ — ส่วนมากการที่ A รู้จัก B และ B ก็รู้จัก A → คือ code smell → ควรแตกออกเป็น interface หรือ service ชั้นกลาง
- ถ้าเลี่ยงไม่ได้จริง ๆ:
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









