공공부하자개발 · 영어 학습 노트
자바
명령어자주 쓰는 API 참조0/8 완료
  • 01String · StringBuilder · 정규식
  • 02Collections · Arrays · Map
  • 03Stream · Collectors · 함수형 인터페이스
  • 04Files · Path · I/O
  • 05java.time · BigDecimal · 기타 유틸
  • 06Oracle SQL
  • 07예외 · 디버깅
  • 08Spring Boot 설정 키
사이트 소개개인정보처리방침연락처
© 2026 공부하자
홈 › 명령어 › 07 / 8

예외 · 디버깅

예외 계층 · 처리 패턴 · 스택트레이스 읽기 · 디버거 · 진단 도구
섹션 15진행 0 / 8

예외 · 디버깅 명령어

예외 계층, try/catch/throw 규칙, Spring/MyBatis 예외 처리, 스택트레이스 읽기, IntelliJ 디버거, jstack/jmap/jcmd 같은 JDK 진단 도구를 한 페이지에 모았습니다. Ctrl+F로 예외 이름(예: ConcurrentModificationException, jstack, Evaluate Expression)을 검색하면 됩니다. 원리는 중급 03 예외 처리 레슨에서 다루므로 여기서는 바로 찾아 쓰는 표와 코드만 담습니다.

01한눈에 보기
항목 설명 예제 결과
throw e; 원래 예외 재던지기 catch (IOException e) { throw e; } 스택트레이스 보존
new X("msg", e) 원인 보존 래핑 new IllegalStateException("실패", e) getCause()로 원인 조회
Objects.requireNonNull(x, msg) null 검사 requireNonNull(id, "id null") NullPointerException: id null
Optional.orElseThrow(...) 없으면 예외 opt.orElseThrow(() -> new NotFound()) 커스텀 예외 던짐
try (AutoCloseable r = ...) 자원 자동 해제 try (var c = ds.getConnection()) finally 없이 close
getSuppressed() 숨겨진 예외 조회 e.getSuppressed() close 중 예외 배열
Throwable.getCause() 원인 예외 e.getCause() 최초 원인 반환
e.getMessage() 메시지만 e.getMessage() "id null"
e.toString() 클래스+메시지 e.toString() "java.lang.NullPointerException: id null"
Thread.currentThread().getStackTrace() 현재 스택 [0]은 이 메서드 자신 StackTraceElement[]
assertThrows(X.class, () -> ...) 예외 발생 검증 JUnit 테스트 통과/실패
@RestControllerAdvice 전역 예외 처리 컨트롤러 밖에서 잡기 응답 규격 통일
@Transactional(rollbackFor = X.class) checked도 롤백 기본은 RuntimeException만 커밋/롤백 결정
jstack <pid> 스레드 덤프 데드락 감지 Found one Java-level deadlock
jmap -histo:live <pid> 객체 수 상위 메모리 누수 의심 시 클래스별 인스턴스 수
-XX:+HeapDumpOnOutOfMemoryError OOM 시 덤프 JVM 옵션 .hprof 파일 생성
조건부 브레이크포인트 특정 값에서만 정지 id == 42 그 값일 때만 멈춤
로그 브레이크포인트 멈추지 않고 출력 콘솔에 값 출력 실행 흐름 유지
Evaluate Expression 정지 상태에서 식 실행 list.size() 즉시 값 확인
-agentlib:jdwp=... 원격 디버그 포트 address=*:5005 운영에서는 금지
02예외 계층
text
Throwable
├─ Error (잡지 않음, 복구 불가)
│   ├─ OutOfMemoryError
│   └─ StackOverflowError
└─ Exception
    ├─ checked (컴파일러가 처리 강제)
    │   ├─ IOException
    │   └─ SQLException
    └─ RuntimeException (unchecked)
        ├─ NullPointerException
        ├─ IllegalArgumentException
        ├─ IllegalStateException
        ├─ IndexOutOfBoundsException
        ├─ ClassCastException
        ├─ NumberFormatException (IllegalArgumentException 하위)
        ├─ UnsupportedOperationException
        ├─ ConcurrentModificationException
        └─ ArithmeticException

각 예외가 언제 나는지와 재현 코드입니다.

