使用LinkedHashMap填充JSF下拉框报Value is not valid错误咨询
Let's work through your problems step by step—this is a common gotcha when working with JSF dropdowns and object-backed options.
1. Why the "Value is not valid" error occurs with entrySet()
That error almost always means JSF can't match the submitted value to any option in your availableMovies list during form processing. Here's why this happens when using entrySet():
When you render options from availableMovies.entrySet(), each option is a Map.Entry<Integer, WebMovie> (I’m assuming your key is the movie ID). If your <h:selectOneMenu> is bound to a property like selectedMovie of type WebMovie, JSF needs to:
- Serialize the
WebMovieobject to a string for the HTML dropdown - Convert that string back to a
WebMovieobject when the form is submitted - Verify the converted object exists in the original
availableMoviescollection
If you haven’t implemented proper equals() and hashCode() methods on WebMovie, JSF will treat two WebMovie instances with the same ID as different objects. Even worse, if your getter for availableMovies recreates the map every time it’s called (e.g., calling get_all_movies() in the getter), you’ll have entirely new instances during submission vs rendering.
2. Do you need a converter? It depends.
You have two solid options to fix the error:
Option A: Implement equals() and hashCode() (Recommended)
This is the simplest approach if you control the WebMovie class. Override these methods using a unique identifier (like the movie ID) so JSF can recognize identical movies even if they’re different instances:
public class WebMovie { private Integer id; // Other fields, getters, setters @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; WebMovie webMovie = (WebMovie) o; return Objects.equals(id, webMovie.id); } @Override public int hashCode() { return Objects.hash(id); } }
Make sure your availableMovies map is initialized once (not recreated every time the getter is called—more on that below). With this, JSF will correctly match the submitted movie to the options list.
Option B: Use a Custom Converter
If you can’t modify WebMovie (or prefer to handle conversion explicitly), create a converter to translate between the movie ID string and the WebMovie object:
@FacesConverter(forClass = WebMovie.class) public class WebMovieConverter implements Converter<WebMovie> { // Inject your DAO/service here if using CDI, or initialize directly private MovieDAO movieDao = new MovieDAO(); @Override public WebMovie getAsObject(FacesContext context, UIComponent component, String value) { if (value == null || value.isBlank()) return null; try { Integer movieId = Integer.parseInt(value); return movieDao.getMovieById(movieId); // Replace with your method to fetch by ID } catch (NumberFormatException e) { throw new ConverterException(new FacesMessage("Invalid movie ID"), e); } } @Override public String getAsString(FacesContext context, UIComponent component, WebMovie value) { return value == null ? "" : value.getId().toString(); } }
JSF will automatically use this converter for any WebMovie-bound components, so no extra XHTML changes are needed (unless you need to explicitly reference it with converter="#{webMovieConverter}" for CDI-managed converters).
3. How to initialize availableMovies without the commented XHTML code
To populate your LinkedHashMap when the bean loads (instead of relying on commented XHTML), use one of these approaches in your WebRental managed bean:
Option 1: Initialize in the Constructor
Simple and straightforward for basic beans:
@ManagedBean // Or @Named if using CDI @ViewScoped // Use appropriate scope (avoid @RequestScoped if possible) public class WebRental { private LinkedHashMap<Integer, WebMovie> availableMovies; public WebRental() { this.availableMovies = get_all_movies(); } // Your existing get_all_movies() method private LinkedHashMap<Integer, WebMovie> get_all_movies() { // Fetch movies from DB and populate the map LinkedHashMap<Integer, WebMovie> movies = new LinkedHashMap<>(); // ... your DB logic here ... return movies; } // Getter for JSF to access the map public LinkedHashMap<Integer, WebMovie> getAvailableMovies() { return availableMovies; } }
Option 2: Use @PostConstruct (Better for Injected Beans)
If your bean uses dependency injection (e.g., injecting a DAO), use @PostConstruct to run initialization after injection is complete:
@ManagedBean @ViewScoped public class WebRental { private LinkedHashMap<Integer, WebMovie> availableMovies; @Inject private MovieDAO movieDao; // Example injected dependency @PostConstruct public void init() { this.availableMovies = get_all_movies(); } private LinkedHashMap<Integer, WebMovie> get_all_movies() { // Use injected movieDao to fetch data LinkedHashMap<Integer, WebMovie> movies = new LinkedHashMap<>(); for (WebMovie movie : movieDao.getAllMovies()) { movies.put(movie.getId(), movie); } return movies; } public LinkedHashMap<Integer, WebMovie> getAvailableMovies() { return availableMovies; } }
This ensures your map is populated once when the bean is created, and the getter just returns the existing map (no recreation on every call, which fixes the "Value is not valid" error caused by duplicate instances). You can safely delete the commented XHTML code once this is set up.
Quick Check List to Avoid Future Issues
- Never recreate your options list in a getter method (always initialize once in constructor/
@PostConstruct). - Ensure
equals()andhashCode()are implemented on your domain objects (likeWebMovie) using a unique key. - If using converters, make sure they correctly fetch the object from your data source (don’t hardcode or use transient data).
内容的提问来源于stack exchange,提问作者Key

