Considering the solution to the triangle is ubiquitous, I don't think there's any harm in offering another. I'll give this one a shot.
Demonstrate Recursion: There is really is no advantage to this that I could tell. Now, if you wanted an upside down triangle, then you'd be on to something. However, since you have to reach the end before you print the first line, it's of dubious value. Fear not, recursion is used, for what it's worth.
Be able to handle 1, 2 and 3 digit numbers: I hate fixed numbers. We'll assume we have infinite width and handle everything. So, yes, 1,2,3 and 4,5,6...
Format the numbers in the shape of a triangle: Given. One reason recursion isn't useful.
Print as many rows as asked: Check.
Code will be commented to explain code to other competitors: Code is its own documentation! However I threw in a couple.
Code will be written in java: This actually made it entertaining. Is there a way to use OOP that will make cleaner code than strictly procedural? I created a couple extra to classes for this. I believe they offered a somewhat cleaner solution than, say, some generic collection of arrays.
Honestly, I'm not sure how much this problem lends itself to innovation. Still, I'd be happy if someone proved me wrong.

java
class PascalRow {
private int [] values;
// default parameter is row 1
public PascalRow() {
this.values = new int[1];
this.values[0] = 1;
}
// given prior row, make next row
public PascalRow(PascalRow priorRow) {
int priorSize = priorRow.values.length;
this.values = new int[priorSize + 1];
this.values[0] = this.values[priorSize] = 1;
for(int i = 1; i < priorSize; i++) {
this.values[i] = priorRow.values[i-1] + priorRow.values[i];
}
}
// largest, and therefore longest string in this row
private int getMaxValue() { return this.values[values.length/2]; }
// all elements to conform to the largest sized element
public int getMaxValueSize() {
int size = String.valueOf(getMaxValue()).length();
// centering is cleaner if the value is always odd
return size + ((size % 2)==0 ? 1 : 0);
}
// centering is a big deal, but only this class need know
private static String getCenteredString(String s, int size) {
int padSize = (size - s.length())/2;
for(int i=0; i<padSize; i++) { s = " " + s; }
for(int i=0; i<size - s.length(); i++) { s += " "; }
return s;
}
private static String getCenteredString(int n, int size) {
return getCenteredString(String.valueOf(n), size);
}
// default is using self values, no centering
public String toString() {
return this.toString(getMaxValueSize());
}
// given a max value size, use that for each of the elements
public String toString(int valueSize) {
String s = "";
for(int i = 0; i < values.length; i++) {
if (i!=0) { s += " "; }
s += getCenteredString(this.values[i], valueSize);
}
return s;
}
// given a max line, use that as a center point
public String toString(int valueSize, int maxLineSize) {
return getCenteredString(this.toString(valueSize), maxLineSize);
}
}
// here we'll hold all the rows, populate them on construction, and off them up as a string
class PascalTriangle {
private PascalRow[] rows;
public PascalTriangle(int rowCount) {
this.rows = new PascalRow[rowCount];
this.rows[0] = new PascalRow();
generate(this.rows, 1);
}
// there is no funcational advantage to recursion
// but, it was requested for some reason
private static void generate(PascalRow[] list, int row) {
if (row<list.length) {
list[row] = new PascalRow(list[row-1]);
generate(list, row + 1);
}
}
public String toString() {
String s = "";
PascalRow lastRow = this.rows[this.rows.length-1];
int valueSize = lastRow.getMaxValueSize();
int maxLineSize = lastRow.toString().length();
for( PascalRow row : this.rows) {
s += row.toString(valueSize, maxLineSize) + "\n";
}
return s;
}
}
public class Main {
public static void main(String[] args) {
System.out.println(new PascalTriangle(10));
}
}