본문 바로가기

Java

오류 정리

Cannot find a Java installation on your machine

Gradle 툴체인 설정 오류

그레들이 내 컴퓨터에서 프로젝트에 설정된 자바 17버전을 찾지 못했다.

원인

JDK 21을 설치하고 17로 생성
build.gradle 파일에는 자바 17로 생성한다고 명시
Gradle은 설정을 보고 프로젝트 빌드를 위한 컴퓨터에 설치된 Java 17 JDK를 찾는다
그러나 컴퓨터는 JDK 21만 찾고 JDK 17을 찾지 못해서 발생한 에러

해결방법

Gradle이 JDK자동으로 다운로드하게 설정

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}

Cannot resolve property 'withUser

EntityGraph로 리팩토리 중 발생

@Query("SELECT t FROM Todo t " +  
        "LEFT JOIN FETCH t.user " +  
        "WHERE t.id = :todoId")  
Optional<Todo> findByIdWithUser(@Param("todoId") Long todoId);

원인

JPA의 쿼리 메소드 동작 방식
JPA는 특정 키워드로 시작하는 메소드를 직접 파싱해서 JPQL을 자동으로 생성
find...By : select 쿼리 생성
Id : 조건절 where에 id 사용
WithUser : 엔티티에서 WithUser라는 필드를 사용
결국 Spring Data JPA는 WHERE t.id = ? AND t.withUser = ? 같은 쿼리를 만들려고 시도하다가 withUser라는 프로퍼티(필드)를 찾을 수 없어서 오류를 발생시키는 것

해결

findBy 메소드 오버라이딩

@Override
@EntityGraph(attributePaths = {"user"})
Optional<Todo> findById(Long todoId);

Error processing condition on org.springdoc.webmvc.ui.SwaggerConfig.springWebProvider

swagger 설정

springdoc 에서 오류 발생

원인

springdoc과 spring boot의 호환되는 버전이 맞지 않아 발생
https://springdoc.org/#what-is-the-compatibility-matrix-of-springdoc-openapi-with-spring-boot
3.3.3 버전은 2.6.0 버전을 사용해야 한다.

해결

버전에 호환되는 springdoc 의존성 변경


Not annotated method overrides method annotated with @NonNullApi

findById 오버라이딩

**findById 오버라이딩 중 오류 발생

원인

@NonNullApi로 어노테이션이 붙은 패키지의 메소드를 재정의(override)하는 당신의 메소드에는, 관련 어노테이션이 빠져있어서 발생
@NonNullApi

  • 파라미터 : null 값을 전달하면 안된다.
  • 반환값 : null을 반환해서는 안된다.

해결

메소드 파라미터 부분에 @NonNull 어노테이션

@Override  
@NonNull  
@EntityGraph(attributePaths = {"user"})  
Optional<Todo> findById(@NonNull Long todoId);  
int countById(Long todoId);

Swagger Failed to load remote configuration

Swagger 연결

swagger api 문서에 접근할 때 에러 발생

원인

API 명세 데이터 요청 (실패)
페이지가 로드된 후 swagger은 화면에 API목록을 그리기 위해 백그라운드에 /v3/api-docs/swagger-config 또는 /v3/api-docs와 같은 URL로 추가적인 데이터를 요청한다
이 경로에 JWTFilter의 예외목록에 걸려 오류 발생
undefined /api-docs/swagger-config
yml과 설정 관계

springdoc:
  api-docs:
    path: /api-docs

이 명세 때문에 API 명세서의 기본 경로를 /v3/api-docs가 아닌 /api-docs로 사용한다.

해결

JwtFilter 조건 수정

if (url.startsWith("/auth") ||
url.startsWith("/swagger-ui") || // Swagger UI 페이지 및 관련 리소스
url.startsWith("/api-docs")) { // Swagger API 명세서 데이터 관련 모든 경로
chain.doFilter(request, response); return; }

Whitelabel Error Page This application has no explicit mapping for error, so you are seeing this as a fallback.

/swagger-ui/index.html

API문서 접근 중 홈페이지에서 오류 발생

원인

JWT 필터 설정

package org.example.expert.config;  

import io.jsonwebtoken.Claims;  
import io.jsonwebtoken.ExpiredJwtException;  
import io.jsonwebtoken.MalformedJwtException;  
import io.jsonwebtoken.UnsupportedJwtException;  
import jakarta.servlet.FilterConfig;  
import jakarta.servlet.*;  
import jakarta.servlet.http.HttpServletRequest;  
import jakarta.servlet.http.HttpServletResponse;  
import lombok.RequiredArgsConstructor;  
import lombok.extern.slf4j.Slf4j;  
import org.example.expert.domain.user.enums.UserRole;  

import java.io.IOException;  

@Slf4j  
@RequiredArgsConstructor  
public class JwtFilter implements Filter {  

    private final JwtUtil jwtUtil;  

    @Override  
    public void init(FilterConfig filterConfig) throws ServletException {  
        Filter.super.init(filterConfig);  
    }  

