Problema com retorno de endpoint - Aplicação Java/Spring

Boa noite pessoal,

Sou novo aqui no fórum e estou desbravando o mundo da programação há pouco tempo.
Estou tentando fazer um projeto usando Java/Spring/Hibernate.
Estou tendo o seguinte erro que vou deixar abaixo e depois um StackOverFlowError.

Já simplifiquei ao máximo o código removi todos os tratamentos de erros mas não sei o que fazer.
Caso precise do resto do código posso postar aqui também pessoal.
Obrigado

@Repository
public interface ClienteRepository extends JpaRepository<Cliente, Integer> {
	List<Cliente> findAll();
}
@AllArgsConstructor
@Data
@Service
public class ClienteService {
	private final ClienteRepository clienteRepository;
	
	public List<Cliente> getAllClients() {
		return clienteRepository.findAll();
	}
}
@AllArgsConstructor
@RestController
@RequestMapping
public class ClienteController {
	
	@GetMapping(path = "clients")
	public List<Cliente> getListAllClients() {
		return clienteService.getAllClients();
	}
}
*** java.lang.instrument ASSERTION FAILED ***: "!errorOutstanding" with message transform method call failed at s\open\src\java.instrument\share\native\libinstrument\JPLISAgent.c line: 873
*** java.lang.instrument ASSERTION FAILED ***: "!errorOutstanding" with message transform method call failed at s\open\src\java.instrument\share\native\libinstrument\JPLISAgent.c line: 873
2023-07-06T21:47:51.916-03:00  WARN 7696 --- [nio-8080-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Failure while trying to resolve exception [org.springframework.http.converter.HttpMessageNotWritableException]

java.lang.IllegalStateException: Cannot call sendError() after the response has been committed
	at org.apache.catalina.connector.ResponseFacade.checkCommitted(ResponseFacade.java:504) ~[tomcat-embed-core-10.1.8.jar:10.1.8]
	at org.apache.catalina.connector.ResponseFacade.sendError(ResponseFacade.java:348) ~[tomcat-embed-core-10.1.8.jar:10.1.8]
	at org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver.sendServerError(DefaultHandlerExceptionResolver.java:581) ~[spring-webmvc-6.0.9.jar:6.0.9]
	at org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver.handleHttpMessageNotWritable(DefaultHandlerExceptionResolver.java:548) ~[spring-webmvc-6.0.9.jar:6.0.9]
	at org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver.doResolveException(DefaultHandlerExceptionResolver.java:221) ~[spring-webmvc-6.0.9.jar:6.0.9]
	at org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver.resolveException(AbstractHandlerExceptionResolver.java:141) ~[spring-webmvc-6.0.9.jar:6.0.9]
	at org.springframework.web.servlet.handler.HandlerExceptionResolverComposite.resolveException(HandlerExceptionResolverComposite.java:80) ~[spring-webmvc-6.0.9.jar:6.0.9]
	at org.springframework.web.servlet.DispatcherServlet.processHandlerException(DispatcherServlet.java:1341) ~[spring-webmvc-6.0.9.jar:6.0.9]
	at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1152) ~[spring-webmvc-6.0.9.jar:6.0.9]
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1098) ~[spring-webmvc-6.0.9.jar:6.0.9]
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:974) ~[spring-webmvc-6.0.9.jar:6.0.9]
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1011) ~[spring-webmvc-6.0.9.jar:6.0.9]
	at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903) ~[spring-webmvc-6.0.9.jar:6.0.9]
	at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:564) ~[tomcat-embed-core-10.1.8.jar:6.0]
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) ~[spring-webmvc-6.0.9.jar:6.0.9]
	at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) ~[tomcat-embed-core-10.1.8.jar:6.0]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:205) ~[tomcat-embed-core-10.1.8.jar:10.1.8]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.8.jar:10.1.8]
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) ~[tomcat-embed-websocket-10.1.8.jar:10.1.8]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.8.jar:10.1.8]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.8.jar:10.1.8]
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-6.0.9.jar:6.0.9]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.9.jar:6.0.9]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.8.jar:10.1.8]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.8.jar:10.1.8]
	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-6.0.9.jar:6.0.9]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.9.jar:6.0.9]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.8.jar:10.1.8]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.8.jar:10.1.8]
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-6.0.9.jar:6.0.9]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.9.jar:6.0.9]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.8.jar:10.1.8]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.8.jar:10.1.8]
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:166) ~[tomcat-embed-core-10.1.8.jar:10.1.8]
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) ~[tomcat-embed-core-10.1.8.jar:10.1.8]
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:482) ~[tomcat-embed-core-10.1.8.jar:10.1.8]
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115) ~[tomcat-embed-core-10.1.8.jar:10.1.8]
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) ~[tomcat-embed-core-10.1.8.jar:10.1.8]
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[tomcat-embed-core-10.1.8.jar:10.1.8]
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:341) ~[tomcat-embed-core-10.1.8.jar:10.1.8]
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:390) ~[tomcat-embed-core-10.1.8.jar:10.1.8]
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) ~[tomcat-embed-core-10.1.8.jar:10.1.8]
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:894) ~[tomcat-embed-core-10.1.8.jar:10.1.8]
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1741) ~[tomcat-embed-core-10.1.8.jar:10.1.8]
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[tomcat-embed-core-10.1.8.jar:10.1.8]
	at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-10.1.8.jar:10.1.8]
	at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-10.1.8.jar:10.1.8]
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-10.1.8.jar:10.1.8]
	at java.base/java.lang.Thread.run(Thread.java:1623) ~[na:na]

