Mostrar erro na tela com Thymeleaf

No controller tem este metodo
public ModelAndView salvar(@Valid PaisDTO pais, BindingResult result, RedirectAttributes attributes) {
if (result.hasErrors()) {
return novo(pais);
}
paisServico.salvar(pais);
logger.info(“Salvando um país”);
return redicionamentoListar();
}

Mesmo tendo erro não é mostrado na tela, o que pode ser ?

Controller
package br.com.netsoft.desif.controller.endereco;

import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import br.com.netsoft.desif.controller.DesifController;
import br.com.netsoft.desif.dto.endereco.PaisDTO;
import br.com.netsoft.desif.model.endereco.PaisEntity;
import br.com.netsoft.desif.servico.endereco.PaisServico;

@Controller
@RequestMapping("/pais")
public class PaisController  {

	private static final long serialVersionUID = 4315185952047207345L;

	@Autowired
	private PaisServico paisServico;

	@RequestMapping(value = "/proximo", method = RequestMethod.GET)
	public ModelAndView proximo() {
		super.totalRegistrosBD = contarTodos();
		super.proximoController();
		return redicionamentoListar();
	}

	@RequestMapping(value = "/anterior", method = RequestMethod.GET)
	public ModelAndView anterior() {
		super.anteriorController();
		return redicionamentoListar();
	}

	@RequestMapping(value = "/primeira", method = RequestMethod.GET)
	public ModelAndView primeira() {
		super.primeiraController();
		return redicionamentoListar();
	}

	@RequestMapping(value = "/ultima", method = RequestMethod.GET)
	public ModelAndView ultima() {
		super.totalRegistrosBD = contarTodos();
		super.ultimaController();
		return redicionamentoListar();
	}

	@RequestMapping(value = "/listar", method = RequestMethod.GET)
	public ModelAndView index() {
		logger.error("Listando paises");
		Long contarTodos = contarTodos();
		Integer totalPagina = super.totalPaginas(contarTodos);
		super.lista = paisServico.listar(maximoResultado, totalRegistros);
		ModelAndView modelAndView = new ModelAndView("pais/index");
		modelAndView.addObject("paises", super.lista);
		modelAndView.addObject("totalRegistros", contarTodos);
		modelAndView.addObject("totalPagina", totalPagina);
		modelAndView.addObject("paginaAtual", paginaAtual);
		return modelAndView;
	}

	@RequestMapping(value = "/salvar", method = RequestMethod.POST)
	public ModelAndView salvar(@Valid PaisDTO pais, BindingResult result, RedirectAttributes attributes) {
		if (result.hasErrors()) {
			return novo(pais);
		}
		paisServico.salvar(pais);
		logger.info("Salvando um país");
		return redicionamentoListar();
	}

	@RequestMapping(value = "/editar/{id}", method = RequestMethod.GET)
	public ModelAndView editar(@PathVariable("id") Long id) {
		logger.info("Editando um pais");
		PaisDTO paisAtual = paisServico.edit(id);
		ModelAndView modelAndView = new ModelAndView("pais/novoEditar");
		modelAndView.addObject("pais", paisAtual);
		modelAndView.addObject("tipoPagina", "Altera");
		return modelAndView;
	}

	@RequestMapping(value = "/novo", method = RequestMethod.GET)
	public ModelAndView novo(PaisDTO pais) {
		logger.info("Novo pais");
		ModelAndView modelAndView = new ModelAndView("pais/novoEditar");
		modelAndView.addObject("pais", pais);
		modelAndView.addObject("tipoPagina", "Novo");
		return modelAndView;
	}

	@RequestMapping(value = "/excluir/{id}", method = RequestMethod.GET)
	public ModelAndView excluir(@PathVariable("id") Long id) {
		logger.info("Excluir um país");
		ModelAndView modelAndView = new ModelAndView("redirect:/pais/listar");
		try {
			paisServico.excluir(id);
		} catch (Exception e) {
			logger.error(e.getMessage());
		}
		return modelAndView;
	}

	private Long contarTodos() {
		return paisServico.contarTodos();
	}

	private ModelAndView redicionamentoListar() {
		return new ModelAndView("redirect:/pais/listar");
	}
}

DTO
package br.com.netsoft.desif.dto.endereco;

import java.io.Serializable;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import org.hibernate.validator.constraints.NotEmpty;

public class PaisDTO implements Serializable {

	private static final long serialVersionUID = -6038498924353800835L;

	private Long id;