예외 언제 발생 재현 코드 한 줄
NullPointerException null 참조 호출 String s = null; s.length();
IllegalArgumentException 잘못된 인자값 LocalDate.of(2024, 13, 1)
IllegalStateException 객체 상태가 부적절 stream.forEach(x->{}); stream.count();
IndexOutOfBoundsException 범위 밖 인덱스 List.of(1,2).get(5)
ClassCastException 잘못된 형변환 (String)(Object)(Integer)1
NumberFormatException 숫자 파싱 실패 Integer.parseInt("12a")
UnsupportedOperationException 불변 컬렉션 수정 List.of(1).add(2)
ConcurrentModificationException 반복 중 컬렉션 수정 for(var x:list) list.remove(x);
ArithmeticException 산술 오류 1 / 0
OutOfMemoryError 힙/Metaspace 고갈 무한 리스트 누적
StackOverflowError 재귀 무한 호출 void f(){f();}

Spring 은 데이터 접근 예외를 DataAccessException(unchecked) 계층으로 감쌉니다. SQLException(checked)을 그대로 던지지 않고 이 계층으로 번역합니다. MyBatis 는 내부 오류를 PersistenceException으로 감싸고, Spring 연동 시 다시 DataAccessException으로 변환됩니다.

03try/catch/finally 규칙

catch 순서는 구체적 예외를 먼저, 일반 예외를 나중에 씁니다.

java
try {
    process();
} catch (IllegalArgumentException e) {   // 구체적
    log.warn("잘못된 인자", e);
} catch (RuntimeException e) {           // 일반적, 뒤에
    log.error("처리 실패", e);
}
// → 순서 반대면 컴파일 에러(이미 잡힌 catch 이후 도달 불가)

multi-catch는 관련 없는 예외를 한 줄로 처리할 때 씁니다.

java
try {
    convert();
} catch (NumberFormatException | DateTimeParseException e) {
    log.warn("입력 형식 오류: {}", e.getMessage());
}

finally 에서 return 하면 try/catch 의 반환값을 덮어써서 버그가 됩니다.

java
static int bad() {
    try {
        return 1;
    } finally {
        return 2;   // → 항상 2 반환, try의 1은 버려짐. 절대 금지
    }
}

try-with-resources 는 여러 자원을 선언 역순으로 닫습니다.

java
try (var a = openA(); var b = openB()) {
    use(a, b);
}
// → 닫는 순서: b.close() 먼저, a.close() 나중
// b.close()에서 예외가 나면 a.close()의 예외는 suppressed로 붙는다

e.getSuppressed()로 숨겨진 예외를 확인합니다.

java
try {
    /* 위 코드에서 예외 발생 시 */
} catch (Exception e) {
    for (var s : e.getSuppressed()) log.warn("suppressed: {}", s.toString());
}

catch 블록의 변수는 그 블록 안에서만 유효합니다. 같은 이름을 여러 catch에서 재사용해도 서로 다른 변수입니다.

04던지기: throw vs throws, 래핑, 보존

throw는 실제로 던지는 문장, throws는 메서드 선언에 붙이는 표시입니다.

java
void save(String id) throws SQLException {   // 선언: 호출자에게 알림
    if (id == null) throw new IllegalArgumentException("id null");  // 실행
}

checked 예외를 unchecked로 래핑할 때는 원인을 반드시 함께 넘깁니다.

java
try {
    repo.save(entity);
} catch (SQLException e) {
    throw new IllegalStateException("저장 실패: id=" + entity.getId(), e);
    // → 두 번째 인자 e가 원인(cause) 보존. 빠뜨리면 원인 추적 불가
}

계층 경계(컨트롤러/서비스/레포지토리)에서는 하위 계층 예외를 도메인 예외로 번역합니다.

java
try {
    return mapper.selectUser(id);
} catch (PersistenceException e) {
    throw new UserNotFoundException("user id=" + id, e);   // 예외 번역
}

그대로 다시 던질 때는 throw e;로 스택트레이스를 보존합니다. 새 예외로 다시 만들면 원래 발생 위치 정보가 사라집니다. 예외 메시지에는 반드시 어떤 id·값에서 났는지 컨텍스트를 넣습니다.

java
throw new IllegalArgumentException("orderId=" + orderId + " status=" + status + " 는 취소 불가");
05커스텀 예외 패턴

비즈니스 예외는 보통 하나로 두고, 에러 코드로 종류를 구분합니다.