    @Override  
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {  
        HttpServletRequest httpRequest = (HttpServletRequest) request;  
        HttpServletResponse httpResponse = (HttpServletResponse) response;  

        String url = httpRequest.getRequestURI();  
        //경로는 통과시킨다
        if (url.startsWith("/auth") || url.startsWith("/swagger-ui") || url.startsWith("/api-docs")) {  
            chain.doFilter(request, response);  
            return;  
        }  
        //그 외 모든 요청에 대해 Authroization 헤더를 확인한다.
        String bearerJwt = httpRequest.getHeader("Authorization");  

        if (bearerJwt == null) {  
            // 토큰이 없는 경우 400을 반환합니다.  
            httpResponse.sendError(HttpServletResponse.SC_BAD_REQUEST, "JWT 토큰이 필요합니다.");  
            return;  
        }  

        String jwt = jwtUtil.substringToken(bearerJwt);  

        try {  
            // JWT 유효성 검사와 claims 추출  
            Claims claims = jwtUtil.extractClaims(jwt);  
            if (claims == null) {  
                httpResponse.sendError(HttpServletResponse.SC_BAD_REQUEST, "잘못된 JWT 토큰입니다.");  
                return;  
            }  

            UserRole userRole = UserRole.valueOf(claims.get("userRole", String.class));  

            httpRequest.setAttribute("userId", Long.parseLong(claims.getSubject()));  
            httpRequest.setAttribute("email", claims.get("email"));  
            httpRequest.setAttribute("userRole", claims.get("userRole"));  

            if (url.startsWith("/admin")) {  
                // 관리자 권한이 없는 경우 403을 반환합니다.  
                if (!UserRole.ADMIN.equals(userRole)) {  
                    httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN, "관리자 권한이 없습니다.");  
                    return;  
                }  
                chain.doFilter(request, response);  
                return;  
            }  

            chain.doFilter(request, response);  
        } catch (SecurityException | MalformedJwtException e) {  
            log.error("Invalid JWT signature, 유효하지 않는 JWT 서명 입니다.", e);  
            httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "유효하지 않는 JWT 서명입니다.");  
        } catch (ExpiredJwtException e) {  
            log.error("Expired JWT token, 만료된 JWT token 입니다.", e);  
            httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "만료된 JWT 토큰입니다.");  
        } catch (UnsupportedJwtException e) {  
            log.error("Unsupported JWT token, 지원되지 않는 JWT 토큰 입니다.", e);  
            httpResponse.sendError(HttpServletResponse.SC_BAD_REQUEST, "지원되지 않는 JWT 토큰입니다.");  
        } catch (Exception e) {  
            log.error("Invalid JWT token, 유효하지 않는 JWT 토큰 입니다.", e);  
            httpResponse.sendError(HttpServletResponse.SC_BAD_REQUEST, "유효하지 않는 JWT 토큰입니다.");  
        }  
    }  

    @Override  
    public void destroy() {  
        Filter.super.destroy();  
    }  
}

필터에서 Swagger 관련 경로 제외하기

if (url.startsWith("/auth") || url.startsWith("/swagger-ui") || url.startsWith("/api-docs")) {  
            chain.doFilter(request, response);  
            return;  
        }

Exception in thread main java.lang.RuntimeException Wrapper properties file

gradlew

./gradlew 중 오류 발생

원인

Gradle Wrapper의 부재

  • gradlew는 Gradle Wrapper라는 스크립트
  • 개발자 컴퓨터에 Gradle이 설치되어 있지 않더라도 gradlew/wrapper/gradle-wrapper.properties 파일에 지정된 버전의 Gradle을 자동으로 다운로드하여 프로젝트를 빌드해준다.

해결

  • Intellij 의 우측 Gradle 탭
  • Tasks -> build setup
  • wrapper 테스크를 실행한다.

jacoco 설정 오류

jacocoTestReport {  
    dependsOn(test) //순서 지정  

    reports {  
        xml.required.set(true)  
        csv.required.set(true)  
        html.required.set(true)  

        xml.destination file(project.layout.buildDirectory.dir("jacoco/index.xml")) as File  
        csv.destination file(project.layout.buildDirectory.dir("jacoco/index.csv")) as File  
        html.destination file(project.layout.buildDirectory.dir("jacoco/index.html")) as File  
    }  
    //테스트 커버리지 제외  
    afterEvaluate {  
        classDirectories.setFrom(  
                files(classDirectories.files.collect {  
                    fileTree(dir: it, excludes: [  
                            '**/*Exception*',  
                            '**/dto/**',  
                            '**/infrastructure/**'  
                            // ...  
                    ])  
                })  
        )  
    }  
    finalizedBy(jacocoTestCoverageVerification)  
}
  • Jacoco는 테스트 커버리지를 계산하기 위해 컴파일된 자바 클래스 파일을 읽어야한다.
  • 이 파일은 compileJava 태스크가 생성
  • afterEvaluate 블록을 사용하여 커버리지에서 제외할 클래스를 설정
  • jacoco의 자동 설정된 compileJava 태스크와 달라져 순서를 직접 지정해야 한다.

'Java' 카테고리의 다른 글

MSA  (6) 2025.08.02
AOP  (1) 2025.07.24
N + 1  (2) 2025.07.22
Interceptor  (4) 2025.07.21
Spring 정리  (0) 2025.07.19