	@NotEmpty(message = "Descrição do país é obrigatório !")
	@NotNull(message = "Descrição do país é obrigatório !")
	@Size(max = 255, message = "Descrição do país deve ser menor que 255 caracteres !")
	private String descricao;

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public String getDescricao() {
		return descricao;
	}

	public void setDescricao(String descricao) {
		this.descricao = descricao;
	}
}

HTML

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
	xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
	xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4"
	layout:decorate="~{layout}">
<meta charset="utf-8">
<div layout:fragment="content">
	<div class="row wrapper border-bottom white-bg page-heading">
		<div class="col-lg-10">
			<h2>País</h2>
			<ol class="breadcrumb">
				<li><a th:href="@{/}">Página inicial</a></li>
				<li><a th:href="@{/pais/listar}">Todos paises</a></li>
				<li class="active"><strong><label
						th:text="@{{tipoPagina}(tipoPagina=${tipoPagina})}"></label> País
						</label></strong></li>
			</ol>
		</div>
		<div class="col-lg-2"></div>
	</div>
	<div class="wrapper wrapper-content animated fadeInRight">
		<div class="row">
			<div class="col-lg-12">
				<div class="ibox float-e-margins">
					<div class="ibox-title">
						<h5></h5>
						<div class="ibox-tools">
							<a class="collapse-link"> <i class="fa fa-chevron-up"></i>
							</a>
						</div>
					</div>
					<div class="ibox-content">
						<form class="form-horizontal" th:object="${pais}"
							th:action="@{/pais/salvar}" method="POST" id="pais">

							<div class="form-group">
								<label class="col-sm-2 control-label">ID</label>
								<div class="col-sm-2">
									<input type="text" class="form-control" id="id"
										th:field="*{id}" readOnly="readonly" />
								</div>
							</div>
							<div class="hr-line-dashed"></div>
							<div class="form-group">
								<label for="descricao" class="col-sm-2 control-label">Descrição</label>
								<div class="col-sm-10">
									<input id="descricao" type="text" class="form-control"
										th:field="*{descricao}" autofocus="autofocus"
										placeholder="Informe a descrição !" maxlength="255"
										onkeyup="this.value = this.value.toUpperCase()" />
									<div th:if="${#fields.hasErrors('descricao')}"
										th:errors="*{descricao}">errors</div>
								</div>
							</div>
							<div class="hr-line-dashed"></div>
							<div class="form-group row">
								<button type="submit" data-toggle="tooltip"
									data-placement="bottom" title="Salvar"
									data-original-title="Salvar">
									<i class="fa fa-save"></i>
								</button>
								<a th:href="@{/pais/listar}" data-toggle="tooltip"
									data-placement="bottom" title="Cancelar"
									data-original-title="Cancelar"><i class="fa fa-reply"></i></a>
							</div>
						</form>
					</div>
				</div>
			</div>
		</div>
	</div>
</div>
</html>

Alguma ideia desta dúvida ?

Bom dia Guilherme, tive um problema parecido.
Tenta tirar o objeto retornado no if.
@RequestMapping(value = “/salvar”, method = RequestMethod.POST)
public ModelAndView salvar(@Valid PaisDTO pais, BindingResult result, RedirectAttributes attributes) {
if (result.hasErrors()) {
return novo(pais);
}
paisServico.salvar(pais);
logger.info(“Salvando um país”);
return redicionamentoListar();
}

Na classe controle implementa a interface WebMvcConfigurer

Olá guilherme, faz um tempo que não uso o thymeleaf, mas por o spring ser desacoplado do front-end, você pode resolver esse tipo de coisa facilmente com javascript, no caso do front você está fazendo uma chamada para o controller spring, sugiro que seja via ajax, você pode capturar o erro por ajax e exibir na tela

$.ajax({
url: “suaURL”,
type: “GET,POST”,
data : {SEUS DADOS},
success: function(response){
//pode exibir a mensagem de sucesso
},
error: function(xhr, error, throwable){
//pode exibir a mensagem de erro do servidor
console.log(throwable);
}
});

Obrigado @Fabioreis1415, como o projeto estava no inicio, mudamos para angular na parte de front-end, até por uma decisão do cliente. Mas valeu.

Boa noite, mesmo tomando outra decisão por conta da problemática para o Angular, o problema está no objeto “DTO” que não é uma classe anotada com @Entity, então a Anotation @Valid, não irão capturar os erros!
Não testei, mais nesses casos se não me engano, se a classe for embedada usando @Embeddable numa Entidade, deveria funcionar os efeitos do @Valid.