java
public enum ErrorCode {
    USER_NOT_FOUND(404, "사용자를 찾을 수 없습니다"),
    DUPLICATE_EMAIL(409, "이미 등록된 이메일입니다"),
    INVALID_STATE(400, "잘못된 상태입니다");

    final int httpStatus;
    final String message;
    ErrorCode(int httpStatus, String message) {
        this.httpStatus = httpStatus;
        this.message = message;
    }
}

생성자는 보통 4종을 둡니다: 코드만, 코드+상세, 코드+원인, 코드+상세+원인.

java
public class BusinessException extends RuntimeException {
    private final ErrorCode errorCode;
    private final String detail;

    public BusinessException(ErrorCode c) { this(c, null, null); }
    public BusinessException(ErrorCode c, String detail) { this(c, detail, null); }
    public BusinessException(ErrorCode c, Throwable cause) { this(c, null, cause); }
    public BusinessException(ErrorCode c, String detail, Throwable cause) {
        super(c.message + (detail != null ? " (" + detail + ")" : ""), cause);
        this.errorCode = c;
        this.detail = detail;
    }
    public ErrorCode getErrorCode() { return errorCode; }
}
// → throw new BusinessException(ErrorCode.USER_NOT_FOUND, "id=" + id);

checked 커스텀 예외는 거의 만들지 않습니다. 호출자에게 강제로 처리시켜야 할 뚜렷한 이유(재시도 필수 등)가 없으면 unchecked로 둡니다. 예외 계층은 2단(최상위 하나 + 세부 하나) 이내로 유지합니다.

06스택트레이스 읽기
text
java.lang.IllegalStateException: 주문 처리 실패: orderId=1001
    at com.company.order.OrderService.process(OrderService.java:42)
    at com.company.order.OrderController.create(OrderController.java:20)
    at jdk.internal.reflect.GeneratedMethodAccessor12.invoke(Unknown Source)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
Caused by: java.sql.SQLException: ORA-00001: unique constraint violated
    at com.company.order.OrderMapper.insert(OrderMapper.java:15)
    at com.company.order.OrderService.process(OrderService.java:40)
    ... 23 more
  1. 맨 아래 Caused by가 진짜 원인입니다. 여기서는 ORA-00001 유니크 제약 위반이 근본 원인입니다.
  2. Caused by 블록에서 내 패키지(com.company...)가 등장하는 첫 줄이 실제 문제 위치입니다.
  3. ... 23 more는 위 스택과 겹치는 하위 프레임 23개를 생략했다는 뜻입니다.
  4. jdk.internal.reflect..., Method.invoke 같은 리플렉션/프록시 프레임은 대부분 건너뛰어도 됩니다.
  5. Spring AOP 프록시 프레임($SpringCGLIB, ReflectiveMethodInvocation)도 마찬가지로 건너뛰고 그 아래 실제 호출을 봅니다.

e.printStackTrace()는 표준출력으로 나가 로그 시스템에 안 남으므로 대신 로거를 씁니다.

java
log.error("주문 처리 실패: orderId={}", orderId, e);   // e를 마지막 인자로

getStackTrace()[0]은 예외가 던져진 최초 위치입니다. toString()은 클래스명: 메시지, getMessage()는 메시지만 반환합니다.

07자주 만나는 예외별 원인·해결
예외 흔한 원인 해결
NullPointerException 메시지의 "because ... is null" 부분 읽기 Helpful NPE로 어떤 변수인지 확인
NumberFormatException 입력에 공백·콤마 포함 s.trim().replace(",", "") 후 파싱
IndexOutOfBoundsException size와 마지막 인덱스 혼동 반복 전 size() > 0 확인
ClassCastException 제네릭 소거, JSON 역직렬화 타입 불일치 대상 타입을 명시적으로 지정
ConcurrentModificationException 반복 중 컬렉션 삭제 list.removeIf(cond) 또는 Iterator.remove()
ArithmeticException 정수 나눗셈 0, BigDecimal 무한소수 0 검사, divide(x, scale, mode)
DateTimeParseException 포맷 문자열이 입력과 불일치 실제 값 형식에 맞는 패턴 확인
UnsupportedOperationException List.of() 등 불변 리스트 수정 new ArrayList<>(List.of(...))
IllegalStateException 스트림을 두 번 소비 매번 새 스트림 생성
OutOfMemoryError 힙 고갈 vs Metaspace 고갈 메시지의 "Java heap space" vs "Metaspace" 구분
StackOverflowError 무한 재귀, toString() 순환 참조 종료 조건 확인, toString에서 순환 필드 제외

