Repository: primefaces/primefaces (java)

Issue:
DataExporter: Numbers greater than 1000 are formatted as currency in excel exporter with numberConverter
### Describe the bug

When using `<f:numberConverter>` with type "number" and exporting with any excel format, the numbers are exported as currency when they are numbers.

![image](https://user-images.githubusercontent.com/16229253/178027169-c8c1ef87-fac9-4498-a106-62792b66555a.png)


### Reproducer

```xhtml
<p:dataTable id="table" var="r" value="#{testView.records}">
    <p:column headerText="Text Value">
        <h:outputText value="#{r.textValue}" />
    </p:column>
    <p:column headerText="Int Value">
        <h:outputText value="#{r.intValue}">
            <f:convertNumber type="number" />
        </h:outputText>
    </p:column>
    <p:column headerText="Long Value">
        <h:outputText value="#{r.longValue}">
            <f:convertNumber type="number" />
        </h:outputText>
    </p:column>
    <p:column headerText="Double Value">
        <h:outputText value="#{r.doubleValue}">
            <f:convertNumber type="number" />
        </h:outputText>
    </p:column>
    <p:column headerText="Currency Value">
        <h:outputText value="#{r.currencyValue}">
            <f:convertNumber type="currency" />
        </h:outputText>
    </p:column>
    <f:facet name="footer">
        <h:commandLink immediate="true">
            <h:outputText value="xls" />
            <p:dataExporter type="xls" target="@parent:@parent" fileName="test" />
        </h:commandLink>,
        <h:commandLink immediate="true">
            <h:outputText value="xlsx" />
            <p:dataExporter type="xlsx" target="@parent:@parent" fileName="test" />
        </h:commandLink>,
        <h:commandLink immediate="true">
            <h:outputText value="xlsxstream" />
            <p:dataExporter type="xlsxstream" target="@parent:@parent" fileName="test" />
        </h:commandLink>
    </f:facet>
</p:dataTable>
```

### Expected behavior

It should be formatted as numbers in excel at least or could be formatted with provided DecimalFormat of ExcelOptions if was set.

![image](https://user-images.githubusercontent.com/16229253/178027568-e301aac5-1e00-43a6-851d-e1dbcbcc1c03.png)


### PrimeFaces edition

Community

### PrimeFaces version

11.0.0, 12.0.0-SNAPSHOT

### Theme

_No response_

### JSF implementation

Mojarra

### JSF version

2.3

### Browser(s)

_No response_

Pull request:
Fixes #8977 - DataExporter: Numbers greater than 1000 are formatted as currency in Excel exporter with numberConverter
Fixes #8977

This modifies #8964 by changing the name of the property decimalFormat to more specific name "currencyFormat"

Patch:
diff --git a/docs/12_0_0/components/dataexporter.md b/docs/12_0_0/components/dataexporter.md
index 8a44913e42..200de8ad3a 100644
--- a/docs/12_0_0/components/dataexporter.md
+++ b/docs/12_0_0/components/dataexporter.md
@@ -340,7 +340,8 @@ public class CustomizedDocumentsView implements Serializable {
         excelOpt.setCellFontColor("#00ff00");
         excelOpt.setCellFontSize("8");
         excelOpt.setStronglyTypedCells(true);
-        excelOpt.setDecimalFormat(DecimalFormat.getCurrencyInstance(new Locale("es", "US")));
+        excelOpt.setNumberFormat(new DecimalFormat("#,##0.00"));
+        excelOpt.setCurrencyFormat(DecimalFormat.getCurrencyInstance(new Locale("es", "US")));
     }
 
     public ExcelOptions getExcelOpt() {
diff --git a/docs/12_0_0/gettingstarted/whatsnew.md b/docs/12_0_0/gettingstarted/whatsnew.md
index 3b29696478..f401ad3f21 100644
--- a/docs/12_0_0/gettingstarted/whatsnew.md
+++ b/docs/12_0_0/gettingstarted/whatsnew.md
@@ -22,7 +22,8 @@ This page contains a list of big features. Please check the GitHub issues for al
   * Added `disableOnAjax` attribute to disable the link during Ajax requests triggered by it.
 * DataExporter:
   * Added new options for `visibleOnly`, `exportHeader` and `exportFooter` to give better control over output. 
-  * ExcelOptions: Added new property `decimalFormat` in order to use a custom java.text.DecimalFormat for formatting currency in Excel export. If null, defaults to decimal format of current Locale.
+  * ExcelOptions: Added new property `numberFormat` in order to use a custom java.text.DecimalFormat for formatting numbers in Excel export. If null, defaults to decimal format of current Locale.
+  * ExcelOptions: Added new property `currencyFormat` in order to use a custom java.text.DecimalFormat for formatting currency in Excel export. If null, defaults to decimal format of current Locale.
 * FileUpload: added `clear()` widget method in SkinSimple mode to clear out selected file.
 * GMap
   * Added `Symbol` which can be used as marker icon.
diff --git a/primefaces/src/main/java/org/primefaces/component/export/ExcelOptions.java b/primefaces/src/main/java/org/primefaces/component/export/ExcelOptions.java
index 98316a1e1b..1abb9f4c25 100644
--- a/primefaces/src/main/java/org/primefaces/component/export/ExcelOptions.java
+++ b/primefaces/src/main/java/org/primefaces/component/export/ExcelOptions.java
@@ -47,7 +47,8 @@ public class ExcelOptions implements ExporterOptions {
 
     private boolean stronglyTypedCells = true;
 
-    private DecimalFormat decimalFormat;
+    private DecimalFormat numberFormat;
+    private DecimalFormat currencyFormat;
 
     public ExcelOptions() {
     }
@@ -162,11 +163,19 @@ public class ExcelOptions implements ExporterOptions {
         this.stronglyTypedCells = stronglyTypedCells;
     }
 
-    public DecimalFormat getDecimalFormat() {
-        return decimalFormat;
+    public DecimalFormat getNumberFormat() {
+        return numberFormat;
     }
 
-    public void setDecimalFormat(DecimalFormat decimalFormat) {
-        this.decimalFormat = decimalFormat;
+    public void setNumberFormat(DecimalFormat numberFormat) {
+        this.numberFormat = numberFormat;
+    }
+
+    public DecimalFormat getCurrencyFormat() {
+        return currencyFormat;
+    }
+
+    public void setCurrencyFormat(DecimalFormat currencyFormat) {
+        this.currencyFormat = currencyFormat;
     }
 }
diff --git a/primefaces/src/main/java/org/primefaces/util/BigDecimalValidator.java b/primefaces/src/main/java/org/primefaces/util/BigDecimalValidator.java
new file mode 100644
index 0000000000..329cd1c635
--- /dev/null
+++ b/primefaces/src/main/java/org/primefaces/util/BigDecimalValidator.java
@@ -0,0 +1,153 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2009-2021 PrimeTek
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.primefaces.util;
+
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+import java.text.ParsePosition;
+
+/**
+ * <p><b>BigDecimal Validation</b> and Conversion routines (<code>java.math.BigDecimal</code>).</p>
+ *
+ * <p>This validator provides a number of methods for
+ *    validating/converting a <code>String</code> value to
+ *    a <code>BigDecimal</code> using a provided <code>java.text.DecimalFormat</code>.</p>
+ *
+ * <p>Fraction/decimal values are automatically trimmed to the appropriate length.</p>
+ *
+ * Modified from Commons Validator to fit PF needs
+ * @see <a href="https://github.com/apache/commons-validator">Commons Validator</a>
+ */
+class BigDecimalValidator implements Serializable {
+
+    private static final long serialVersionUID = -670320911490506772L;
+
+    private static final BigDecimalValidator VALIDATOR = new BigDecimalValidator();
+
+    /**
+     * Return a singleton instance of this validator.
+     *
+     * @return A singleton instance of the BigDecimalValidator.
+     */
+    public static BigDecimalValidator getInstance() {
+        return VALIDATOR;
+    }
+
+    /**
+     * <p>
+     * Validate/convert a <code>BigDecimal</code> using the specified <code>DecimalFormat</code>.
+     *
+     * @param value The value validation is being performed on.
+     * @param format The format used to validate the value against.
+     * @return The parsed <code>BigDecimal</code> if valid or <code>null</code> if invalid.
+     */
+    public BigDecimal validate(String value, DecimalFormat format) {
+        return (BigDecimal) parse(value, format);
+    }
+
+    /**
+     * <p>
+     * Parse the value with the specified <code>DecimalFormat</code>.
+     * </p>
+     *
+     * @param value The value to be parsed.
+     * @param formatter The Format to parse the value with.
+     * @return The parsed value if valid or <code>null</code> if invalid.
+     */
+    protected Number parse(String value, DecimalFormat formatter) {
+        ParsePosition pos = new ParsePosition(0);
+        Number parsedValue = formatter.parse(value, pos);
+        if (pos.getErrorIndex() > -1) {
+            return null;
+        }
+
+        if (pos.getIndex() < value.length()) {
+            return null;
+        }
+
+        if (parsedValue != null) {
+            if (Double.isInfinite(parsedValue.doubleValue())) {
+                return null;
+            }
+            parsedValue = processParsedValue(parsedValue, formatter);
+        }
+
+        return parsedValue;
+    }
+
+    /**
+     * Convert the parsed value to a <code>BigDecimal</code>.
+     *
+     * @param value The parsed <code>Number</code> object created.
+     * @param formatter The Format used to parse the value with.
+     * @return The parsed <code>Number</code> converted to a <code>BigDecimal</code>.
+     */
+    protected BigDecimal processParsedValue(Number value, DecimalFormat formatter) {
+        BigDecimal decimal;
+        if (value instanceof Long) {
+            decimal = BigDecimal.valueOf((Long) value);
+        }
+        else {
+            decimal = new BigDecimal(value.toString());
+        }
+
+        int scale = determineScale(formatter);
+        if (scale >= 0) {
+            decimal = decimal.setScale(scale, RoundingMode.DOWN);
+        }
+
+        return decimal;
+    }
+
+    /**
+     * <p>
+     * Returns the <i>multiplier</i> of the <code>NumberFormat</code>.
+     * </p>
+     *
+     * @param format The <code>NumberFormat</code> to determine the multiplier of.
+     * @return The multiplying factor for the format.
+     */
+    protected int determineScale(NumberFormat format) {
+        int minimumFraction = format.getMinimumFractionDigits();
+        int maximumFraction = format.getMaximumFractionDigits();
+        if (minimumFraction != maximumFraction) {
+            return -1;
+        }
+        int scale = minimumFraction;
+        if (format instanceof DecimalFormat) {
+            int multiplier = ((DecimalFormat) format).getMultiplier();
+            if (multiplier == 100) { // CHECKSTYLE IGNORE MagicNumber
+                scale += 2; // CHECKSTYLE IGNORE MagicNumber
+            }
+            else if (multiplier == 1000) { // CHECKSTYLE IGNORE MagicNumber
+                scale += 3; // CHECKSTYLE IGNORE MagicNumber
+            }
+        }
+        return scale;
+    }
+}
diff --git a/primefaces/src/main/java/org/primefaces/util/CurrencyValidator.java b/primefaces/src/main/java/org/primefaces/util/CurrencyValidator.java
index 596f44a93d..cd6fa4cc12 100644
--- a/primefaces/src/main/java/org/primefaces/util/CurrencyValidator.java
+++ b/primefaces/src/main/java/org/primefaces/util/CurrencyValidator.java
@@ -23,10 +23,7 @@
  */
 package org.primefaces.util;
 
-import java.io.Serializable;
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-import java.text.*;
+import java.text.DecimalFormat;
 
 /**
  * <p>
@@ -42,15 +39,15 @@ import java.text.*;
  * Modified from Commons Validator to fit PF needs
  * @see <a href="https://github.com/apache/commons-validator">Commons Validator</a>
  */
-public class CurrencyValidator implements Serializable {
+public class CurrencyValidator extends BigDecimalValidator {
+    /** DecimalFormat's currency symbol */
+    public static final char CURRENCY_SYMBOL = '\u00A4';
+    public static final String CURRENCY_SYMBOL_STR = Character.toString(CURRENCY_SYMBOL);
 
     private static final long serialVersionUID = -4201640771171486514L;
 
     private static final CurrencyValidator VALIDATOR = new CurrencyValidator();
 
-    /** DecimalFormat's currency symbol */
-    private static final char CURRENCY_SYMBOL = '\u00A4';
-    private static final String CURRENCY_SYMBOL_STR = Character.toString(CURRENCY_SYMBOL);
     /** Space hack to fix Brazilian Real and maybe others */
     private static final char NON_BREAKING_SPACE = '\u00A0';
     private static final String NON_BREAKING_SPACE_STR = Character.toString(NON_BREAKING_SPACE);
@@ -64,18 +61,6 @@ public class CurrencyValidator implements Serializable {
         return VALIDATOR;
     }
 
-    /**
-     * <p>
-     * Validate/convert a <code>BigDecimal</code> using the specified <code>Locale</code>.
-     *
-     * @param value The value validation is being performed on.
-     * @param format The format to use for the number format, system default if null.
-     * @return The parsed <code>BigDecimal</code> if valid or <code>null</code> if invalid.
-     */
-    public BigDecimal validate(String value, DecimalFormat format) {
-        return (BigDecimal) parse(value, format);
-    }
-
     /**
      * <p>
      * Parse the value with the specified <code>Format</code>.
@@ -89,7 +74,8 @@ public class CurrencyValidator implements Serializable {
      * @param formatter The Format to parse the value with.
      * @return The parsed value if valid or <code>null</code> if invalid.
      */
-    protected Object parse(String value, DecimalFormat formatter) {
+    @Override
+    protected Number parse(String value, DecimalFormat formatter) {
         // check and replace currency symbol
         if (value.indexOf(CURRENCY_SYMBOL) >= 0) {
             value = value.replace(CURRENCY_SYMBOL_STR, formatter.getDecimalFormatSymbols().getCurrencySymbol());
@@ -101,7 +87,7 @@ public class CurrencyValidator implements Serializable {
         }
 
         // Initial parse of the value
-        Number parsedValue = parseFormat(value, formatter);
+        Number parsedValue = super.parse(value, formatter);
         if (parsedValue != null) {
             return parsedValue;
         }
@@ -117,106 +103,8 @@ public class CurrencyValidator implements Serializable {
             }
             DecimalFormat copyFormatter = (DecimalFormat) formatter.clone();
             copyFormatter.applyPattern(buffer.toString());
-            parsedValue = parseFormat(value, copyFormatter);
+            parsedValue = super.parse(value, copyFormatter);
         }
         return parsedValue;
     }
-
-    /**
-     * <p>
-     * Parse the value with the specified <code>Format</code>.
-     * </p>
-     *
-     * @param value The value to be parsed.
-     * @param formatter The Format to parse the value with.
-     * @return The parsed value if valid or <code>null</code> if invalid.
-     */
-    protected Number parseFormat(String value, DecimalFormat formatter) {
-        ParsePosition pos = new ParsePosition(0);
-        Number parsedValue = formatter.parse(value, pos);
-        if (pos.getErrorIndex() > -1) {
-            return null;
-        }
-
-        if (pos.getIndex() < value.length()) {
-            return null;
-        }
-
-        if (parsedValue != null) {
-            if (Double.isInfinite(parsedValue.doubleValue())) {
-                return null;
-            }
-            parsedValue = processParsedValue(parsedValue, formatter);
-        }
-
-        return parsedValue;
-
-    }
-
-    /**
-     * Convert the parsed value to a <code>BigDecimal</code>.
-     *
-     * @param value The parsed <code>Number</code> object created.
-     * @param formatter The Format used to parse the value with.
-     * @return The parsed <code>Number</code> converted to a <code>BigDecimal</code>.
-     */
-    protected BigDecimal processParsedValue(Number value, DecimalFormat formatter) {
-        BigDecimal decimal;
-        if (value instanceof Long) {
-            decimal = BigDecimal.valueOf((Long) value);
-        }
-        else {
-            decimal = new BigDecimal(value.toString());
-        }
-
-        int scale = determineScale(formatter);
-        if (scale >= 0) {
-            decimal = decimal.setScale(scale, RoundingMode.DOWN);
-        }
-
-        return decimal;
-    }
-
-    /**
-     * <p>
-     * Returns the <i>multiplier</i> of the <code>NumberFormat</code>.
-     * </p>
-     *
-     * @param format The <code>NumberFormat</code> to determine the multiplier of.
-     * @return The multiplying factor for the format.
-     */
-    protected int determineScale(NumberFormat format) {
-        int minimumFraction = format.getMinimumFractionDigits();
-        int maximumFraction = format.getMaximumFractionDigits();
-        if (minimumFraction != maximumFraction) {
-            return -1;
-        }
-        int scale = minimumFraction;
-        if (format instanceof DecimalFormat) {
-            int multiplier = ((DecimalFormat) format).getMultiplier();
-            if (multiplier == 100) { // CHECKSTYLE IGNORE MagicNumber
-                scale += 2; // CHECKSTYLE IGNORE MagicNumber
-            }
-            else if (multiplier == 1000) { // CHECKSTYLE IGNORE MagicNumber
-                scale += 3; // CHECKSTYLE IGNORE MagicNumber
-            }
-        }
-        return scale;
-    }
-
-    /**
-     * <p>
-     * Returns a <code>String</code> representing the Excel pattern for this currency.
-     * </p>
-     *
-     * @param format The format a <code>NumberFormat</code> is required for.
-     * @return The <code>String</code> pattern for using in Excel format.
-     */
-    public String getExcelPattern(DecimalFormat format) {
-        String pattern = format.toLocalizedPattern();
-        pattern =  pattern.replace(CURRENCY_SYMBOL_STR, "\"" + format.getDecimalFormatSymbols().getCurrencySymbol() + "\"");
-        String[] patterns = pattern.split(";");
-        return patterns[0];
-    }
-
 }
diff --git a/primefaces/src/main/java/org/primefaces/util/ExcelStylesManager.java b/primefaces/src/main/java/org/primefaces/util/ExcelStylesManager.java
index c8e3997512..a746c84316 100644
--- a/primefaces/src/main/java/org/primefaces/util/ExcelStylesManager.java
+++ b/primefaces/src/main/java/org/primefaces/util/ExcelStylesManager.java
@@ -28,6 +28,7 @@ import org.apache.poi.hssf.usermodel.HSSFPalette;
 import org.apache.poi.hssf.usermodel.HSSFRichTextString;
 import org.apache.poi.hssf.usermodel.HSSFWorkbook;
 import org.apache.poi.hssf.util.HSSFColor;
+import org.apache.poi.ss.usermodel.BuiltinFormats;
 import org.apache.poi.ss.usermodel.Cell;
 import org.apache.poi.ss.usermodel.CellStyle;
 import org.apache.poi.ss.usermodel.FillPatternType;
@@ -46,7 +47,9 @@ import org.primefaces.component.api.UIColumn;
 import org.primefaces.component.export.ExcelOptions;
 
 import java.awt.*;
+import java.math.BigDecimal;
 import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
 import java.util.Locale;
 
 public class ExcelStylesManager {
@@ -59,10 +62,10 @@ public class ExcelStylesManager {
     //internal
     private final boolean hssf;
     private final boolean stronglyTypedCells;
-    private final DecimalFormat decimalFormat;
+    private final DecimalFormat numberFormat, currencyFormat;
     private CellStyle facetStyle;
     private CellStyle defaultCellStyle;
-    private CellStyle numberStyle;
+    private CellStyle generalNumberStyle, formattedDecimalStyle, formattedIntegerStyle;
     private CellStyle currencyStyle;
 
     public ExcelStylesManager(Workbook wb, Locale locale, ExcelOptions options) {
@@ -72,7 +75,8 @@ public class ExcelStylesManager {
         //internal vars
         this.hssf = wb instanceof HSSFWorkbook;
         this.stronglyTypedCells = options == null || options.isStronglyTypedCells();
-        this.decimalFormat = getDecimalFormat();
+        this.numberFormat = getNumberFormat();
+        this.currencyFormat = getCurrencyFormat();
     }
 
     /**
@@ -96,10 +100,10 @@ public class ExcelStylesManager {
     private void updateCell(Cell cell, String value) {
         boolean printed = false;
         if (stronglyTypedCells) {
-            printed = applyNumberStyleIfApropiate(cell, value);
+            printed = setNumberValueIfAppropiate(cell, value);
 
             if (!printed) {
-                printed = applyCurrencyStyleIfApropiate(cell, value);
+                printed = setCurrencyValueIfAppropiate(cell, value);
             }
         }
 
@@ -110,19 +114,32 @@ public class ExcelStylesManager {
         }
     }
 
-    private boolean applyNumberStyleIfApropiate(Cell cell, String value) {
+    private boolean setNumberValueIfAppropiate(Cell cell, String value) {
         if (LangUtils.isNumeric(value)) {
-            cell.setCellValue(Double.parseDouble(value));
-            cell.setCellStyle(getNumberStyle());
+            double number = Double.parseDouble(value);
+            cell.setCellValue(number);
+            cell.setCellStyle(getGeneralNumberStyle());
             return true;
         }
         else {
+            BigDecimal number = BigDecimalValidator.getInstance().validate(value, numberFormat);
+            if (number != null) {
+                cell.setCellValue(number.doubleValue());
+                boolean hasDecimals = number.stripTrailingZeros().scale() > 0;
+                if (hasDecimals) {
+                    cell.setCellStyle(getFormattedDecimalStyle());
+                }
+                else {
+                    cell.setCellStyle(getFormattedIntegerStyle());
+                }
+                return true;
+            }
             return false;
         }
     }
 
-    private boolean applyCurrencyStyleIfApropiate(Cell cell, String value) {
-        Number currency = CurrencyValidator.getInstance().validate(value, decimalFormat);
+    private boolean setCurrencyValueIfAppropiate(Cell cell, String value) {
+        Number currency = CurrencyValidator.getInstance().validate(value, currencyFormat);
         if (currency == null) {
             return false;
         }
@@ -160,11 +177,25 @@ public class ExcelStylesManager {
         return defaultCellStyle;
     }
 
-    private CellStyle getNumberStyle() {
-        if (numberStyle == null) {
-            numberStyle = createNumberStyle();
+    private CellStyle getGeneralNumberStyle() {
+        if (generalNumberStyle == null) {
+            generalNumberStyle = createGeneralNumberStyle();
+        }
+        return generalNumberStyle;
+    }
+
+    private CellStyle getFormattedIntegerStyle() {
+        if (formattedIntegerStyle == null) {
+            formattedIntegerStyle = createFormattedIntegerStyle();
         }
-        return numberStyle;
+        return formattedIntegerStyle;
+    }
+
+    private CellStyle getFormattedDecimalStyle() {
+        if (formattedDecimalStyle == null) {
+            formattedDecimalStyle = createFormattedDecimalStyle();
+        }
+        return formattedDecimalStyle;
     }
 
     private CellStyle getCurrencyStyle() {
@@ -191,18 +222,34 @@ public class ExcelStylesManager {
         return style;
     }
 
-    private CellStyle createNumberStyle() {
+    private CellStyle createGeneralNumberStyle() {
         CellStyle style = createDefaultCellStyle();
         style.setAlignment(HorizontalAlignment.RIGHT);
-        applyCellOptions(style);
+        return style;
+    }
+
+    private CellStyle createFormattedIntegerStyle() {
+        CellStyle style = createGeneralNumberStyle();
+        String format = getFormattedIntegerExcelFormat();
+        short currencyFormat = wb.getCreationHelper().createDataFormat().getFormat(format);
+        style.setDataFormat(currencyFormat);
+        return style;
+    }
+
+    private CellStyle createFormattedDecimalStyle() {
+        CellStyle style = createGeneralNumberStyle();
+        String format = getFormattedDecimalExcelFormat();
+        short currencyFormat = wb.getCreationHelper().createDataFormat().getFormat(format);
+        style.setDataFormat(currencyFormat);
         return style;
     }
 
     private CellStyle createCurrencyStyle() {
-        CellStyle style = createNumberStyle();
-        String pattern = CurrencyValidator.getInstance().getExcelPattern(decimalFormat);
-        short currencyPattern = wb.getCreationHelper().createDataFormat().getFormat(pattern);
-        style.setDataFormat(currencyPattern);
+        CellStyle style = createDefaultCellStyle();
+        String format = getCurrencyExcelFormat();
+        short currencyFormat = wb.getCreationHelper().createDataFormat().getFormat(format);
+        style.setDataFormat(currencyFormat);
+        style.setAlignment(HorizontalAlignment.RIGHT);
         return style;
     }
 
@@ -367,9 +414,67 @@ public class ExcelStylesManager {
         }
     }
 
-    private DecimalFormat getDecimalFormat() {
-        if (options != null && options.getDecimalFormat() != null) {
-            return options.getDecimalFormat();
+    protected String getCurrencyExcelFormat() {
+        String pattern = currencyFormat.toLocalizedPattern();
+        String[] patterns = pattern.split(";");
+        return toExcelPattern(patterns[0], currencyFormat.getDecimalFormatSymbols());
+    }
+
+    protected String getFormattedIntegerExcelFormat() {
+        if (options == null || options.getNumberFormat() == null) {
+            return BuiltinFormats.getBuiltinFormat(3);
+        }
+        else {
+            return toExcelPattern(numberFormat.toLocalizedPattern(), numberFormat.getDecimalFormatSymbols());
+        }
+    }
+
+    protected String getFormattedDecimalExcelFormat() {
+        if (options == null || options.getNumberFormat() == null) {
+            return "#,##0.###";
+        }
+        else {
+            return toExcelPattern(numberFormat.toLocalizedPattern(), numberFormat.getDecimalFormatSymbols());
+        }
+    }
+
+    private String toExcelPattern(String pattern, DecimalFormatSymbols symbols) {
+
+        StringBuilder buffer = new StringBuilder(pattern.length());
+        for (int i = 0; i < pattern.length(); i++) {
+            char character = pattern.charAt(i);
+            if (character == symbols.getDecimalSeparator()) {
+                buffer.append(".");
+            }
+            else if (character == symbols.getGroupingSeparator()) {
+                buffer.append(",");
+            }
+            else if (character == symbols.getPatternSeparator()) {
+                buffer.append(";");
+            }
+            else if (character == CurrencyValidator.CURRENCY_SYMBOL) {
+                buffer.append("\"").append(symbols.getCurrencySymbol()).append("\"");
+            }
+            else {
+                buffer.append(character);
+            }
+        }
+
+        return buffer.toString();
+    }
+
+    private DecimalFormat getNumberFormat() {
+        if (options != null && options.getNumberFormat() != null) {
+            return options.getNumberFormat();
+        }
+        else {
+            return (DecimalFormat) DecimalFormat.getInstance(locale);
+        }
+    }
+
+    private DecimalFormat getCurrencyFormat() {
+        if (options != null && options.getCurrencyFormat() != null) {
+            return options.getCurrencyFormat();
         }
         else {
             return (DecimalFormat) DecimalFormat.getCurrencyInstance(locale);


Test patch:
diff --git a/primefaces/src/test/java/org/primefaces/util/BigDecimalValidatorTest.java b/primefaces/src/test/java/org/primefaces/util/BigDecimalValidatorTest.java
new file mode 100644
index 0000000000..4e927453e0
--- /dev/null
+++ b/primefaces/src/test/java/org/primefaces/util/BigDecimalValidatorTest.java
@@ -0,0 +1,110 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2009-2021 PrimeTek
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.primefaces.util;
+
+import org.junit.jupiter.api.Test;
+
+import java.math.BigDecimal;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.util.Locale;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+/**
+ * Test Case for CurrencyValidator.
+ */
+class BigDecimalValidatorTest {
+
+    private static final DecimalFormat US = (DecimalFormat) DecimalFormat.getInstance(Locale.US);
+    private static final DecimalFormat EC = (DecimalFormat) DecimalFormat.getInstance(new Locale("es", "EC"));
+    private static final DecimalFormat CUSTOM = new DecimalFormat("#,##0.00", new DecimalFormatSymbols(new Locale("es", "US")));
+
+    private static int getVersion() {
+        String version = System.getProperty("java.version");
+        if (version.startsWith("1.")) {
+            version = version.substring(2, 3);
+        }
+        else {
+            int dot = version.indexOf(".");
+            if (dot != -1) {
+                version = version.substring(0, dot);
+            }
+        }
+        return Integer.parseInt(version);
+    }
+
+    /**
+     * Test Valid currency values
+     */
+    @Test
+    public void testValid() {
+        BigDecimalValidator validator = BigDecimalValidator.getInstance();
+        BigDecimal expected = new BigDecimal("1234.56");
+        BigDecimal negative = new BigDecimal("-1234.56");
+        BigDecimal noDecimal = new BigDecimal("1234");
+        BigDecimal oneDecimal = new BigDecimal("1234.5");
+        BigDecimal threeDecimals = new BigDecimal("1234.567");
+
+        assertEquals(expected, validator.validate("1.234,56", EC), "EC locale");
+        assertEquals(negative, validator.validate("-1.234,56", EC), "EC negative");
+        assertEquals(noDecimal, validator.validate("1.234", EC), "EC no decimal");
+        assertEquals(oneDecimal, validator.validate("1.234,5", EC), "EC 1 decimal");
+        assertEquals(threeDecimals, validator.validate("1.234,567", EC), "EC 3 decimals");
+
+        assertEquals(expected, validator.validate("1,234.56", US), "US locale");
+        assertEquals(noDecimal, validator.validate("1,234", US), "US no decimal");
+        assertEquals(oneDecimal, validator.validate("1,234.5", US), "US 1 decimal");
+        assertEquals(threeDecimals, validator.validate("1,234.567", US), "US 3 decimals");
+    }
+
+    /**
+     * Test Valid integer (non-decimal) currency values
+     */
+    @Test
+    public void testIntegerValid() {
+        BigDecimalValidator validator = BigDecimalValidator.getInstance();
+        BigDecimal expected = new BigDecimal("1234");
+        BigDecimal negative = new BigDecimal("-1234");
+
+        assertEquals(expected, validator.validate("1.234", EC), "EC locale");
+        assertEquals(negative, validator.validate("-1.234", EC), "EC negative");
+
+        assertEquals(expected, validator.validate("1,234", US), "US locale");
+        assertEquals(negative, validator.validate("-1,234", US), "US negative");
+    }
+
+    /**
+     * Test Infinity is not parsed
+     */
+    @Test
+    public void testWeirdPatternIsNotParsed() {
+        BigDecimalValidator validator = BigDecimalValidator.getInstance();
+
+        Number result = validator.validate("74E12341", US);
+        assertNull(result);
+    }
+
+}
diff --git a/primefaces/src/test/java/org/primefaces/util/CurrencyValidatorTest.java b/primefaces/src/test/java/org/primefaces/util/CurrencyValidatorTest.java
index 93236675bc..c7a386793a 100644
--- a/primefaces/src/test/java/org/primefaces/util/CurrencyValidatorTest.java
+++ b/primefaces/src/test/java/org/primefaces/util/CurrencyValidatorTest.java
@@ -133,18 +133,6 @@ public class CurrencyValidatorTest {
         assertEquals(expected, validator.validate("¤20.00", uk), "UK locale");
     }
 
-    /**
-     * Test Patterns
-     */
-    @Test
-    public void testExcelPatterns() {
-        CurrencyValidator validator = CurrencyValidator.getInstance();
-
-        assertEquals("\"" + usDollar + "\"" + "#,##0.00", validator.getExcelPattern(us), "US");
-        assertEquals("\"" + ukPound + "\"" +  "#,##0.00", validator.getExcelPattern(uk), "UK");
-        assertEquals("\"" + customCurrencySymbol + "\"#,##0.00", validator.getExcelPattern(custom), "Custom");
-    }
-
     /**
      * Test Infinity is not parsed
      */
diff --git a/primefaces/src/test/java/org/primefaces/util/ExcelStylesManagerTest.java b/primefaces/src/test/java/org/primefaces/util/ExcelStylesManagerTest.java
new file mode 100644
index 0000000000..dc2f7ac480
--- /dev/null
+++ b/primefaces/src/test/java/org/primefaces/util/ExcelStylesManagerTest.java
@@ -0,0 +1,115 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2009-2021 PrimeTek
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.primefaces.util;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.primefaces.component.export.ExcelOptions;
+
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.params.provider.Arguments.arguments;
+
+public class ExcelStylesManagerTest {
+
+    private static String usDollar;
+    private static String ukPound;
+    private static String customCurrencySymbol;
+    private static DecimalFormat uk = (DecimalFormat) DecimalFormat.getCurrencyInstance(Locale.UK);
+    private static DecimalFormat us = (DecimalFormat) DecimalFormat.getCurrencyInstance(Locale.US);
+    private static DecimalFormat customNumber = new DecimalFormat("#,##0.##", new DecimalFormatSymbols(new Locale("es", "US")));
+    private static DecimalFormat customCurrency = new DecimalFormat("¤#,##0.##", new DecimalFormatSymbols(new Locale("es", "US")));
+
+    @BeforeAll
+    protected static void setUp() {
+        usDollar = us.getDecimalFormatSymbols().getCurrencySymbol();
+        ukPound = uk.getDecimalFormatSymbols().getCurrencySymbol();
+        customCurrencySymbol = customCurrency.getDecimalFormatSymbols().getCurrencySymbol();
+    }
+
+    @ParameterizedTest
+    @MethodSource("testLocales")
+    public void testFormattedDecimalExcelFormatWithLocaleOnly(Locale locale) {
+        ExcelStylesManager sut = new ExcelStylesManager(null, locale, null);
+
+        assertEquals("#,##0.###", sut.getFormattedDecimalExcelFormat(), locale.toString());
+    }
+
+    @ParameterizedTest
+    @MethodSource("testNumberDecimalFormats")
+    public void testDecimalExcelFormatWithCustomFormat(DecimalFormat numberFormat, String expectedFormat) {
+        ExcelOptions options = new ExcelOptions();
+        options.setNumberFormat(numberFormat);
+        ExcelStylesManager sut = new ExcelStylesManager(null, Locale.getDefault(), options);
+
+        assertEquals(expectedFormat, sut.getFormattedDecimalExcelFormat(), "custom currency format");
+    }
+
+    @ParameterizedTest
+    @MethodSource("testLocales")
+    public void testCurrencyExcelFormatWithLocaleOnly(Locale locale) {
+        ExcelStylesManager sut = new ExcelStylesManager(null, locale, null);
+        DecimalFormatSymbols symbols = new DecimalFormatSymbols(locale);
+
+        assertEquals("\"" + symbols.getCurrencySymbol() + "\"#,##0.00", sut.getCurrencyExcelFormat(), locale.toString());
+    }
+
+    @ParameterizedTest
+    @MethodSource("testCurrencyDecimalFormats")
+    public void testCurrencyExcelFormatWithCustomFormat(DecimalFormat currencyFormat, String expectedFormat) {
+        ExcelOptions options = new ExcelOptions();
+        options.setCurrencyFormat(currencyFormat);
+        ExcelStylesManager sut = new ExcelStylesManager(null, Locale.getDefault(), options);
+
+        assertEquals(expectedFormat, sut.getCurrencyExcelFormat(), "custom currency format");
+    }
+
+    public static List<Locale> testLocales() {
+        return Arrays.asList(Locale.US, Locale.UK, new Locale("es", "US"));
+    }
+
+    public static List<Arguments> testNumberDecimalFormats() {
+        return Arrays.asList(
+                arguments(DecimalFormat.getInstance(Locale.US), "#,##0.###"),
+                arguments(DecimalFormat.getInstance(Locale.UK), "#,##0.###"),
+                arguments(customNumber, "#,##0.##" )
+        );
+    }
+
+    public static List<Arguments> testCurrencyDecimalFormats() {
+        return Arrays.asList(
+                arguments(us, "\"" + usDollar + "\"#,##0.00"),
+                arguments(uk, "\"" + ukPound + "\"#,##0.00"),
+                arguments(customCurrency, "\"" + customCurrencySymbol + "\"#,##0.##" )
+        );
+    }
+
+}