Suppose the following configuration bean:
#ConfigurationProperties(prefix="foo")
private class FooProperties {
private String mystring;
private int myint;
}
and the following application.properties:
foo.mystring = ${bar.mystring}
foo.myint = ${bar.myint}
Notice the two properties are unresolvable since no properties starting with bar are defined. Here is what will happen:
foo.mystring is set to the string "${bar.mystring}" (without resolution)
foo.myint will cause a conversion error since the string "${bar.myint}" cannot be converted into a valid integer.
I would instead expect a kind of Unresolvable Property exception being thrown in this case. Just like what would happen if I had use #Value("${foo.mystring}").
Is that behavior expected?
Is there a way to make SpringBoot throw such exception in this case?
I would instead expect a kind of Unresolvable Property exception being
thrown in this case.
foo.myint is not throwing Unresolvable Property exception as it is getting resolved with ${bar.myint} whereas it fails at next step when Spring try to typecast String to int as every thing you store inside a Properties file is a String value unless you explicitly specified it's type, as:
foo.myint = (java.lang.Integer)${bar.myint}
Is that behaviour expected?
Probably Yes.
Is there a way to make SpringBoot throw such exception in this case?
Yes, for Global level you can use #ControllerAdvice and if you want to have it at Controller level then you can go with #ExceptionHandler
Related
I am using sprin version 4.3.8.RELEASE. also i am using #Value to inject values from property file, if the properties are string that no problem, but if the property is Integer that is a problem (i know there is many questions about this i tried all the answers but the issue still exist)
The property is
CONNECTION.TIME.OUT=100000
First solution
#Value("${CONNECTION.TIME.OUT}")
protected Integer connectionTimeOut;
Ecxeption
Failed to convert value of type 'java.lang.String' to required type 'java.lang.Integer'; nested exception is java.lang.NumberFormatException: For input string: "${CONNECTION.TIME.OUT}"
Second solution
#Value("#{new Integer('${CONNECTION.TIME.OUT}')}")
protected Integer connectionTimeOut;
Exception
EL1003E: A problem occurred whilst attempting to construct an object of type 'Integer' using arguments '(java.lang.String)'
Third solution
#Value("#{new Integer.parseInteger('${CONNECTION.TIME.OUT}')}")
protected Integer connectionTimeOut;
Exception
EL1003E: A problem occurred whilst attempting to construct an object of type 'Integer' using arguments '(java.lang.String)'
any ideas why is that
To avoid such type of situation where the exception occurs due to un-availibilty of the property, Add default value in the tag. If property is not available then it will populate the default value
#Value("${CONNECTION.TIME.OUT:10}")
Your property file is probably not loaded properly.
When provided with no valid value for a property placeholder, Spring will automatically try to assign this value to the name of the #Value annotation. In your case, this:
#Value("#{new Integer('${CONNECTION.TIME.OUT}')}")
protected Integer connectionTimeOut;
Is interpreted as:
protected Integer connectionTimeOut = new Integer("${CONNECTION.TIME.OUT}");
Which, indeed, brings an error.
Try to either configure a PropertyPlaceholderConfigurer in your beans, or make sure that your property file is loaded properly in your classpath by your configuration. Something among the lines of:
<context:property-placeholder
ignore-unresolvable="true"
location="classpath:yourfile.properties" />
In your configuration file will help, in this case.
For #Value("${CONNECTION.TIME.OUT}") your error is java.lang.NumberFormatException: For input string: "${CONNECTION.TIME.OUT}". This means that expression was not processed resulting in Integer.parseInt("${CONNECTION.TIME.OUT}") which thrown the NumberFormatException.
Either there is no PropertyPlaceholderConfigurer bean registered in the Spring context and #Value annotations are not processed or there is no property CONNECTION.TIME.OUT defined.
Try removing single quotes worked ''. It worked for me.
#Value("#{new Integer(${CONNECTION.TIME.OUT})}")
Don't forget the "${}" around it! I kept looking at what should have been obvious and missing it.
I have ImmutableBiMap filled with 2 simple Spring beans.
OS: Manjaro Linux
JDK version: 1.8.0.102 Oracle
Spring version: 4.3.4.RELEASE from
<groupId>io.spring.platform</groupId>
<artifactId>platform-bom</artifactId>
<version>Athens-SR1</version>
Creating context throws:
Exception in thread "main" org.springframework.beans.factory.BeanCreationException:
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [...]: Illegal arguments for constructor; nested exception is java.lang.IllegalArgumentException: argument type mismatch
Caused by: java.lang.IllegalArgumentException: argument type mismatch
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
As following screen show, when exception is throw by Spring's BeanUtil argument is a LinkedHashMap instead of BiMap.
Minimal, Complete, and Verifiable example:
#Component
#Slf4j
public class TestControl {
private final BiMap<String, Integer> automatons;
#Autowired
public TestControl(BiMap<String, Integer> automatons) {
this.automatons = automatons;
log.info("automatons={}", automatons.keySet());
}
}
#Configuration
public class TextContext {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(
TextContext.class,
TestControl.class
);
BiMap bean = context.getBean(BiMap.class);
}
#Bean
BiMap<String, Integer> automatons() {
return ImmutableBiMap.of(
"Cellular Automaton", cellularAutomaton(),
"Monte Carlo Automaton", monteCarloAutomaton());
}
#Bean
Integer cellularAutomaton() {
return 6;
}
#Bean
Integer monteCarloAutomaton() {
return 5;
}
}
This is a side effect of how Spring handles some container types.
Even typed Maps can be autowired as long as the expected key type is
String. The Map values will contain all beans of the expected type,
and the keys will contain the corresponding bean names: [...]
A BiMap is a Map.
Spring isn't trying to inject your automatons bean into the TestControl. Instead, it's trying to find all beans of type Integer as the values, collecting them into a Map (LinkedHashMap as implementation of choice), and associating them with their bean name as the key.
In this case, it fails because the constructor expects a BiMap.
One solution is to inject by name.
#Autowired()
public TestControl(#Qualifier(value = "automatons") BiMap<String, Integer> automatons) {
this.automatons = automatons;
}
By specifying a qualifier with a name, Spring will instead try to find a bean (with the appropriate type) that's named automatons.
If you're not too attached to the final instance field, you could also inject the field with #Resource
#Resource(name = "automatons") // if you don't specify the name element, Spring will try to use the field name
private BiMap<String, Integer> automatons;
For reasons, this will only work 4.3+.
For beans that are themselves defined as a collection/map or array
type, #Resource is a fine solution, referring to the specific
collection or array bean by unique name. That said, as of 4.3,
collection/map and array types can be matched through Spring’s
#Autowired type matching algorithm as well, as long as the element
type information is preserved in #Bean return type signatures or
collection inheritance hierarchies. In this case, qualifier values can
be used to select among same-typed collections, as outlined in the
previous paragraph.
I would be OK with the behavior you're seeing in pre-4.3, but this does seem like a bug for Map. (The correct behavior occurs for List and array types.)
I've opened SPR-15117 to track it, which has now been resolved (2 day turnover, wow!).
Unless there is a giant bug in Spring (which I doubt) this must be a human/editor error.
I have re-created a somewhat simpler example, same basics I have just used String, Integer, Long, and Boolean since I didn't have your types - this simple example it works.
LinkedHashMap is not a BiMap, so it would be a bug if it is chosen as an autowire candidate. It almost sounds like the source and compiled code is out-of-sync, have you tried to delete the build folder and rebuild?
If rebuilding does not help, the only way to solve this is good old fashioned debugging.
Put a breakpoint inside LinkedHashMaps constructor and see where it is constructed, does it have anything to do with your beans?
Set a conditional breakpoint (so you only stop if beanName.equals( "automatonTypeSettingsControl") in org.springframework.beans.factory.support.ConstructorResolver#autowireConstructor, and step through the method so you can see how spring finds the autowire candidate;
Make the simplest standalone example which fails, put it on Github and post a link, then some one else may be able to help you debug.
Observation: I have read a lot of StackOverflow post during the last month, and it looks like the average developer is not very good a debugging thirdparty code. You can actually learn a lot from debugging other peoples code, especially the spring framework code, which I find quite easy to read, considering the problem it is solving.
Edit This turned out to be a limitation in Spring as described in another answer. That said I ended up reproducing the error and reading trough the Spring code to find the exact code for this behavior in about 1 hour. I feel that many developers overlook debugging as a software discipline. For me it is one of the most important disciplins, since you probably spend most of your time working with code you did not write yourself.
I have the following class:
class Foo
{
#NotEmpty
private String member1;
#NotEmpty
private String member2;
private String member3; //this one is optional, so has no rules
}
I have a library to which I add all the property names and corresponding UI fields, each time the UI's onChange event occurs, I call validateValue() on the given field name for that field, to validate it and show error/success message.
The problem is, in this case where I have no rules for member3, if I try to validate it by doing this:
String value = event.getValue(); //whatever the new value is now
validator.validateValue(Foo.class, "member3", value);
On the 2nd line, I get the following exception:
Caused by: java.lang.IllegalArgumentException: member3 is not a valid property of
com.xxx.Foo
Both member1 and member2 within the same class, are validated correctly.
Is there anything I can do to avoid getting this exception on the fields that don't have any rules set on them? If not, is there a way (without reflection or specifying it manually for each field) to check if a rule has no rules set on it, so i can avoid calling validateValue on it?
Which version of Hibernate Validator are you using? I checked with the latest version (5.1.0.Final) and there it works. If you can I recommend you upgrade.
You can also create an issue in the Validator issue tracker, reporting your problem and in particular which Validator version you are using.
Last but not least, to answer your question about alternatives. You could use the Bean Validation metadata API to find the constrained properties:
validator.getConstraintsForClass(Foo.class).getConstrainedProperties()
This will allow you to process only the properties which are acually constrained.
The purpose of my code is to load some settings value from a *.properties file so that I later can use these values in some if-statements in my code. I want to load in some list-structure, but since that seems hard, an array will do. I have not really gotten that far, since I am stuck at the trivial matter of loading just a String from the properties file.
When I try to debug my code that is using some spring specific data. I get some interesting behaviour, pointing on the definition in the code right above the breakpoint gives me that the variable value is null.
#Value(value = "${ViewableReportFilter.allStates.verify}")
String verifyStringStates;
public ViewableReportFilter() {
viewStates = null;
log.debug("Read in properties for states: verify:" + verifyStringStates);
/*BREAKPOINT HERE*/
in my my.properties file:
ViewableReportFilter.allStates.verify=ONHOLD
And my config to use the properties-file:
<context:property-placeholder location="classpath:properties/my.properties" order="1" ignore-unresolvable="true" />
Spring can't set the fields of an object before that object is created. The first thing Spring does is use reflection to instantiate your class. It'll use either Class#newInstance() or use Constructor#newInstance() depending on the context. Only when the constructor has finished its work and returned can Spring, again using reflection, set the value of fields.
An alternative is to put a #Value annotated parameter in the constructor parameter list and set your field inside your constructor from the argument that's given to it by Spring.
public ViewableReportFilter(#Value String verify) {
this.verifyStringStates = verify;
...
Go through the Spring documentation for its IoC container. It explains all of this in much detail.
Updated the constructor, and added Autowire annotation. No changes in the properties file, no XML.
String arrayOfStrings;
#Autowired
public ViewableReportFilter(
#Value("${TMSViewableReportFilter.allStates.verify}") String[] verifyStringStates) {
arrayOfStrings = verifyStringStates;
public logViewableReportFilter() {
log.debug("Read in properties for states: verify:" + arrayOfString);
}
Try using this:
#Value(value = "${allStates.verify}")
And in your property my.properties:
allStates.verify=ONHOLD
I'm trying to cast the output of a value to an integer:
#Value("${api.orders.pingFrequency}")
private Integer pingFrequency;
The above throws the error
org.springframework.beans.TypeMismatchException:
Failed to convert value of type 'java.lang.String' to required type 'java.lang.Integer';
nested exception is java.lang.NumberFormatException:
For input string: "(java.lang.Integer)${api.orders.pingFrequency}"
I've also tried #Value("(java.lang.Integer)${api.orders.pingFrequency}")
Google doesn't appear to say much on the subject. I'd like to always be dealing with an integer instead of having to parse this value everywhere it's used.
Workaround
I realize a workaround may be to use a setter method to run the conversion for me, but if Spring can do it I'd rather learn something about Spring.
Assuming you have a properties file on your classpath that contains
api.orders.pingFrequency=4
I tried inside a #Controller
#Controller
public class MyController {
#Value("${api.orders.pingFrequency}")
private Integer pingFrequency;
...
}
With my servlet context containing :
<context:property-placeholder location="classpath:myprops.properties" />
It worked perfectly.
So either your property is not an integer type, you don't have the property placeholder configured correctly, or you are using the wrong property key.
I tried running with an invalid property value, 4123;. The exception I got is
java.lang.NumberFormatException: For input string: "4123;"
which makes me think the value of your property is
api.orders.pingFrequency=(java.lang.Integer)${api.orders.pingFrequency}
I was looking for the answer on internet and I found the following
#Value("#{new java.text.SimpleDateFormat('${aDateFormat}').parse('${aDateStr}')}")
Date myDate;
So in your case you could try with this
#Value("#{new Integer('${api.orders.pingFrequency}')}")
private Integer pingFrequency;
I had the exact same situation. It was caused by not having a PropertySourcesPlaceholderConfigurer in the Spring context, which resolves values against the #Value annotation inside of classes.
Include a property placeholder to solve the problem, no need to use Spring expressions for integers (the property file does not have to exist if you use ignore-resource-not-found="true"):
<context:property-placeholder location="/path/to/my/app.properties"
ignore-resource-not-found="true" />
If you want to convert a property to an integer from properties file there are 2 solutions which I found:
Given scenario: customer.properties contains customer.id = 100 as a field and you want to access it in spring configuration file as integer.The property customerId is declared as type int in the Bean Customer
Solution 1:
<property name="customerId" value="#{T(java.lang.Integer).parseInt('${customer.id}')}" />
In the above line, the string value from properties file is converted to int type.
solution 2: Use some other extension inplace of propeties.For Ex.- If your properties file name is
customer.properties then make it customer.details and in the configuration file use the below code
<property name="customerId" value="${customer.id}" />
If you are using #Configuation then instantiate below static bean. If not static #Configutation is instantiated very early and and the BeanPostProcessors responsible for resolving annotations like #Value, #Autowired etc, cannot act on it. Refer here
#Bean
public static PropertySourcesPlaceholderConfigurer propertyConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
I had the same issue I solved using this. Refer this Spring MVC: #Value annotation to get int value defined in *.properties file
#Value(#{propertyfileId.propertyName})
works
when use #Value, you should add #PropertySource annotation on Class, or specify properties holder in spring's xml file.
eg.
#Component
#PropertySource("classpath:config.properties")
public class BusinessClass{
#Value("${user.name}")
private String name;
#Value("${user.age}")
private int age;
#Value("${user.registed:false}")
private boolean registed;
}
config.properties
user.name=test
user.age=20
user.registed=true
this works!
Of course, you can use placeholder xml configuration instead of annotation.
spring.xml
<context:property-placeholder location="classpath:config.properties"/>
This problem also occurs when you have 2 resources with the same file name; say "configurations.properties" within 2 different jar or directory path configured within the classpath. For example:
You have your "configurations.properties" in your process or web application (jar, war or ear). But another dependency (jar) have the same file "configurations.properties" in the same path. Then I suppose that Spring have no idea (#_#?) where to get the property and just sends the property name declared within the #Value annotation.
In my case, the problem was that my POST request was sent to the same url as GET (with get parameters using "?..=..") and that parameters had the same name as form parameters. Probably Spring is merging them into an array and parsing was throwing error.
Since using the #Value("new Long("myconfig")") with cast could throw error on startup if the config is not found or if not in the same expected number format
We used the following approach and is working as expected with fail safe check.
#Configuration()
public class MyConfiguration {
Long DEFAULT_MAX_IDLE_TIMEOUT = 5l;
#Value("db.timeoutInString")
private String timeout;
public Long getTimout() {
final Long timoutVal = StringUtil.parseLong(timeout);
if (null == timoutVal) {
return DEFAULT_MAX_IDLE_TIMEOUT;
}
return timoutVal;
}
}