Helpful NPE 메시지 예시입니다.

text
Cannot invoke "String.length()" because "s" is null

Helpful NullPointerException은 JDK 15+ 기본 활성화이며 어떤 변수가 null인지 알려줍니다.

BigDecimal 무한소수 예시입니다.

java
new BigDecimal("10").divide(new BigDecimal("3"));
// → ArithmeticException: Non-terminating decimal expansion
new BigDecimal("10").divide(new BigDecimal("3"), 2, RoundingMode.HALF_UP);
// → 3.33

자주 만나는 ORA 코드입니다.

ORA 코드 의미
ORA-00001 유니크 제약 위반
ORA-01400 NOT NULL 컬럼에 null 삽입
ORA-01722 문자를 숫자로 변환 실패
ORA-00942 테이블 또는 뷰가 없음
ORA-01000 오픈 커서 초과
ORA-00060 데드락 감지
08Spring / MyBatis 예외 처리

전역 예외 처리 골격입니다.

java
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<ErrorResponse> handle(BusinessException e) {
        var code = e.getErrorCode();
        return ResponseEntity.status(code.httpStatus)
                .body(new ErrorResponse(code.name(), e.getMessage()));
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponse> handle(MethodArgumentNotValidException e) {
        var msg = e.getBindingResult().getFieldErrors().get(0).getDefaultMessage();
        return ResponseEntity.badRequest().body(new ErrorResponse("VALIDATION", msg));
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handle(Exception e) {
        String traceId = MDC.get("traceId");
        log.error("처리되지 않은 예외 traceId={}", traceId, e);
        return ResponseEntity.internalServerError()
                .body(new ErrorResponse("INTERNAL", "서버 오류, traceId=" + traceId));
    }
}

@Transactional은 기본적으로 RuntimeException(및 Error)만 롤백합니다. checked 예외까지 롤백하려면 rollbackFor를 명시합니다.

java
@Transactional(rollbackFor = Exception.class)
void transfer(...) throws Exception { ... }

트랜잭션 안에서 예외를 catch 후 삼키면 트랜잭션이 정상 커밋됩니다.

java
@Transactional
void bad() {
    try {
        repo.save(entity);
    } catch (Exception e) {
        log.warn("저장 실패", e);   // → 삼켜짐, 트랜잭션은 그대로 커밋된다
    }
}
// 반드시 다시 던지거나 setRollbackOnly() 호출

MyBatis 예외는 Spring 연동 시 PersistenceExceptionTranslator가 DataAccessException으로 자동 변환합니다. 호출 코드에서는 SQLException이 아니라 DataAccessException 하위 타입을 잡습니다.

java
try {
    mapper.insert(user);
} catch (DuplicateKeyException e) {   // ORA-00001이 여기로 매핑
    throw new BusinessException(ErrorCode.DUPLICATE_EMAIL, e);
}
09null 다루기
java
Objects.requireNonNull(id, "id must not be null");
// → null이면 NullPointerException: id must not be null

String name = Objects.requireNonNullElse(input, "default");
// → input이 null이면 "default"

Optional<User> opt = repo.findById(id);
opt.orElse(guestUser);                              // 기본값(즉시 계산됨)
opt.orElseGet(() -> buildGuestUser());               // 기본값(지연 계산)
opt.orElseThrow(() -> new UserNotFoundException(id));// 없으면 예외

방어적 null 체크는 메서드 경계(공개 API 진입점)에서만 하고 내부에서는 반복하지 않습니다. @Nullable/@NonNull 주석은 관례상 파라미터·반환값이 null 가능한지 표시합니다.

NPE 방지 체크리스트입니다.

  • 외부 입력(요청 파라미터, DB 조회 결과)은 진입점에서 null 검사
  • Map.get() 결과는 곧바로 쓰지 말고 존재 확인
  • 메서드가 컬렉션을 반환할 때 null 대신 빈 컬렉션 반환
  • equals()는 Objects.equals(a, b)로 null-safe 비교
  • 필드 초기화를 생성자에서 빠뜨리지 않았는지 확인
10디버거 (IntelliJ 기준)
기능 IntelliJ VSCode
브레이크포인트 라인 번호 옆 클릭 라인 번호 옆 클릭
조건부 브레이크포인트 우클릭 → Condition에 id == 42 우클릭 → Edit Breakpoint → Condition
로그 브레이크포인트 Condition 옆 "Evaluate and log" Edit Breakpoint → Log Message
예외 브레이크포인트 Run → View Breakpoints → Java Exception Breakpoints Java: Add Exception Breakpoint
Step Over/Into/Out F8/F7/Shift+F8 F10/F11/Shift+F11
Run to Cursor Alt+F9 Ctrl+F10
Evaluate Expression Alt+F8 디버그 콘솔에 직접 입력
변수 값 바꾸기 Variables 창에서 값 더블클릭(Set Value) Variables에서 값 편집
Drop Frame Debug 창 툴바 별도 지원 약함, 재시작 권장

예외 브레이크포인트는 특정 예외(예: NPE)가 던져지는 즉시 멈춥니다. 어디서 catch 되든 상관없이 최초 발생 지점에서 멈추므로 원인 추적에 유용합니다.

Evaluate Expression은 중단 상태에서 임의의 식을 즉시 실행해 값을 확인합니다.

text
Evaluate: list.stream().filter(x -> x.getId() == 42).count()

Drop Frame은 현재 스택 프레임을 버리고 호출 이전 상태로 되돌려 재실행합니다. 호출 부작용(DB 저장 등)이 이미 일어났다면 중복 실행 위험이 있어 주의합니다.

원격 디버그는 JVM에 다음 옵션을 붙여 포트를 엽니다.

bash
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 -jar app.jar

suspend=n은 디버거 연결 전에도 앱이 먼저 실행됨을 의미합니다. 이 포트는 인증 없이 코드 실행이 가능하므로 운영 환경에는 절대 열지 않습니다.

11로그로 디버깅

로그는 입력값, 분기 결과, 외부 호출(DB/API) 전후에 남깁니다.

java
log.debug("조회 시작 userId={}", userId);
var user = repo.findById(userId).orElseThrow();
log.debug("조회 완료 status={}", user.getStatus());

레벨은 ERROR(장애), WARN(비정상이지만 계속 가능), INFO(주요 흐름), DEBUG(상세 값) 순으로 선택합니다. 파라미터는 문자열 연결 대신 자리표시자({})를 씁니다.

java
log.info("주문 생성 orderId={} amount={}", orderId, amount);   // 문자열 연결 금지

MDC로 요청 단위 traceId를 남기면 로그 여러 줄을 하나의 요청으로 묶을 수 있습니다.

java
MDC.put("traceId", UUID.randomUUID().toString());
// ... 요청 처리 ...
MDC.clear();   // 스레드 재사용 대비 필수

조건부 DEBUG는 if (log.isDebugEnabled())로 비용이 큰 로그 생성을 막습니다. 운영 로그에서 문제를 재현하는 절차입니다.

  1. 에러 로그에서 traceId 확인
  2. 같은 traceId의 앞뒤 로그로 요청 파라미터 복원
  3. 로컬에서 동일 파라미터로 재현 시도
  4. 안 되면 DB 데이터 상태(락, 상태값)까지 복원
12JDK 진단 도구
명령 용도
jps 실행 중인 JVM 프로세스 목록과 PID
jstack <pid> 스레드 덤프, 데드락 감지
jmap -histo:live <pid> 클래스별 살아있는 객체 수 상위 목록
jmap -dump:live,format=b,file=heap.hprof <pid> 힙 덤프 파일 생성
jcmd <pid> GC.heap_info 힙 영역별 사용량
jcmd <pid> Thread.print jstack과 동일한 스레드 덤프
jcmd <pid> VM.flags 적용된 JVM 옵션 확인
jcmd <pid> GC.run Full GC 강제 실행
jstat -gcutil <pid> 1000 1초 간격 GC 영역별 사용률
jconsole / jvisualvm GUI 모니터링, 폐쇄망 반입 가능
bash
C:\project\jdk-21.0.8\bin\jstack 12345 > thread_dump.txt

데드락이 있으면 다음과 같이 표시됩니다.

text
Found one Java-level deadlock:
"Thread-1": waiting to lock monitor 0x000... (a java.lang.Object),
  which is held by "Thread-0"

스레드 덤프에서 확인할 항목입니다.

  • BLOCKED 상태 스레드: 락을 기다리는 중, waiting to lock 뒤의 대상 확인
  • RUNNABLE이 수백 개면 스택 상단 메서드로 무슨 작업(파싱, 정렬 등) 중인지 추정
  • 같은 락을 서로 기다리면 데드락, 메시지가 명시적으로 알려줌

VisualVM은 JDK 기본 제공이 아니라 별도 다운로드가 필요하며, 폐쇄망은 사전 반입이 필요합니다. 힙 덤프(.hprof) 상세 분석은 MAT(Eclipse Memory Analyzer)를 별도 설치해 사용합니다.

13자주 쓰는 JVM 옵션
bash
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=C:\dumps
# → OOM 발생 시 자동으로 힙 덤프 생성

-Xlog:gc*:file=gc.log
# → GC 로그를 파일로 기록

-XX:+ExitOnOutOfMemoryError
# → OOM 시 프로세스 즉시 종료(재기동 스크립트와 병행)

-Dfile.encoding=UTF-8
# → 파일/콘솔 인코딩 고정, 폐쇄망 한글 깨짐 방지

-ea
# → assert 문 활성화(기본은 비활성)

-verbose:class
# → 클래스 로딩 과정 출력, 클래스 충돌 진단

-XX:+PrintFlagsFinal
# → 전체 JVM 옵션 값 출력, grep으로 필터링
bash
C:\project\jdk-21.0.8\bin\java -XX:+PrintFlagsFinal -version | grep -i heapsize
14테스트로 재현·고정

버그를 고치기 전에 재현 테스트를 먼저 작성합니다.

java
@Test
void 존재하지않는사용자조회시예외() {
    assertThrows(UserNotFoundException.class, () -> service.getUser(-1L));
}

경계값(0, 음수, 최대값, 빈 문자열, null)을 먼저 테스트합니다. 스택트레이스의 파라미터 값(로그에 남은 id, 입력값)을 그대로 테스트 입력으로 씁니다.

java
@ParameterizedTest
@ValueSource(strings = {"", " ", "abc"})
void 잘못된형식이면예외(String input) {
    assertThrows(NumberFormatException.class, () -> Integer.parseInt(input.trim()));
}

수정 후에는 같은 테스트가 통과하는지 재실행해 회귀를 막습니다. 자동화된 회귀 테스트 구성은 실무 확장 12 레슨에서 다룹니다.

15디버깅 체크리스트

버그를 만나면 다음 순서로 좁혀갑니다.

  1. 증상 확인(에러 메시지, 재현 빈도)
  2. 최근 변경 사항 확인(git log, 배포 이력)
  3. 로그에서 traceId로 요청 전체 흐름 확인
  4. 스택트레이스 맨 아래 Caused by에서 진짜 원인 찾기
  5. 최소 재현 코드 작성
  6. 가설을 하나씩만 세우고 검증
  7. 수정 후 회귀 테스트 실행

"내 컴퓨터에서는 되는데 운영에서는 다르다" 계열은 환경 차이를 먼저 확인합니다.

차이 항목 확인 방법
인코딩 file.encoding 값, DB 커넥션 문자셋
시간대 user.timezone, DB 서버 시간대
로케일 Locale.getDefault(), 숫자/날짜 포맷 영향
JDK 버전 java -version, 배포 서버와 개발 환경 일치 여부
활성 프로파일 spring.profiles.active 설정값

이 표의 항목이 개발 환경과 운영 환경에서 다르면 같은 코드가 다르게 동작할 수 있습니다.

목차
  • 한눈에 보기
  • 예외 계층
  • try/catch/finally 규칙
  • 던지기: throw vs throws, 래핑, 보존
  • 커스텀 예외 패턴
  • 스택트레이스 읽기
  • 자주 만나는 예외별 원인·해결
  • Spring / MyBatis 예외 처리
  • null 다루기
  • 디버거 (IntelliJ 기준)
  • 로그로 디버깅
  • JDK 진단 도구
  • 자주 쓰는 JVM 옵션
  • 테스트로 재현·고정
  • 디버깅 체크리스트
참고 영상 · 인터넷 연결 시 유튜브 검색이 열립니다자바 예외 · 디버깅명령어 예외 Exception RuntimeException
이전06 Oracle SQL다음08 Spring Boot 설정 키