2023-07-06T21:47:51.920-03:00 ERROR 7696 --- [nio-8080-exec-2] o.a.c.c.C.[.[.[.[dispatcherServlet]      : Servlet.service() for servlet [dispatcherServlet] in context with path [/jpaexemplo/v1] threw exception [Request processing failed: org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Infinite recursion (StackOverflowError)] with root cause

Posta a classe Cliente pra gente ver. Talvez o problema seja por conta de algum relacionamento bidirecional, que geralmente dá problema quando é transformado para json.

Olá

Vou postar a classe Cliente, e vou postar uma solução que encontrei para o problema.
Mas tenho algumas dúvidas com relação a maneira que estou fazendo, vou deixando as dúvidas abaixo também.


package br.com.tdsoft.jpaexemplo.model.Entity;

import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

@Entity
@Table(name = "cliente")
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Cliente {
    //PESQUISAR SUBSTITUIÇÃO DO @NotBlank

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int clienteId;

    @Column(name = "name")
    private String name;
    @Column(name = "main_name")
    private String mainName;
    @Column(name = "address")
    private String address;
    @Column(name = "desc_address")
    private String descAddress;
    @Column(name = "number")//, nullable = false)
    private String number;
    @Column(name = "city")
    private String city;
    @Column(name = "state")
    private String state;
    @Column(name = "cep")
    private String cep;
    @Column(name = "main_telephone")
    private String mainTelephone;
    @Column(name = "telephone2")
    private String telephone2;
    @Column(name = "Email")
    private String email;
    @Column(name = "CNPJ")
    private String cnpj;
    @Column(name = "number_gov_state")
    private String numberGovState;
    @Column(name = "number_gov_city")
    private String numberGovCity;

    @OneToMany(mappedBy = "cliente", fetch = FetchType.LAZY)
    private List<Program> programs;



    @Override
    public String toString() {
        return "Cliente{" +
                "clienteId=" + clienteId +
                ", name='" + name + '\'' +
                ", mainName='" + mainName + '\'' +
                ", address='" + address + '\'' +
                ", descAddress='" + descAddress + '\'' +
                ", number='" + number + '\'' +
                ", city='" + city + '\'' +
                ", state='" + state + '\'' +
                ", cep='" + cep + '\'' +
                ", mainTelephone='" + mainTelephone + '\'' +
                ", telephone2='" + telephone2 + '\'' +
                ", email='" + email + '\'' +
                ", cnpj='" + cnpj + '\'' +
                ", numberGovState='" + numberGovState + '\'' +
                ", numberGovCity='" + numberGovCity + '\'' +
                '}';
    }
}
@Repository
public interface ClienteRepository extends JpaRepository<Cliente, Integer> {

    List<Cliente> findAll();
}
@AllArgsConstructor
@Data
@Service
public class ClienteService {


    private final ClienteRepository clienteRepository;


    public List<ClienteDTO> getAllClients() {
        //List<Cliente> clientes = clienteRepository.findAll();


       return clienteRepository.findAll().stream()
               .filter(cliente -> cliente != null)
               .map(ClienteDTO::clienteToDto)
               .collect(Collectors.toList());



        //return  clientes;

    }
}
**CLASSE CONTROLLER**
@GetMapping("/clients")
    public ResponseEntity<List<ClienteDTO>> getListAllClients() {
        return ResponseEntity.ok().body(clienteService.getAllClients());
        /*
        List<ClienteDTO> clientesDTO = clienteService.getAllClients();


        return new ResponseEntity<>(clientesDTO, HttpStatus.OK);

         */
    }

Minha dúvida é: porque passando pelo DTO ele retorna o resultado esperado, e chamando diretamente sem passar por DTO ele não retorna?
Exemplo:
Repository List findAll();
Service List getAllClients() { return clienteRepository.findAll();}
Controller: ListgetAllClientsController();{return clienteService.getAllClients();}

O problema é por conta da lista de programas no cliente:

private List<Program> programs;

pois deve ter um Cliente em cada Program. Isso causa uma loop infinito quando for serializado para json. E por isso que funciona no DTO, pois provavelmente não tem esse relacionamento bidirecional.

Entendi Lucas, agora… O Relacionamento é 1cliente tem N programas e 1 programa tem 1 cliente.
Devo seguir declarando List programs, ou deixo Program program?

O problema não é ser uma lista ou não, eh o relacionamento bidirecional mesmo. Se sua modelagem e o negócio precisa desse relacionamento, vc deve manter. A questão é tratar o retorno usando DTO mesmo para evitar o problema quando os objetos forem serializados para json. É a melhor solução, pois mantém seu código desacoplado. Nunca é uma boa ideia retornar as entidades direto na requisição http.

Show!! Muito obrigado por me ajudar amigo. Boa tarde

Depois marca a resposta que te ajudou como solução.

Não é a solução MAS… vale a pena comentar. De maneira geral, não é uma boa idéia retornar Entities diretamente. O ideal é fazer um DTO tanto para as entradas, quanto para as saídas. Além de regras de validação diferentes, elas frequentemente tem campos diferentes.

Isso também torna o servidor mais resistente a mudanças entre as camadas e reduz as chances de você fazer cargas despreocupadas das partes lazy das entidades.

1 curtida